Skip to content

Commit

Permalink
feat(digital-guide): add accessibility modes dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
simon-the-shark committed Jan 18, 2025
1 parent 8075640 commit f38e00d
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import "dart:async";

import "package:flutter/material.dart";

import "../../../../config/ui_config.dart";
import "../../../../theme/app_theme.dart";
import "../../tabs/accessibility_dialog/presentation/accessibility_dialog.dart";

class AccessibilityButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 8),
child: OutlinedButton(
onPressed: () {},
onPressed: () {
unawaited(
showDialog(
context: context,
builder: (_) => const AccessibilityDialog(),
),
);
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import "dart:async";

import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:riverpod_annotation/riverpod_annotation.dart";

import "../data/accessibility_mode_repository.dart";
import "../data/modes.dart";

part "accessibility_mode_service.g.dart";

// if the mode has children, it will be calculated based on them
// if the mode has a key, it will be calculated based on the repository
@riverpod
class AccessibilityModeService extends _$AccessibilityModeService {
@override
Future<bool> build(AccessibilityMode mode) async {
return switch (mode) {
ModeWithChildren() => _calculateModeWithChildrenState(mode),
ModeWithKey() => ref.watch(
accessibilityModeRepositoryProvider(mode).future,
),
};
}

Future<void> setMode({required bool newValue}) async {
final modeStronglyTyped = mode; // needed for typing system
await switch (modeStronglyTyped) {
ModeWithChildren() =>
_setModeWithChildrenState(modeStronglyTyped, newValue),
ModeWithKey() => _setSingularModeState(modeStronglyTyped, newValue),
};
}

// true if any of its children are true
Future<bool> _calculateModeWithChildrenState(ModeWithChildren mode) async {
final submodesValues = await Future.wait(
mode.children.map(
(child) => ref.watch(accessibilityModeServiceProvider(child).future),
),
);
return submodesValues.anyIs(true);
}

// sets all childrens' of the mode to newValue
Future<void> _setModeWithChildrenState(
ModeWithChildren mode,
bool newValue,
) async {
for (final child in mode.children) {
await ref
.read(accessibilityModeServiceProvider(child).notifier)
.setMode(newValue: newValue);
}
}

// calls directly the repository
Future<void> _setSingularModeState(
ModeWithKey modeStronglyTyped,
bool newValue,
) {
return ref
.read(
accessibilityModeRepositoryProvider(modeStronglyTyped).notifier,
)
.setMode(newValue: newValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import "dart:async";

import "package:riverpod_annotation/riverpod_annotation.dart";

import "../../../../../config/shared_prefs.dart";
import "modes.dart";

part "accessibility_mode_repository.g.dart";

@riverpod
class AccessibilityModeRepository extends _$AccessibilityModeRepository {
@override
Future<bool> build(ModeWithKey mode) async {
final prefs = await ref.watch(sharedPreferencesSingletonProvider.future);
return prefs.getBool(mode.sharedPrefsKey) ?? false;
}

Future<void> setMode({required bool newValue}) async {
state = AsyncValue.data(newValue);
final prefs = await ref.watch(sharedPreferencesSingletonProvider.future);
await prefs.setBool(mode.sharedPrefsKey, newValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// https://dart.dev/language/class-modifiers#sealed
sealed class AccessibilityMode {
const AccessibilityMode();
}

// this mode's state depends on the state of its children
sealed class ModeWithChildren extends AccessibilityMode {
const ModeWithChildren(this.children);
final List<AccessibilityMode> children;
}

// this mode's state is stored locally in shared preferences
sealed class ModeWithKey extends AccessibilityMode {
const ModeWithKey(this.sharedPrefsKey);
final String sharedPrefsKey;
}

class MotorImpairment extends ModeWithKey {
const MotorImpairment() : super("_prefs_accessibility_motor_impairment");
}

class Blind extends ModeWithKey {
const Blind() : super("_prefs_accessibility_blind");
}

class LowVision extends ModeWithKey {
const LowVision() : super("_prefs_accessibility_low_vision");
}

class SensorySensitivity extends ModeWithKey {
const SensorySensitivity()
: super("_prefs_accessibility_sensory_sensitivity");
}

class CognitiveImpairment extends ModeWithKey {
const CognitiveImpairment()
: super("_prefs_accessibility_cognitive_impairment");
}

class VisualImpairment extends ModeWithChildren {
const VisualImpairment()
: super(
const [
LowVision(),
Blind(),
],
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "package:flutter/material.dart";

import "../../../../../utils/context_extensions.dart";
import "checkboxes_list.dart";
import "red_dialog.dart";

class AccessibilityDialog extends StatelessWidget {
const AccessibilityDialog({super.key});

@override
Widget build(BuildContext context) {
return RedDialog(
title: context.localize.accessibility_profiles,
subtitle: context.localize.you_can_adjust,
child: const CheckboxesList(),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";

import "../data/modes.dart";
import "mode_checkbox.dart";

class _SubModePadding extends Padding {
const _SubModePadding({super.child})
: super(
padding: const EdgeInsets.only(left: 25),
);
}

class CheckboxesList extends HookWidget {
const CheckboxesList({super.key});

@override
Widget build(BuildContext context) {
return const Column(
children: [
ModeCheckbox(MotorImpairment()),
ModeCheckbox(VisualImpairment()),
_SubModePadding(child: ModeCheckbox(Blind())),
_SubModePadding(child: ModeCheckbox(LowVision())),
ModeCheckbox(SensorySensitivity()),
ModeCheckbox(CognitiveImpairment()),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "package:flutter/material.dart";

import "../../../../../utils/context_extensions.dart";
import "../data/modes.dart";

extension AccessibilityModeLocalizationX on AccessibilityMode {
String localizedLabel(BuildContext context) {
return switch (this) {
MotorImpairment() => context.localize.motorImpairment,
VisualImpairment() => context.localize.visualImpairment,
Blind() => context.localize.blind,
LowVision() => context.localize.lowVision,
SensorySensitivity() => context.localize.sensorySensitivity,
CognitiveImpairment() => context.localize.cognitiveImpairment,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import "dart:async";

import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";

import "../../../../../theme/app_theme.dart";
import "../business/accessibility_mode_service.dart";
import "../data/modes.dart";
import "labels.dart";

class ModeCheckbox extends ConsumerWidget {
const ModeCheckbox(this.mode, {super.key});
final AccessibilityMode mode;
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(accessibilityModeServiceProvider(mode));

// ignore: avoid_positional_boolean_parameters
void onChanged(bool? value) {
if (value != null) {
unawaited(
ref
.read(accessibilityModeServiceProvider(mode).notifier)
.setMode(newValue: value),
);
}
}

return CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
title: Text(
mode.localizedLabel(context),
style: context.aboutUsTheme.body,
),
value: value.value ?? false,
onChanged: onChanged,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import "package:flutter/material.dart";

import "../../../../../config/ui_config.dart";
import "../../../../../theme/app_theme.dart";
import "../../../../../utils/context_extensions.dart";

// pure UI, no logic, just a nice dialog with a title, subtitle and a child
class RedDialog extends StatelessWidget {
final String title;
final String subtitle;
final Widget child;

const RedDialog({
super.key,
required this.title,
required this.child,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: DecoratedBox(
decoration: BoxDecoration(
color: context.colorTheme.whiteSoap,
borderRadius: BorderRadius.circular(8),
border: Border(
top: BorderSide(
color: context.colorTheme.orangePomegranade,
),
bottom: BorderSide(
color: context.colorTheme.orangePomegranade,
width: 5,
),
),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(64),
offset: const Offset(0, 4),
blurRadius: 4,
),
],
),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 332),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(20).copyWith(bottom: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: context.textTheme.headline.copyWith(height: 1.4),
),
IconButton(
icon: const Icon(Icons.close),
color: context.colorTheme.greyPigeon,
onPressed: () => Navigator.of(context).pop(),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
subtitle,
style: context.aboutUsTheme.body.copyWith(
height: 1.4,
color: context.colorTheme.greyPigeon,
),
),
),
const SizedBox(height: 6),
child,
Padding(
padding: const EdgeInsets.all(20).copyWith(top: 0),
child: ElevatedButton(
onPressed: () {
// we're saving the changes in real time anyway
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
backgroundColor:
context.colorTheme.orangePomegranadeLighter,
elevation: 2,
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 20,
),
shape: RoundedRectangleBorder(
borderRadius: FilterConfig.radius,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
context.localize.apply,
style: context.textTheme.titleWhite,
),
],
),
),
),
],
),
),
),
);
}
}
Loading

0 comments on commit f38e00d

Please sign in to comment.