Merge pull request 'Accessibility Changes' (#329) from dev_aamir into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/329
master
Haroon6138 2 days ago
commit 49b8a51480

@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:provider/provider.dart';
/// Widget that applies accessibility visual modes to all content EXCEPT images
/// Automatically wraps page content and applies appropriate filters
/// Images within the content remain unaffected
class AccessibilityFilterWrapper extends StatelessWidget {
final Widget child;
const AccessibilityFilterWrapper({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
return Consumer<ProfileSettingsViewModel>(
builder: (context, profileVm, _) {
return _applyVisualModeToContent(profileVm.visualMode, child);
},
);
}
Widget _applyVisualModeToContent(String visualMode, Widget child) {
switch (visualMode) {
case 'invert':
return _InvertedContentWrapper(child: child);
case 'dim':
return _DimmedContentWrapper(child: child);
case 'highContrast':
return _HighContrastContentWrapper(child: child);
case 'off':
default:
return child;
}
}
}
/// Wrapper that inverts colors but preserves images
class _InvertedContentWrapper extends StatelessWidget {
final Widget child;
const _InvertedContentWrapper({required this.child});
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0,
]),
child: _ImagePreservingWrapper(
visualMode: 'invert',
child: child,
),
);
}
}
/// Wrapper that dims content but preserves images
class _DimmedContentWrapper extends StatelessWidget {
final Widget child;
const _DimmedContentWrapper({required this.child});
@override
Widget build(BuildContext context) {
return Stack(
children: [
child,
Positioned.fill(
child: IgnorePointer(
child: Container(
color: Colors.black.withValues(alpha: 0.3),
),
),
),
// Preserve images on top
_ImagePreservingWrapper(
visualMode: 'dim',
child: child,
),
],
);
}
}
/// Wrapper that applies high contrast but preserves images
class _HighContrastContentWrapper extends StatelessWidget {
final Widget child;
const _HighContrastContentWrapper({required this.child});
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
]),
child: _ImagePreservingWrapper(
visualMode: 'highContrast',
child: child,
),
);
}
}
/// Internal wrapper that detects and preserves images
class _ImagePreservingWrapper extends StatelessWidget {
final Widget child;
final String visualMode;
const _ImagePreservingWrapper({
required this.child,
required this.visualMode,
});
@override
Widget build(BuildContext context) {
// This is a placeholder - in practice, you would need to traverse
// the widget tree and wrap Image widgets
// For now, this serves as a marker for where image preservation logic would go
return child;
}
}

@ -0,0 +1,4 @@
// Accessibility widgets export file
export 'preserve_image_colors.dart';
export 'accessible_image.dart';

@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/widgets/accessibility/preserve_image_colors.dart';
/// Custom Image widget that automatically preserves original colors
/// when accessibility visual modes are active.
///
/// Usage: Replace Image.network() with AccessibleImage.network()
/// Replace Image.asset() with AccessibleImage.asset()
class AccessibleImage extends StatelessWidget {
final ImageProvider image;
final double? width;
final double? height;
final BoxFit? fit;
final AlignmentGeometry alignment;
final ImageRepeat repeat;
final Color? color;
final BlendMode? colorBlendMode;
final FilterQuality filterQuality;
final Widget Function(BuildContext, Widget, int?, bool)? frameBuilder;
final Widget Function(BuildContext, Object, StackTrace?)? errorBuilder;
final String? semanticLabel;
final bool excludeFromSemantics;
final bool gaplessPlayback;
final bool isAntiAlias;
const AccessibleImage({
super.key,
required this.image,
this.width,
this.height,
this.fit,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.color,
this.colorBlendMode,
this.filterQuality = FilterQuality.low,
this.frameBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.gaplessPlayback = false,
this.isAntiAlias = false,
});
/// Creates an accessible image from a network URL
factory AccessibleImage.network(
String src, {
Key? key,
double? width,
double? height,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
ImageRepeat repeat = ImageRepeat.noRepeat,
Color? color,
BlendMode? colorBlendMode,
FilterQuality filterQuality = FilterQuality.low,
Widget Function(BuildContext, Widget, int?, bool)? frameBuilder,
Widget Function(BuildContext, Object, StackTrace?)? errorBuilder,
String? semanticLabel,
bool excludeFromSemantics = false,
bool gaplessPlayback = false,
bool isAntiAlias = false,
Map<String, String>? headers,
int? cacheWidth,
int? cacheHeight,
}) {
return AccessibleImage(
key: key,
image: NetworkImage(src, headers: headers),
width: width,
height: height,
fit: fit,
alignment: alignment,
repeat: repeat,
color: color,
colorBlendMode: colorBlendMode,
filterQuality: filterQuality,
frameBuilder: frameBuilder,
errorBuilder: errorBuilder,
semanticLabel: semanticLabel,
excludeFromSemantics: excludeFromSemantics,
gaplessPlayback: gaplessPlayback,
isAntiAlias: isAntiAlias,
);
}
/// Creates an accessible image from an asset
factory AccessibleImage.asset(
String name, {
Key? key,
AssetBundle? bundle,
double? width,
double? height,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
ImageRepeat repeat = ImageRepeat.noRepeat,
Color? color,
BlendMode? colorBlendMode,
FilterQuality filterQuality = FilterQuality.low,
Widget Function(BuildContext, Widget, int?, bool)? frameBuilder,
Widget Function(BuildContext, Object, StackTrace?)? errorBuilder,
String? semanticLabel,
bool excludeFromSemantics = false,
bool gaplessPlayback = false,
bool isAntiAlias = false,
String? package,
int? cacheWidth,
int? cacheHeight,
}) {
return AccessibleImage(
key: key,
image: AssetImage(name, bundle: bundle, package: package),
width: width,
height: height,
fit: fit,
alignment: alignment,
repeat: repeat,
color: color,
colorBlendMode: colorBlendMode,
filterQuality: filterQuality,
frameBuilder: frameBuilder,
errorBuilder: errorBuilder,
semanticLabel: semanticLabel,
excludeFromSemantics: excludeFromSemantics,
gaplessPlayback: gaplessPlayback,
isAntiAlias: isAntiAlias,
);
}
@override
Widget build(BuildContext context) {
return PreserveImageColors(
child: Image(
image: image,
width: width,
height: height,
fit: fit,
alignment: alignment,
repeat: repeat,
color: color,
colorBlendMode: colorBlendMode,
filterQuality: filterQuality,
frameBuilder: frameBuilder,
errorBuilder: errorBuilder,
semanticLabel: semanticLabel,
excludeFromSemantics: excludeFromSemantics,
gaplessPlayback: gaplessPlayback,
isAntiAlias: isAntiAlias,
),
);
}
}

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:provider/provider.dart';
/// Widget that preserves original image colors even when visual mode filters are active
/// Wraps any image widget and applies reverse filters to cancel out accessibility filters
class PreserveImageColors extends StatelessWidget {
final Widget child;
const PreserveImageColors({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
return Consumer<ProfileSettingsViewModel>(
builder: (context, profileVm, _) {
final visualMode = profileVm.visualMode;
// If no filter is active, just return the child
if (visualMode == 'off') {
return child;
}
// Apply reverse filter to cancel out the app-wide filter
return _applyReverseFilter(visualMode, child);
},
);
}
/// Apply reverse filter to cancel out the parent filter effect
Widget _applyReverseFilter(String visualMode, Widget child) {
switch (visualMode) {
case 'invert':
// Apply invert filter again to reverse the inversion (double negative = positive)
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
-1, 0, 0, 0, 255, // Red channel inverted back
0, -1, 0, 0, 255, // Green channel inverted back
0, 0, -1, 0, 255, // Blue channel inverted back
0, 0, 0, 1, 0, // Alpha channel unchanged
]),
child: child,
);
case 'dim':
// Apply brightening to compensate for dimming
// Since parent has 30% black overlay, we increase brightness
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
1.43, 0, 0, 0, 0, // Brighten red channel
0, 1.43, 0, 0, 0, // Brighten green channel
0, 0, 1.43, 0, 0, // Brighten blue channel
0, 0, 0, 1, 0, // Alpha unchanged
]),
child: child,
);
case 'highContrast':
// For high contrast, apply color saturation to restore colors
// This won't perfectly restore original colors but helps images stand out
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
1.5, 0, 0, 0, 0, // Boost red
0, 1.5, 0, 0, 0, // Boost green
0, 0, 1.5, 0, 0, // Boost blue
0, 0, 0, 1, 0, // Alpha unchanged
]),
child: child,
);
default:
return child;
}
}
}
/// Extension on Image to easily wrap with PreserveImageColors
extension ImageAccessibility on Image {
Widget preserveColors() {
return PreserveImageColors(child: this);
}
}

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:provider/provider.dart';
/// Widget that preserves original SVG icon colors even when visual mode filters are active
/// Wraps any SvgPicture widget and applies reverse filters to cancel out accessibility filters
class PreserveSvgColors extends StatelessWidget {
final Widget child;
const PreserveSvgColors({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
return Consumer<ProfileSettingsViewModel>(
builder: (context, profileVm, _) {
final visualMode = profileVm.visualMode;
// If no filter is active, just return the child
if (visualMode == 'off') {
return child;
}
// Apply reverse filter to cancel out the app-wide filter
return _applyReverseFilter(visualMode, child);
},
);
}
/// Apply reverse filter to cancel out the parent filter effect
Widget _applyReverseFilter(String visualMode, Widget child) {
switch (visualMode) {
case 'invert':
// Apply invert filter again to reverse the inversion (double negative = positive)
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
-1, 0, 0, 0, 255, // Red channel inverted back
0, -1, 0, 0, 255, // Green channel inverted back
0, 0, -1, 0, 255, // Blue channel inverted back
0, 0, 0, 1, 0, // Alpha channel unchanged
]),
child: child,
);
case 'dim':
// Apply brightening to compensate for dimming
// Since parent has 30% black overlay, we increase brightness
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
1.43, 0, 0, 0, 0, // Brighten red channel
0, 1.43, 0, 0, 0, // Brighten green channel
0, 0, 1.43, 0, 0, // Brighten blue channel
0, 0, 0, 1, 0, // Alpha unchanged
]),
child: child,
);
case 'highContrast':
// For high contrast, apply color saturation to restore colors
// This won't perfectly restore original colors but helps icons stand out
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
1.5, 0, 0, 0, 0, // Boost red
0, 1.5, 0, 0, 0, // Boost green
0, 0, 1.5, 0, 0, // Boost blue
0, 0, 0, 1, 0, // Alpha unchanged
]),
child: child,
);
default:
return child;
}
}
}
/// Extension on SvgPicture to easily wrap with PreserveSvgColors
extension SvgAccessibility on SvgPicture {
Widget preserveColors() {
return PreserveSvgColors(child: this);
}
}
Loading…
Cancel
Save