import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.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/dependencies.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/services/dialog_service.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'; import 'package:screen_brightness/screen_brightness.dart'; enum VisualMode { off, invert, dim, highContrast } class AccessibilityPage extends StatefulWidget { const AccessibilityPage({super.key}); @override State createState() => _AccessibilityPageState(); } class _AccessibilityPageState extends State { late ProfileSettingsViewModel profileSettingsViewModel; int _currentFontSizeStep = 2; // 0: Small, 1: Small-Medium, 2: Medium (default), 3: Medium-Large, 4: Large VisualMode _selectedMode = VisualMode.off; // Screen brightness control final ScreenBrightness _screenBrightness = ScreenBrightness(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { if (mounted) { profileSettingsViewModel = Provider.of(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() { // Reset to system brightness when leaving accessibility page // This ensures we don't keep the preview brightness when navigating away _screenBrightness.resetScreenBrightness(); super.dispose(); } /// Apply brightness based on visual mode Future _applyBrightness(VisualMode mode) async { try { switch (mode) { case VisualMode.dim: // Reduce brightness to 40% for dim mode await _screenBrightness.setScreenBrightness(0.4); break; case VisualMode.off: case VisualMode.invert: case VisualMode.highContrast: // Reset to system brightness for other modes await _screenBrightness.resetScreenBrightness(); break; } } catch (e) { print('Error setting brightness: $e'); } } 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(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: () async { // Save accessibility settings to ViewModel profileSettingsViewModel.setFontSizeStep(_currentFontSizeStep); profileSettingsViewModel.setVisualMode(_visualModeToString(_selectedMode)); // Apply brightness changes based on selected mode await _applyBrightness(_selectedMode); // Show success bottom sheet getIt().showSuccessBottomSheetWithoutH( message: LocaleKeys.settingsSavedSuccessfully.tr(context: context), onOkPressed: () { 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.textColor, fontWeight: FontWeight.w600), LocaleKeys.fontSizeMedium.tr(context: context).toText12(color: AppColors.textColor, fontWeight: FontWeight.w600), LocaleKeys.fontSizeLarge.tr(context: context).toText12(color: AppColors.textColor, fontWeight: FontWeight.w600), ], ), ], ), ], ), ); } 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.textColor : AppColors.textColor, weight: FontWeight.w600), ], ), ); } Widget _buildPreviewCard() { // Always build preview content with base "Off" mode colors // Use raw widgets WITHOUT preservation wrappers to isolate from saved visual mode 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: SvgPicture.asset( AppAssets.arrow_forward, colorFilter: ColorFilter.mode(AppColors.blackBgColor, BlendMode.srcIn), width: 24.w, height: 24.h, ), ), ], ), 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), SvgPicture.asset( AppAssets.saudi_riyal_icon, colorFilter: ColorFilter.mode(AppColors.blackBgColor, BlendMode.srcIn), width: 16.w, height: 16.h, ), ], ), SizedBox(height: 16.h), // Button - simple container without preservation wrappers Container( height: 44.h, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), decoration: BoxDecoration( color: AppColors.primaryRedColor, borderRadius: BorderRadius.circular(12.r), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( AppAssets.add_icon, colorFilter: ColorFilter.mode(AppColors.whiteColor, BlendMode.srcIn), width: 20.w, height: 20.h, ), SizedBox(width: 8.w), LocaleKeys.buttonText.tr(context: context).toText14( isBold: true, color: AppColors.whiteColor, ), ], ), ), ], ), ); // First, cancel out the global app filter by applying reverse filter Widget neutralizedContent = _cancelGlobalFilter(previewContent); // Then apply the selected mode filter on top of the neutralized content return _applySelectedModeFilter(neutralizedContent); } /// Cancel out the global app filter to get back to base colors Widget _cancelGlobalFilter(Widget child) { final savedMode = profileSettingsViewModel.visualMode; switch (savedMode) { case 'invert': // Global filter is invert, apply reverse (invert again) to get base colors return ColorFiltered( colorFilter: const ColorFilter.matrix([ -1, 0, 0, 0, 255, 0, -1, 0, 0, 255, 0, 0, -1, 0, 255, 0, 0, 0, 1, 0, ]), child: child, ); case 'dim': // Global filter has overlay, but we can't reverse an overlay // Just return child as-is (dim affects whole screen anyway) return child; case 'highContrast': // Global filter is grayscale, apply saturation boost to approximate reverse return ColorFiltered( colorFilter: const ColorFilter.matrix([ 1.5, 0, 0, 0, 0, 0, 1.5, 0, 0, 0, 0, 0, 1.5, 0, 0, 0, 0, 0, 1, 0, ]), child: child, ); case 'off': default: // No global filter, return as-is return child; } } /// Apply the selected mode filter to the preview Widget _applySelectedModeFilter(Widget child) { switch (_selectedMode) { case VisualMode.invert: return ColorFiltered( colorFilter: const ColorFilter.matrix([ -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: child, ); case VisualMode.dim: // For dim mode, we control actual screen brightness // No filter is applied - images remain unchanged return child; case VisualMode.highContrast: return ColorFiltered( colorFilter: const ColorFilter.matrix([ 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: child, ); case VisualMode.off: return child; } } }