Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added a service for displaying snacks #114

Merged
merged 15 commits into from
May 13, 2024
Merged
25 changes: 25 additions & 0 deletions lib/features/app/app.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_easy_dialogs/flutter_easy_dialogs.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_template/features/navigation/app_router.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_provider.dart';
import 'package:flutter_template/features/theme_mode/presentation/widgets/theme_mode_builder.dart';
import 'package:flutter_template/l10n/app_localizations.g.dart';
import 'package:flutter_template/uikit/themes/app_theme_data.dart';
import 'package:nested/nested.dart';
import 'package:provider/provider.dart';

/// {@template app.class}
Expand Down Expand Up @@ -45,6 +48,28 @@ class _AppState extends State<App> {
darkTheme: AppThemeData.darkTheme,
themeMode: themeMode,

/// For snack and dialogs.
builder: (builderContext, widget) {
final mediaQueryData = MediaQuery.of(builderContext);
final easyDialogsBuilder = FlutterEasyDialogs.builder();

return Nested(
children: const [
SnackQueueProvider(),
],
child: MediaQuery(
data: mediaQueryData.copyWith(textScaler: TextScaler.noScaling),
child: Overlay(
initialEntries: [
OverlayEntry(
builder: (overlayContext) => easyDialogsBuilder(overlayContext, widget),
),
],
),
),
);
},

/// Localization.
locale: _localizations.firstOrNull,
localizationsDelegates: _localizationsDelegates,
Expand Down
31 changes: 31 additions & 0 deletions lib/features/debug/presentation/ui_kit/ui_kit_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ class UiKitScreen extends ElementaryWidget<IUiKitWM> {
),
child: Text(wm.l10n.uiKitScreenTetradicButtonText),
),
const SizedBox(width: AppSizes.double8),
Text(wm.l10n.uiKitScreenSnackFromScaffoldMessengerText),
const SizedBox(width: AppSizes.double8),
Row(
children: [
Expanded(
Expand All @@ -121,6 +124,34 @@ class UiKitScreen extends ElementaryWidget<IUiKitWM> {
),
],
),
const SizedBox(width: AppSizes.double8),
Text(wm.l10n.uiKitScreenSnackFromSnackQueueText),
const SizedBox(width: AppSizes.double8),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: wm.onDangerSnackQueueButtonPressed,
style: ElevatedButton.styleFrom(
foregroundColor: wm.colorScheme.onDanger,
backgroundColor: wm.colorScheme.danger,
),
child: Text(wm.l10n.uiKitScreenDangerSnackButtonText),
),
),
const SizedBox(width: AppSizes.double8),
Expanded(
child: ElevatedButton(
onPressed: wm.onPositiveSnackQueueButtonPressed,
style: ElevatedButton.styleFrom(
foregroundColor: wm.colorScheme.onPositive,
backgroundColor: wm.colorScheme.positive,
),
child: Text(wm.l10n.uiKitScreenPositiveSnackButtonText),
),
),
],
),
const SizedBox(height: AppSizes.double16),
const _ColorGrid(),
]
Expand Down
37 changes: 35 additions & 2 deletions lib/features/debug/presentation/ui_kit/ui_kit_wm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@ import 'package:flutter_template/common/mixin/theme_wm_mixin.dart';
import 'package:flutter_template/features/app/di/app_scope.dart';
import 'package:flutter_template/features/debug/presentation/ui_kit/ui_kit_model.dart';
import 'package:flutter_template/features/debug/presentation/ui_kit/ui_kit_screen.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_message_type.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_controller.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_provider.dart';
import 'package:flutter_template/features/theme_mode/presentation/theme_mode_provider.dart';
import 'package:provider/provider.dart';

/// Factory for [UiKitWM].
UiKitWM uiKitScreenWMFactory(BuildContext context) {
final appScope = context.read<IAppScope>();
final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackQueueController = SnackQueueProvider.of(context);

final model = UiKitModel(logWriter: appScope.logger);

return UiKitWM(model, scaffoldMessenger: scaffoldMessenger);
return UiKitWM(
model,
scaffoldMessenger: scaffoldMessenger,
snackQueueController: snackQueueController,
);
}

/// Interface for [UiKitWM].
Expand All @@ -40,19 +48,28 @@ abstract interface class IUiKitWM with ILocalizationMixin, ThemeIModelMixin impl

/// Callback of pressing the positive snack button.
void onPositiveSnackButtonPressed();

/// Callback of pressing the danger snack button.
void onDangerSnackQueueButtonPressed();

/// Callback of pressing the positive snack button.
void onPositiveSnackQueueButtonPressed();
}

/// {@template ui_kit_widget_model.class}
/// [WidgetModel] for [UiKitScreen].
/// {@endtemplate}
class UiKitWM extends WidgetModel<UiKitScreen, UiKitModel> with LocalizationMixin, ThemeWMMixin implements IUiKitWM {
final ScaffoldMessengerState _scaffoldMessenger;
final SnackQueueController _snackQueueController;

/// {@macro ui_kit_widget_model.class}
UiKitWM(
super._model, {
required ScaffoldMessengerState scaffoldMessenger,
}) : _scaffoldMessenger = scaffoldMessenger;
required SnackQueueController snackQueueController,
}) : _scaffoldMessenger = scaffoldMessenger,
_snackQueueController = snackQueueController;

@override
Future<void> switchTheme() => ThemeModeProvider.of(context).switchThemeMode();
Expand Down Expand Up @@ -102,4 +119,20 @@ class UiKitWM extends WidgetModel<UiKitScreen, UiKitModel> with LocalizationMixi
),
);
}

@override
void onDangerSnackQueueButtonPressed() {
_snackQueueController.addSnack(
l10n.uiKitScreenDangerSnackText,
messageType: SnackMessageType.error,
);
}

@override
void onPositiveSnackQueueButtonPressed() {
_snackQueueController.addSnack(
l10n.uiKitScreenPositiveSnackText,
messageType: SnackMessageType.success,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// ignore_for_file: no-equal-arguments
import 'package:flutter/material.dart';
import 'package:flutter_easy_dialogs/flutter_easy_dialogs.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_message_type.dart';
import 'package:flutter_template/uikit/app_sizes.dart';
import 'package:flutter_template/uikit/colors/app_color_scheme.dart';
import 'package:flutter_template/uikit/text/app_text_scheme.dart';

/// Default controller for displaying messages.
class DefaultSnackController {
/// Create an instance [DefaultSnackController].
const DefaultSnackController();

/// Show the message.
Future<void> showSnack({
required SnackMessageType messageType,
required String message,
required BuildContext context,
required EasyDialogDecoration dialogDecoration,
required EasyDialogAnimationConfiguration animationConfiguration,
Duration? autoHideDuration,
}) {
final colorScheme = AppColorScheme.of(context);
final textScheme = AppTextScheme.of(context);
final topPadding = MediaQuery.of(context).viewPadding.top;
return FlutterEasyDialogs.show(
EasyDialog.positioned(
decoration: dialogDecoration,
content: SizedBox(
width: double.infinity,
child: DecoratedBox(
decoration: BoxDecoration(
color: switch (messageType) {
// TODO(anyone): Set up colors according to your theme.
SnackMessageType.error => colorScheme.danger,
SnackMessageType.success => colorScheme.positive,
SnackMessageType.warning => colorScheme.dangerSecondary,
},
),
child: Padding(
padding: EdgeInsets.fromLTRB(
AppSizes.double16,
topPadding + AppSizes.double8,
AppSizes.double16,
AppSizes.double16,
),
child: Text(
message,
style: textScheme.label.copyWith(
color: switch (messageType) {
// TODO(anyone): Set up colors according to your theme.
SnackMessageType.error => colorScheme.onDanger,
SnackMessageType.success => colorScheme.onPositive,
SnackMessageType.warning => colorScheme.onDanger,
},
),
textAlign: TextAlign.center,
),
),
),
),
autoHideDuration: autoHideDuration,
animationConfiguration: animationConfiguration,
).swipe(
direction: DismissDirection.up,
willDismiss: () => true,
),
);
}

/// Hide Snack.
Future<void> hideSnack() {
return FlutterEasyDialogs.hide(
PositionedDialog.identifier(
position: EasyDialogPosition.top,
),
instantly: true,
);
}
}
11 changes: 11 additions & 0 deletions lib/features/snack_queue/presentation/snack_message_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// The type of message on the snack bar.
enum SnackMessageType {
/// Successfully completed.
success,

/// Error.
error,

/// Warning.
warning,
}
17 changes: 17 additions & 0 deletions lib/features/snack_queue/presentation/snack_queue_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter_easy_dialogs/flutter_easy_dialogs.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_message_type.dart';

/// The controller for displaying snacks.
/// Will be available to descendants.
abstract interface class SnackQueueController {
/// Add Snack to Queue.
void addSnack(
String message, {
required SnackMessageType messageType,
EasyDialogDecoration? dialogDecoration,
EasyDialogAnimationConfiguration? animationConfiguration,
});

/// Clears the snack queue from potential displays that were queued before [closeTime].
void clearSnackQueue(DateTime closeTime);
}
13 changes: 13 additions & 0 deletions lib/features/snack_queue/presentation/snack_queue_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:elementary/elementary.dart';
import 'package:flutter_template/core/architecture/presentation/base_model.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_widget.dart';

/// {@template snack_queue_model.class}
/// [ElementaryModel] for [SnackQueueWidget].
/// {@endtemplate}
final class SnackQueueModel extends BaseModel {
/// {@macro snack_queue_model.class}
SnackQueueModel({
required super.logWriter,
});
}
22 changes: 22 additions & 0 deletions lib/features/snack_queue/presentation/snack_queue_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_controller.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_widget.dart';
import 'package:nested/nested.dart';
import 'package:provider/provider.dart';

/// {@template snack_queue_provider.class}
/// Provides [SnackQueueController] to its descendants.
/// {@endtemplate}
class SnackQueueProvider extends SingleChildStatelessWidget {
/// {@macro snack_queue_provider.class}
const SnackQueueProvider({super.key});

/// Get the [SnackQueueController] from the [BuildContext].
// ignore: prefer-widget-private-members
static SnackQueueController of(BuildContext context) => Provider.of<SnackQueueController>(context, listen: false);

@override
Widget buildWithChild(BuildContext context, Widget? child) {
return SnackQueueWidget(child: child ?? const SizedBox.shrink());
}
}
28 changes: 28 additions & 0 deletions lib/features/snack_queue/presentation/snack_queue_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:elementary/elementary.dart';
import 'package:flutter/material.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_controller.dart';
import 'package:flutter_template/features/snack_queue/presentation/snack_queue_wm.dart';
import 'package:provider/provider.dart';

/// {@template snack_queue_widget.class}
/// SnackQueueWidget.
/// {@endtemplate}
class SnackQueueWidget extends ElementaryWidget<ISnackQueueWM> {
/// {@macro snack_queue_widget.class}
const SnackQueueWidget({
required this.child,
super.key,
WidgetModelFactory wmFactory = defaultSnackQueueWMFactory,
}) : super(wmFactory);

/// Child.
final Widget child;

@override
Widget build(ISnackQueueWM wm) {
return Provider<SnackQueueController>.value(
value: wm,
child: child,
);
}
}
Loading
Loading