Compare commits
8 Commits
6eaef4250b
...
8a94f5b476
| Author | SHA1 | Date |
|---|---|---|
|
|
8a94f5b476 | 2 months ago |
|
|
cdc6cf1fec | 2 months ago |
|
|
1587dddda4 | 2 months ago |
|
|
ce23c0302a | 2 months ago |
|
|
02e44ed7fb | 2 months ago |
|
|
c8378278c9 | 2 months ago |
|
|
e7758a8142 | 2 months ago |
|
|
826d12e57d | 2 months ago |
@ -0,0 +1,147 @@
|
|||||||
|
class ExplanationsResponseModel {
|
||||||
|
final ExplanationsDataDetails? dataDetails;
|
||||||
|
|
||||||
|
ExplanationsResponseModel({this.dataDetails});
|
||||||
|
|
||||||
|
factory ExplanationsResponseModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExplanationsResponseModel(
|
||||||
|
dataDetails: json['dataDetails'] != null
|
||||||
|
? ExplanationsDataDetails.fromJson(json['dataDetails'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'dataDetails': dataDetails?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExplanationsDataDetails {
|
||||||
|
final List<ExplanationEvidence>? supportingEvidence;
|
||||||
|
final List<ExplanationEvidence>? conflictingEvidence;
|
||||||
|
final List<ExplanationEvidence>? unconfirmedEvidence;
|
||||||
|
final String? message;
|
||||||
|
final List<String>? errorList;
|
||||||
|
final int? id;
|
||||||
|
final String? language;
|
||||||
|
final String? generalId;
|
||||||
|
final String? createDate;
|
||||||
|
final String? lastEditDate;
|
||||||
|
final String? createdBy;
|
||||||
|
final String? lastEditBy;
|
||||||
|
final bool? active;
|
||||||
|
final int? sortOrder;
|
||||||
|
final int? userType;
|
||||||
|
final String? userId;
|
||||||
|
|
||||||
|
ExplanationsDataDetails({
|
||||||
|
this.supportingEvidence,
|
||||||
|
this.conflictingEvidence,
|
||||||
|
this.unconfirmedEvidence,
|
||||||
|
this.message,
|
||||||
|
this.errorList,
|
||||||
|
this.id,
|
||||||
|
this.language,
|
||||||
|
this.generalId,
|
||||||
|
this.createDate,
|
||||||
|
this.lastEditDate,
|
||||||
|
this.createdBy,
|
||||||
|
this.lastEditBy,
|
||||||
|
this.active,
|
||||||
|
this.sortOrder,
|
||||||
|
this.userType,
|
||||||
|
this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExplanationsDataDetails.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExplanationsDataDetails(
|
||||||
|
supportingEvidence: json['supporting_evidence'] != null
|
||||||
|
? (json['supporting_evidence'] as List)
|
||||||
|
.map((item) => ExplanationEvidence.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
conflictingEvidence: json['conflicting_evidence'] != null
|
||||||
|
? (json['conflicting_evidence'] as List)
|
||||||
|
.map((item) => ExplanationEvidence.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
unconfirmedEvidence: json['unconfirmed_evidence'] != null
|
||||||
|
? (json['unconfirmed_evidence'] as List)
|
||||||
|
.map((item) => ExplanationEvidence.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
message: json['Message'],
|
||||||
|
errorList: json['ErrorList'] != null
|
||||||
|
? List<String>.from(json['ErrorList'])
|
||||||
|
: null,
|
||||||
|
id: json['Id'],
|
||||||
|
language: json['language'],
|
||||||
|
generalId: json['generalId'],
|
||||||
|
createDate: json['CreateDate'],
|
||||||
|
lastEditDate: json['LastEditDate'],
|
||||||
|
createdBy: json['CreatedBy'],
|
||||||
|
lastEditBy: json['LastEditBy'],
|
||||||
|
active: json['Active'],
|
||||||
|
sortOrder: json['SortOrder'],
|
||||||
|
userType: json['userType'],
|
||||||
|
userId: json['userId'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'supporting_evidence': supportingEvidence?.map((item) => item.toJson()).toList(),
|
||||||
|
'conflicting_evidence': conflictingEvidence?.map((item) => item.toJson()).toList(),
|
||||||
|
'unconfirmed_evidence': unconfirmedEvidence?.map((item) => item.toJson()).toList(),
|
||||||
|
'Message': message,
|
||||||
|
'ErrorList': errorList,
|
||||||
|
'Id': id,
|
||||||
|
'language': language,
|
||||||
|
'generalId': generalId,
|
||||||
|
'CreateDate': createDate,
|
||||||
|
'LastEditDate': lastEditDate,
|
||||||
|
'CreatedBy': createdBy,
|
||||||
|
'LastEditBy': lastEditBy,
|
||||||
|
'Active': active,
|
||||||
|
'SortOrder': sortOrder,
|
||||||
|
'userType': userType,
|
||||||
|
'userId': userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExplanationEvidence {
|
||||||
|
final String? id;
|
||||||
|
final String? name;
|
||||||
|
final String? commonName;
|
||||||
|
|
||||||
|
ExplanationEvidence({
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.commonName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExplanationEvidence.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExplanationEvidence(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
commonName: json['common_name'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'common_name': commonName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get display name
|
||||||
|
String getDisplayName() {
|
||||||
|
return commonName ?? name ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
class RationaleResponseModel {
|
||||||
|
final RationaleDataDetails? dataDetails;
|
||||||
|
|
||||||
|
RationaleResponseModel({this.dataDetails});
|
||||||
|
|
||||||
|
factory RationaleResponseModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RationaleResponseModel(
|
||||||
|
dataDetails: json['dataDetails'] != null
|
||||||
|
? RationaleDataDetails.fromJson(json['dataDetails'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'dataDetails': dataDetails?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RationaleDataDetails {
|
||||||
|
final String? type;
|
||||||
|
final List<RationaleObservationParam>? observationParams;
|
||||||
|
final List<RationaleConditionParam>? conditionParams;
|
||||||
|
final String? message;
|
||||||
|
final List<String>? errorList;
|
||||||
|
final int? id;
|
||||||
|
final String? language;
|
||||||
|
final String? generalId;
|
||||||
|
final String? createDate;
|
||||||
|
final String? lastEditDate;
|
||||||
|
final String? createdBy;
|
||||||
|
final String? lastEditBy;
|
||||||
|
final bool? active;
|
||||||
|
final int? sortOrder;
|
||||||
|
final int? userType;
|
||||||
|
final String? userId;
|
||||||
|
|
||||||
|
RationaleDataDetails({
|
||||||
|
this.type,
|
||||||
|
this.observationParams,
|
||||||
|
this.conditionParams,
|
||||||
|
this.message,
|
||||||
|
this.errorList,
|
||||||
|
this.id,
|
||||||
|
this.language,
|
||||||
|
this.generalId,
|
||||||
|
this.createDate,
|
||||||
|
this.lastEditDate,
|
||||||
|
this.createdBy,
|
||||||
|
this.lastEditBy,
|
||||||
|
this.active,
|
||||||
|
this.sortOrder,
|
||||||
|
this.userType,
|
||||||
|
this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RationaleDataDetails.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RationaleDataDetails(
|
||||||
|
type: json['type'],
|
||||||
|
observationParams: json['observation_params'] != null
|
||||||
|
? (json['observation_params'] as List)
|
||||||
|
.map((item) => RationaleObservationParam.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
conditionParams: json['condition_params'] != null
|
||||||
|
? (json['condition_params'] as List)
|
||||||
|
.map((item) => RationaleConditionParam.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
message: json['Message'],
|
||||||
|
errorList: json['ErrorList'] != null
|
||||||
|
? List<String>.from(json['ErrorList'])
|
||||||
|
: null,
|
||||||
|
id: json['Id'],
|
||||||
|
language: json['language'],
|
||||||
|
generalId: json['generalId'],
|
||||||
|
createDate: json['CreateDate'],
|
||||||
|
lastEditDate: json['LastEditDate'],
|
||||||
|
createdBy: json['CreatedBy'],
|
||||||
|
lastEditBy: json['LastEditBy'],
|
||||||
|
active: json['Active'],
|
||||||
|
sortOrder: json['SortOrder'],
|
||||||
|
userType: json['userType'],
|
||||||
|
userId: json['userId'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'type': type,
|
||||||
|
'observation_params': observationParams?.map((item) => item.toJson()).toList(),
|
||||||
|
'condition_params': conditionParams?.map((item) => item.toJson()).toList(),
|
||||||
|
'Message': message,
|
||||||
|
'ErrorList': errorList,
|
||||||
|
'Id': id,
|
||||||
|
'language': language,
|
||||||
|
'generalId': generalId,
|
||||||
|
'CreateDate': createDate,
|
||||||
|
'LastEditDate': lastEditDate,
|
||||||
|
'CreatedBy': createdBy,
|
||||||
|
'LastEditBy': lastEditBy,
|
||||||
|
'Active': active,
|
||||||
|
'SortOrder': sortOrder,
|
||||||
|
'userType': userType,
|
||||||
|
'userId': userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RationaleObservationParam {
|
||||||
|
final String? id;
|
||||||
|
final String? name;
|
||||||
|
final String? commonName;
|
||||||
|
|
||||||
|
RationaleObservationParam({
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.commonName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RationaleObservationParam.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RationaleObservationParam(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
commonName: json['common_name'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'common_name': commonName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get display name
|
||||||
|
String getDisplayName() {
|
||||||
|
return commonName ?? name ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RationaleConditionParam {
|
||||||
|
final String? id;
|
||||||
|
final String? name;
|
||||||
|
final String? commonName;
|
||||||
|
|
||||||
|
RationaleConditionParam({
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.commonName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RationaleConditionParam.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RationaleConditionParam(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
commonName: json['common_name'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'common_name': commonName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get display name
|
||||||
|
String getDisplayName() {
|
||||||
|
return commonName ?? name ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,151 @@
|
|||||||
|
class TriageLevelResponseModel {
|
||||||
|
final TriageLevelDataDetails? dataDetails;
|
||||||
|
|
||||||
|
TriageLevelResponseModel({this.dataDetails});
|
||||||
|
|
||||||
|
factory TriageLevelResponseModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TriageLevelResponseModel(
|
||||||
|
dataDetails: json['dataDetails'] != null
|
||||||
|
? TriageLevelDataDetails.fromJson(json['dataDetails'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'dataDetails': dataDetails?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TriageLevelDataDetails {
|
||||||
|
final String? triageLevel;
|
||||||
|
final List<SeriousEvidence>? serious;
|
||||||
|
final String? rootCause;
|
||||||
|
final bool? teleconsultationApplicable;
|
||||||
|
final String? message;
|
||||||
|
final List<String>? errorList;
|
||||||
|
final int? id;
|
||||||
|
final String? language;
|
||||||
|
final String? generalId;
|
||||||
|
final String? createDate;
|
||||||
|
final String? lastEditDate;
|
||||||
|
final String? createdBy;
|
||||||
|
final String? lastEditBy;
|
||||||
|
final bool? active;
|
||||||
|
final int? sortOrder;
|
||||||
|
final int? userType;
|
||||||
|
final String? userId;
|
||||||
|
|
||||||
|
TriageLevelDataDetails({
|
||||||
|
this.triageLevel,
|
||||||
|
this.serious,
|
||||||
|
this.rootCause,
|
||||||
|
this.teleconsultationApplicable,
|
||||||
|
this.message,
|
||||||
|
this.errorList,
|
||||||
|
this.id,
|
||||||
|
this.language,
|
||||||
|
this.generalId,
|
||||||
|
this.createDate,
|
||||||
|
this.lastEditDate,
|
||||||
|
this.createdBy,
|
||||||
|
this.lastEditBy,
|
||||||
|
this.active,
|
||||||
|
this.sortOrder,
|
||||||
|
this.userType,
|
||||||
|
this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TriageLevelDataDetails.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TriageLevelDataDetails(
|
||||||
|
triageLevel: json['triage_level'],
|
||||||
|
serious: json['serious'] != null
|
||||||
|
? (json['serious'] as List)
|
||||||
|
.map((item) => SeriousEvidence.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
rootCause: json['root_cause'],
|
||||||
|
teleconsultationApplicable: json['teleconsultation_applicable'],
|
||||||
|
message: json['Message'],
|
||||||
|
errorList: json['ErrorList'] != null
|
||||||
|
? List<String>.from(json['ErrorList'])
|
||||||
|
: null,
|
||||||
|
id: json['Id'],
|
||||||
|
language: json['language'],
|
||||||
|
generalId: json['generalId'],
|
||||||
|
createDate: json['CreateDate'],
|
||||||
|
lastEditDate: json['LastEditDate'],
|
||||||
|
createdBy: json['CreatedBy'],
|
||||||
|
lastEditBy: json['LastEditBy'],
|
||||||
|
active: json['Active'],
|
||||||
|
sortOrder: json['SortOrder'],
|
||||||
|
userType: json['userType'],
|
||||||
|
userId: json['userId'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'triage_level': triageLevel,
|
||||||
|
'serious': serious?.map((item) => item.toJson()).toList(),
|
||||||
|
'root_cause': rootCause,
|
||||||
|
'teleconsultation_applicable': teleconsultationApplicable,
|
||||||
|
'Message': message,
|
||||||
|
'ErrorList': errorList,
|
||||||
|
'Id': id,
|
||||||
|
'language': language,
|
||||||
|
'generalId': generalId,
|
||||||
|
'CreateDate': createDate,
|
||||||
|
'LastEditDate': lastEditDate,
|
||||||
|
'CreatedBy': createdBy,
|
||||||
|
'LastEditBy': lastEditBy,
|
||||||
|
'Active': active,
|
||||||
|
'SortOrder': sortOrder,
|
||||||
|
'userType': userType,
|
||||||
|
'userId': userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SeriousEvidence {
|
||||||
|
final String? id;
|
||||||
|
final String? name;
|
||||||
|
final String? commonName;
|
||||||
|
final String? seriousness;
|
||||||
|
final bool? isEmergency;
|
||||||
|
|
||||||
|
SeriousEvidence({
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.commonName,
|
||||||
|
this.seriousness,
|
||||||
|
this.isEmergency,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SeriousEvidence.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SeriousEvidence(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
commonName: json['common_name'],
|
||||||
|
seriousness: json['seriousness'],
|
||||||
|
isEmergency: json['is_emergency'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'common_name': commonName,
|
||||||
|
'seriousness': seriousness,
|
||||||
|
'is_emergency': isEmergency,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get display name
|
||||||
|
String getDisplayName() {
|
||||||
|
return commonName ?? name ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,476 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
||||||
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||||
|
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||||
|
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
enum VisualMode { off, invert, dim, highContrast }
|
||||||
|
|
||||||
|
class AccessibilityPage extends StatefulWidget {
|
||||||
|
const AccessibilityPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccessibilityPage> createState() => _AccessibilityPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccessibilityPageState extends State<AccessibilityPage> {
|
||||||
|
late ProfileSettingsViewModel profileSettingsViewModel;
|
||||||
|
int _currentFontSizeStep = 2; // 0: Small, 1: Small-Medium, 2: Medium (default), 3: Medium-Large, 4: Large
|
||||||
|
VisualMode _selectedMode = VisualMode.off;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
profileSettingsViewModel = Provider.of<ProfileSettingsViewModel>(context, listen: false);
|
||||||
|
// Load saved settings from ViewModel
|
||||||
|
_loadSavedSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadSavedSettings() {
|
||||||
|
setState(() {
|
||||||
|
_currentFontSizeStep = profileSettingsViewModel.fontSizeStep;
|
||||||
|
_selectedMode = _visualModeFromString(profileSettingsViewModel.visualMode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
VisualMode _visualModeFromString(String mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'off':
|
||||||
|
return VisualMode.off;
|
||||||
|
case 'invert':
|
||||||
|
return VisualMode.invert;
|
||||||
|
case 'dim':
|
||||||
|
return VisualMode.dim;
|
||||||
|
case 'highContrast':
|
||||||
|
return VisualMode.highContrast;
|
||||||
|
default:
|
||||||
|
return VisualMode.off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _visualModeToString(VisualMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case VisualMode.off:
|
||||||
|
return 'off';
|
||||||
|
case VisualMode.invert:
|
||||||
|
return 'invert';
|
||||||
|
case VisualMode.dim:
|
||||||
|
return 'dim';
|
||||||
|
case VisualMode.highContrast:
|
||||||
|
return 'highContrast';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getFontSizeForStep(int step) {
|
||||||
|
const double mediumSize = 40.0; // Medium is baseline
|
||||||
|
switch (step) {
|
||||||
|
case 0:
|
||||||
|
return mediumSize * 0.5; // Small: 20.0
|
||||||
|
case 1:
|
||||||
|
return mediumSize * 0.75; // Small-Medium: 30.0
|
||||||
|
case 2:
|
||||||
|
return mediumSize; // Medium (default): 40.0
|
||||||
|
case 3:
|
||||||
|
return mediumSize * 1.5; // Medium-Large: 60.0
|
||||||
|
case 4:
|
||||||
|
return mediumSize * 1.75; // Large: 70.0
|
||||||
|
default:
|
||||||
|
return mediumSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
profileSettingsViewModel = Provider.of<ProfileSettingsViewModel>(context, listen: false);
|
||||||
|
|
||||||
|
return CollapsingListView(
|
||||||
|
title: LocaleKeys.accessibility.tr(context: context),
|
||||||
|
isClose: true,
|
||||||
|
bottomChild: Container(
|
||||||
|
padding: EdgeInsets.all(24.w),
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
borderRadius: 24.r,
|
||||||
|
hasShadow: false,
|
||||||
|
),
|
||||||
|
child: CustomButton(
|
||||||
|
height: 56.h,
|
||||||
|
icon: AppAssets.checkmark_icon,
|
||||||
|
iconColor: AppColors.whiteColor,
|
||||||
|
text: LocaleKeys.saveChanges.tr(context: context),
|
||||||
|
backgroundColor: AppColors.primaryRedColor,
|
||||||
|
borderColor: AppColors.primaryRedColor,
|
||||||
|
textColor: AppColors.whiteColor,
|
||||||
|
fontSize: 16.f,
|
||||||
|
isBold: true,
|
||||||
|
onPressed: () {
|
||||||
|
// Save accessibility settings to ViewModel
|
||||||
|
profileSettingsViewModel.setFontSizeStep(_currentFontSizeStep);
|
||||||
|
profileSettingsViewModel.setVisualMode(_visualModeToString(_selectedMode));
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: LocaleKeys.settingsSavedSuccessfully.tr(context: context).toText14(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
),
|
||||||
|
backgroundColor: AppColors.successColor,
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pop the page
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.only(top: 16.h, bottom: 24.h, left: 24.w, right: 24.w),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Font Size Card
|
||||||
|
_buildFontSizeCard(),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
|
||||||
|
// Mode for Partially Blind Card
|
||||||
|
_buildVisualModeCard(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFontSizeCard() {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24.w),
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
borderRadius: 24.r,
|
||||||
|
hasShadow: false,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Card Label
|
||||||
|
LocaleKeys.fontSize.tr(context: context).toText18(isBold: true),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
|
||||||
|
// Center Aa heading that changes size
|
||||||
|
Container(
|
||||||
|
height: 70.h,
|
||||||
|
child: Center(
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: _getFontSizeForStep(_currentFontSizeStep),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.blackBgColor,
|
||||||
|
),
|
||||||
|
child: const Text("Aa"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 32.h),
|
||||||
|
|
||||||
|
// Stepped slider
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
// Stack to position circles on top of the bar
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// Grey bar
|
||||||
|
Container(
|
||||||
|
height: 4.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.greyTextColor.withValues(alpha: 0.2),
|
||||||
|
borderRadius: BorderRadius.circular(2.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Step indicator with dots positioned on top of the bar
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
bool isSelected = index == _currentFontSizeStep;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_currentFontSizeStep = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(8.w), // Increased tap zone
|
||||||
|
child: Container(
|
||||||
|
width: isSelected ? 20.w : 12.w,
|
||||||
|
height: isSelected ? 20.w : 12.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? AppColors.primaryRedColor : Colors.transparent,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? AppColors.primaryRedColor : AppColors.greyTextColor.withValues(alpha: 0.3),
|
||||||
|
width: 2.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
LocaleKeys.fontSizeSmall.tr(context: context).toText12(color: AppColors.greyTextColor),
|
||||||
|
LocaleKeys.fontSizeMedium.tr(context: context).toText12(color: AppColors.greyTextColor),
|
||||||
|
LocaleKeys.fontSizeLarge.tr(context: context).toText12(color: AppColors.greyTextColor),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVisualModeCard() {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24.w),
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
borderRadius: 24.r,
|
||||||
|
hasShadow: false,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Card Label
|
||||||
|
LocaleKeys.blindModes.tr(context: context).toText18(isBold: true),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
|
||||||
|
// Two columns layout
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Left Column - Radio buttons
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildRadioOption(VisualMode.off, LocaleKeys.offTheme.tr(context: context)),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
_buildRadioOption(VisualMode.invert, LocaleKeys.invertTheme.tr(context: context)),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
_buildRadioOption(VisualMode.dim, LocaleKeys.dimTheme.tr(context: context)),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
_buildRadioOption(VisualMode.highContrast, LocaleKeys.highContrastTheme.tr(context: context)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16.w),
|
||||||
|
|
||||||
|
// Right Column - Preview Card
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: _buildPreviewCard(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRadioOption(VisualMode mode, String label) {
|
||||||
|
bool isSelected = _selectedMode == mode;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selectedMode = mode;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 20.w,
|
||||||
|
height: 20.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? AppColors.primaryRedColor : AppColors.greyTextColor,
|
||||||
|
width: 2.w,
|
||||||
|
),
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: isSelected
|
||||||
|
? Center(
|
||||||
|
child: Container(
|
||||||
|
width: 10.w,
|
||||||
|
height: 10.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: AppColors.primaryRedColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
label.toText14(
|
||||||
|
isBold: isSelected,
|
||||||
|
color: isSelected ? AppColors.blackBgColor : AppColors.greyTextColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPreviewCard() {
|
||||||
|
// Always build preview content with base "Off" mode colors
|
||||||
|
Widget previewContent = Container(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
borderRadius: 20.r,
|
||||||
|
isCustomShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color(0xff000000).withValues(alpha: 0.1),
|
||||||
|
blurRadius: 16,
|
||||||
|
spreadRadius: 16 * 0.1, // 10% of blur radius
|
||||||
|
offset: const Offset(0, 0), // Uniform shadow on all sides
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Top row with heading and arrow
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
LocaleKeys.cardHeading.tr(context: context).toText14(
|
||||||
|
isBold: true,
|
||||||
|
color: AppColors.blackBgColor, // Always use base color
|
||||||
|
),
|
||||||
|
Transform.scale(
|
||||||
|
scaleX: Utils.appState.isArabic() ? -1 : 1,
|
||||||
|
child: Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.arrow_forward,
|
||||||
|
iconColor: AppColors.blackBgColor, // Always use base color
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
|
||||||
|
// Price with SAR symbol
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
"1234".toText24(
|
||||||
|
isBold: true,
|
||||||
|
color: AppColors.blackBgColor, // Always use base color
|
||||||
|
),
|
||||||
|
SizedBox(width: 4.w),
|
||||||
|
LocaleKeys.riyal.tr(context: context).toText14(
|
||||||
|
color: AppColors.blackBgColor, // Always use base color
|
||||||
|
),
|
||||||
|
SizedBox(width: 4.w),
|
||||||
|
Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.saudi_riyal_icon,
|
||||||
|
iconColor: AppColors.blackBgColor, // Always use base color
|
||||||
|
width: 16.w,
|
||||||
|
height: 16.w,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
|
||||||
|
// Button - always show with base "Off" colors
|
||||||
|
CustomButton(
|
||||||
|
height: 44.h,
|
||||||
|
icon: AppAssets.add_icon,
|
||||||
|
iconColor: AppColors.whiteColor,
|
||||||
|
// Always use base color
|
||||||
|
text: LocaleKeys.buttonText.tr(context: context),
|
||||||
|
backgroundColor: AppColors.primaryRedColor,
|
||||||
|
// Always use base color
|
||||||
|
borderColor: AppColors.primaryRedColor,
|
||||||
|
// Always use base color
|
||||||
|
textColor: AppColors.whiteColor,
|
||||||
|
// Always use base color
|
||||||
|
fontSize: 14.f,
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply visual mode filters to preview
|
||||||
|
switch (_selectedMode) {
|
||||||
|
case VisualMode.invert:
|
||||||
|
return ColorFiltered(
|
||||||
|
colorFilter: const ColorFilter.matrix(<double>[
|
||||||
|
-1, 0, 0, 0, 255, // Red channel inverted
|
||||||
|
0, -1, 0, 0, 255, // Green channel inverted
|
||||||
|
0, 0, -1, 0, 255, // Blue channel inverted
|
||||||
|
0, 0, 0, 1, 0, // Alpha channel unchanged
|
||||||
|
]),
|
||||||
|
child: previewContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
case VisualMode.dim:
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
previewContent,
|
||||||
|
Positioned.fill(
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.blackBgColor.withValues(alpha: 0.3),
|
||||||
|
borderRadius: BorderRadius.circular(20.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
case VisualMode.highContrast:
|
||||||
|
return ColorFiltered(
|
||||||
|
colorFilter: const ColorFilter.matrix(<double>[
|
||||||
|
0.2126, 0.7152, 0.0722, 0, 0, // Red channel (grayscale)
|
||||||
|
0.2126, 0.7152, 0.0722, 0, 0, // Green channel (grayscale)
|
||||||
|
0.2126, 0.7152, 0.0722, 0, 0, // Blue channel (grayscale)
|
||||||
|
0, 0, 0, 1, 0, // Alpha channel unchanged
|
||||||
|
]),
|
||||||
|
child: previewContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
case VisualMode.off:
|
||||||
|
return previewContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,231 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/api_consts.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_state.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
||||||
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||||
|
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||||
|
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
||||||
|
|
||||||
|
// Before using the checkup, please read the Terms of Service and remember:
|
||||||
|
//
|
||||||
|
// Checkup isn't a diagnosis. It's only for your information and not a qualified medical opinion.
|
||||||
|
// Checkup isn't for emergencies. Call your local emergency number right away when there's a health emergency.
|
||||||
|
// Your data is safe. Privacy measures ensure the information you share is protected.
|
||||||
|
class UserConsentScreen extends StatefulWidget {
|
||||||
|
const UserConsentScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UserConsentScreen> createState() => _UserConsentScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserConsentScreenState extends State<UserConsentScreen> {
|
||||||
|
bool _isTermsAccepted = false;
|
||||||
|
bool _isPrivacyAccepted = false;
|
||||||
|
|
||||||
|
void _onTermsAndConditionsAgreed() {
|
||||||
|
setState(() {
|
||||||
|
_isTermsAccepted = !_isTermsAccepted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPrivacyPolicyAgreed() {
|
||||||
|
setState(() {
|
||||||
|
_isPrivacyAccepted = !_isPrivacyAccepted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _canProceed => _isTermsAccepted && _isPrivacyAccepted;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColors.bgScaffoldColor,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CollapsingListView(
|
||||||
|
title: LocaleKeys.termsService.tr(),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
"Before using the checkup, please read the Terms of Service and remember:".toText14(),
|
||||||
|
SizedBox(height: 44.h),
|
||||||
|
"• Checkup isn't a diagnosis. It's only for your information and not a qualified medical opinion.".toText14(),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
"• Checkup isn't for emergencies. Call your local emergency number right away when there's a health emergency.".toText14(),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
"• Your data is safe. Privacy measures ensure the information you share is protected.".toText14(),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _onTermsAndConditionsAgreed,
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.only(bottom: 12.h),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
width: 24.w,
|
||||||
|
height: 24.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(5.r),
|
||||||
|
border: Border.all(
|
||||||
|
color: _isTermsAccepted ? AppColors.primaryRedColor : AppColors.checkBoxBorderColor,
|
||||||
|
width: 1.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _isTermsAccepted ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
|
||||||
|
),
|
||||||
|
SizedBox(width: 12.w),
|
||||||
|
Expanded(
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: LocaleKeys.iAgreeAndAcceptTermsAndConditions.tr(context: context),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.f,
|
||||||
|
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textColor,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
AppState appState = getIt.get<AppState>();
|
||||||
|
Utils.openWebView(
|
||||||
|
url: appState.isArabic() ? AppUrls.hmgTermsUrlAr : AppUrls.hmgTermsUrlEn,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
LocaleKeys.termsAndConditions.tr(context: context),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.f,
|
||||||
|
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.primaryRedColor,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: AppColors.primaryRedColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 16.h,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _onPrivacyPolicyAgreed,
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.only(bottom: 12.h),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
width: 24.w,
|
||||||
|
height: 24.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _isPrivacyAccepted ? AppColors.primaryRedColor : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(5.r),
|
||||||
|
border: Border.all(
|
||||||
|
color: _isPrivacyAccepted ? AppColors.primaryRedColor : AppColors.checkBoxBorderColor,
|
||||||
|
width: 1.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _isPrivacyAccepted ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
|
||||||
|
),
|
||||||
|
SizedBox(width: 12.w),
|
||||||
|
Expanded(
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: LocaleKeys.iAgreeHealthInfoCanBeUsed.tr(context: context),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.f,
|
||||||
|
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textColor,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
AppState appState = getIt.get<AppState>();
|
||||||
|
Utils.openWebView(
|
||||||
|
url: appState.isArabic() ? AppUrls.hmgPrivacyUrlAr : AppUrls.hmgPrivacyUrlEn,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
LocaleKeys.privacyPolicyText.tr(context: context),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.f,
|
||||||
|
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.primaryRedColor,
|
||||||
|
decorationColor: AppColors.primaryRedColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingAll(24.w),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildStickyBottomCard(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStickyBottomCard(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CustomButton(
|
||||||
|
text: LocaleKeys.next.tr(context: context),
|
||||||
|
onPressed: _canProceed ? () => context.navigateWithName(AppRoutes.userInfoSelection) : null,
|
||||||
|
isDisabled: !_canProceed,
|
||||||
|
backgroundColor: AppColors.primaryRedColor,
|
||||||
|
borderColor: AppColors.primaryRedColor,
|
||||||
|
textColor: AppColors.whiteColor,
|
||||||
|
fontSize: 16.f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
],
|
||||||
|
).paddingSymmetrical(24.w, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,280 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_level_response_model.dart';
|
||||||
|
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
||||||
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||||
|
|
||||||
|
class TriageLevelCard extends StatefulWidget {
|
||||||
|
final TriageLevelDataDetails triageLevelData;
|
||||||
|
|
||||||
|
const TriageLevelCard({super.key, required this.triageLevelData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TriageLevelCard> createState() => _TriageLevelCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TriageLevelCardState extends State<TriageLevelCard> with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _borderAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// Create animation controller for border pulse effect
|
||||||
|
_animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 2000),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create animation that goes from 1.0 to 0.3 and back (for opacity)
|
||||||
|
_borderAnimation = Tween<double>(begin: 1.0, end: 0.3).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start the animation and repeat
|
||||||
|
_animationController.repeat(reverse: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get localized title based on triage level
|
||||||
|
String getTitle(BuildContext context) {
|
||||||
|
final triageLevel = widget.triageLevelData.triageLevel ?? '';
|
||||||
|
|
||||||
|
switch (triageLevel) {
|
||||||
|
case 'emergency_ambulance':
|
||||||
|
return LocaleKeys.triageLevelEmergencyAmbulance.tr(context: context);
|
||||||
|
case 'emergency':
|
||||||
|
return LocaleKeys.triageLevelEmergency.tr(context: context);
|
||||||
|
case 'consultation_24':
|
||||||
|
return LocaleKeys.triageLevelConsultation24.tr(context: context);
|
||||||
|
case 'consultation':
|
||||||
|
return LocaleKeys.triageLevelConsultation.tr(context: context);
|
||||||
|
case 'self_care':
|
||||||
|
return LocaleKeys.triageLevelSelfCare.tr(context: context);
|
||||||
|
default:
|
||||||
|
return LocaleKeys.triageLevelConsultation.tr(context: context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get localized description based on triage level
|
||||||
|
String getDescription(BuildContext context) {
|
||||||
|
final triageLevel = widget.triageLevelData.triageLevel ?? '';
|
||||||
|
|
||||||
|
switch (triageLevel) {
|
||||||
|
case 'emergency_ambulance':
|
||||||
|
return LocaleKeys.triageLevelEmergencyAmbulanceDesc.tr(context: context);
|
||||||
|
case 'emergency':
|
||||||
|
return LocaleKeys.triageLevelEmergencyDesc.tr(context: context);
|
||||||
|
case 'consultation_24':
|
||||||
|
return LocaleKeys.triageLevelConsultation24Desc.tr(context: context);
|
||||||
|
case 'consultation':
|
||||||
|
return LocaleKeys.triageLevelConsultationDesc.tr(context: context);
|
||||||
|
case 'self_care':
|
||||||
|
return LocaleKeys.triageLevelSelfCareDesc.tr(context: context);
|
||||||
|
default:
|
||||||
|
return LocaleKeys.triageLevelConsultationDesc.tr(context: context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get color based on triage level
|
||||||
|
Color getBackgroundColor() {
|
||||||
|
final triageLevel = widget.triageLevelData.triageLevel ?? '';
|
||||||
|
|
||||||
|
switch (triageLevel) {
|
||||||
|
case 'emergency_ambulance':
|
||||||
|
case 'emergency':
|
||||||
|
return AppColors.chipColorEmergency.withValues(alpha: 0.1);
|
||||||
|
case 'consultation_24':
|
||||||
|
case 'consultation':
|
||||||
|
return AppColors.chipColorSeekMedicalAdvice.withValues(alpha: 0.1);
|
||||||
|
case 'self_care':
|
||||||
|
return AppColors.chipColorMonitor.withValues(alpha: 0.1);
|
||||||
|
default:
|
||||||
|
return AppColors.chipColorSeekMedicalAdvice.withValues(alpha: 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get border color based on triage level
|
||||||
|
Color getBorderColor() {
|
||||||
|
final triageLevel = widget.triageLevelData.triageLevel ?? '';
|
||||||
|
|
||||||
|
switch (triageLevel) {
|
||||||
|
case 'emergency_ambulance':
|
||||||
|
case 'emergency':
|
||||||
|
return AppColors.chipColorEmergency;
|
||||||
|
case 'consultation_24':
|
||||||
|
case 'consultation':
|
||||||
|
return AppColors.chipColorSeekMedicalAdvice;
|
||||||
|
case 'self_care':
|
||||||
|
return AppColors.chipColorMonitor;
|
||||||
|
default:
|
||||||
|
return AppColors.chipColorSeekMedicalAdvice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get icon based on triage level
|
||||||
|
IconData getIcon() {
|
||||||
|
final triageLevel = widget.triageLevelData.triageLevel ?? '';
|
||||||
|
|
||||||
|
switch (triageLevel) {
|
||||||
|
case 'emergency_ambulance':
|
||||||
|
return Icons.local_hospital;
|
||||||
|
case 'emergency':
|
||||||
|
return Icons.emergency;
|
||||||
|
case 'consultation_24':
|
||||||
|
return Icons.schedule;
|
||||||
|
case 'consultation':
|
||||||
|
return Icons.medical_services;
|
||||||
|
case 'self_care':
|
||||||
|
return Icons.self_improvement;
|
||||||
|
default:
|
||||||
|
return Icons.medical_services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get root cause text (localized)
|
||||||
|
String? getRootCauseText(BuildContext context) {
|
||||||
|
final rootCause = widget.triageLevelData.rootCause ?? '';
|
||||||
|
|
||||||
|
switch (rootCause) {
|
||||||
|
case 'emergency_evidence_present':
|
||||||
|
return LocaleKeys.rootCauseEmergencyEvidencePresent.tr(context: context);
|
||||||
|
case 'serious_evidence_present':
|
||||||
|
return LocaleKeys.rootCauseSeriousEvidencePresent.tr(context: context);
|
||||||
|
case 'emergency_condition_likely':
|
||||||
|
return LocaleKeys.rootCauseEmergencyConditionLikely.tr(context: context);
|
||||||
|
case 'emergency_condition_possible':
|
||||||
|
return LocaleKeys.rootCauseEmergencyConditionPossible.tr(context: context);
|
||||||
|
case 'consultation_condition_likely':
|
||||||
|
return LocaleKeys.rootCauseConsultationConditionLikely.tr(context: context);
|
||||||
|
case 'self_care_sufficient':
|
||||||
|
return LocaleKeys.rootCauseSelfCareSufficient.tr(context: context);
|
||||||
|
case 'diagnosis_unknown':
|
||||||
|
return LocaleKeys.rootCauseDiagnosisUnknown.tr(context: context);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final borderColor = getBorderColor();
|
||||||
|
final title = getTitle(context);
|
||||||
|
final description = getDescription(context);
|
||||||
|
final rootCauseText = getRootCauseText(context);
|
||||||
|
final seriousSymptoms = widget.triageLevelData.serious ?? [];
|
||||||
|
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _borderAnimation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 8.h),
|
||||||
|
padding: EdgeInsets.all(20.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
borderRadius: BorderRadius.circular(24.r),
|
||||||
|
border: Border.all(
|
||||||
|
color: borderColor.withValues(alpha: _borderAnimation.value),
|
||||||
|
width: 2.w,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.08),
|
||||||
|
blurRadius: 10.r,
|
||||||
|
offset: Offset(0, 2.h),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: title.toText18(isBold: true, color: AppColors.textColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
|
||||||
|
// Description
|
||||||
|
description.toText14(color: AppColors.textColor),
|
||||||
|
|
||||||
|
// Root Cause (if available)
|
||||||
|
if (rootCauseText != null) ...[
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 0.w, vertical: 8.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.whiteColor.withValues(alpha: 0.5),
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
size: 16.f,
|
||||||
|
color: borderColor,
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
Flexible(
|
||||||
|
child: rootCauseText.toText12(
|
||||||
|
color: AppColors.textColor,
|
||||||
|
isBold: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Serious Symptoms (if any)
|
||||||
|
if (seriousSymptoms.isNotEmpty) ...[
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
LocaleKeys.seriousSymptoms.tr(context: context).toText14(
|
||||||
|
isBold: true,
|
||||||
|
color: borderColor,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
...seriousSymptoms.map((symptom) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 4.h),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(top: 6.h, right: 8.w, left: 8.w),
|
||||||
|
width: 6.w,
|
||||||
|
height: 6.h,
|
||||||
|
decoration: BoxDecoration(shape: BoxShape.circle, color: borderColor),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: (symptom.getDisplayName()).toText13(color: AppColors.textColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue