Compare commits
No commits in common. 'c5b77e595d7fd0185fede162a812a1bd6019648b' and '8a94f5b4769cc57ae7702b69d19a0409d6f126c1' have entirely different histories.
c5b77e595d
...
8a94f5b476
@ -1,135 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
// Accessibility widgets export file
|
|
||||||
export 'preserve_image_colors.dart';
|
|
||||||
export 'accessible_image.dart';
|
|
||||||
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
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…
Reference in New Issue