import 'dart:io'; import 'dart:async'; import 'package:fimber/fimber.dart'; import 'package:fluro/fluro.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:git_touch/utils/utils.dart'; import 'package:git_touch/widgets/action_button.dart'; import 'package:primer/primer.dart'; import 'package:shared_preferences/shared_preferences.dart'; class DialogOption { final T value; final Widget widget; DialogOption({this.value, this.widget}); } class AppThemeType { static const material = 0; static const cupertino = 1; static const values = [AppThemeType.material, AppThemeType.cupertino]; } class AppBrightnessType { static const followSystem = 0; static const light = 1; static const dark = 2; static const values = [ AppBrightnessType.followSystem, AppBrightnessType.light, AppBrightnessType.dark ]; } class PickerItem { final T value; final String text; PickerItem(this.value, {@required this.text}); } class PickerGroupItem { final T value; final List> items; final Function(T value) onChange; final Function(T value) onClose; PickerGroupItem({ @required this.value, @required this.items, this.onChange, this.onClose, }); } class SelectorItem { T value; String text; SelectorItem({@required this.value, @required this.text}); } // No animation. For replacing route // TODO: Go back class StaticRoute extends PageRouteBuilder { final WidgetBuilder builder; StaticRoute({this.builder}) : super( pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { return builder(context); }, transitionsBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { return child; }, ); } class Palette { final Color primary; final Color text; final Color secondaryText; final Color tertiaryText; final Color background; final Color grayBackground; final Color border; const Palette({ this.primary, this.text, this.secondaryText, this.tertiaryText, this.background, this.grayBackground, this.border, }); } class ThemeModel with ChangeNotifier { int _theme; int get theme => _theme; bool get ready => _theme != null; Brightness systemBrightness = Brightness.light; void setSystemBrightness(Brightness v) { // print('systemBrightness: $v'); if (v != systemBrightness) { Future.microtask(() { systemBrightness = v; notifyListeners(); }); } } int _brightnessValue = AppBrightnessType.followSystem; int get brighnessValue => _brightnessValue; // could be null Brightness get brightness { switch (_brightnessValue) { case AppBrightnessType.light: return Brightness.light; case AppBrightnessType.dark: return Brightness.dark; default: return systemBrightness; } } Future setBrightness(int v) async { _brightnessValue = v; final prefs = await SharedPreferences.getInstance(); await prefs.setInt(StorageKeys.iBrightness, v); Fimber.d('write brightness: $v'); notifyListeners(); } final router = Router(); final paletteLight = Palette( primary: PrimerColors.blue500, text: Colors.black, secondaryText: Colors.grey.shade800, tertiaryText: Colors.grey.shade600, background: Colors.white, grayBackground: Colors.grey.shade100, border: Colors.grey.shade300, ); final paletteDark = Palette( primary: PrimerColors.blue500, text: Colors.grey.shade300, secondaryText: Colors.grey.shade400, tertiaryText: Colors.grey.shade500, background: Colors.black, grayBackground: Colors.grey.shade900, border: Colors.grey.shade700, ); Palette paletteOf(BuildContext context) { switch (brightness) { case Brightness.light: return paletteLight; case Brightness.dark: return paletteDark; default: throw 'brightnessOf should not return null'; } } Future init() async { final prefs = await SharedPreferences.getInstance(); final v = prefs.getInt(StorageKeys.iTheme); Fimber.d('read theme: $v'); if (AppThemeType.values.contains(v)) { _theme = v; } else if (Platform.isIOS) { _theme = AppThemeType.cupertino; } else { _theme = AppThemeType.material; } final b = prefs.getInt(StorageKeys.iBrightness); Fimber.d('read brightness: $b'); if (AppBrightnessType.values.contains(b)) { _brightnessValue = b; } notifyListeners(); } Future setTheme(int v) async { _theme = v; final prefs = await SharedPreferences.getInstance(); await prefs.setInt(StorageKeys.iTheme, v); Fimber.d('write theme: $v'); notifyListeners(); } push(BuildContext context, String url, {bool replace = false}) { if (url.startsWith('/')) { return router.navigateTo( context, url, transition: replace ? TransitionType.fadeIn : theme == AppThemeType.cupertino ? TransitionType.cupertino : TransitionType.material, replace: replace, ); } else { launchUrl(url); } } pushGitlab(BuildContext context, String url, {bool replace = false}) { if (url.startsWith('/')) { push(context, '/gitlab/$url', replace: replace); } else { launchUrl(url); } } Future showConfirm(BuildContext context, Widget content) { switch (theme) { case AppThemeType.cupertino: return showCupertinoDialog( context: context, builder: (context) { return CupertinoAlertDialog( title: content, actions: [ CupertinoDialogAction( child: const Text('cancel'), isDefaultAction: true, onPressed: () { Navigator.pop(context, false); }, ), CupertinoDialogAction( child: const Text('OK'), onPressed: () { Navigator.pop(context, true); }, ), ], ); }, ); default: return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( content: content, actions: [ FlatButton( child: const Text('CANCEL'), onPressed: () { Navigator.pop(context, false); }, ), FlatButton( child: const Text('OK'), onPressed: () { Navigator.pop(context, true); }, ) ], ); }, ); } } Future showDialogOptions( BuildContext context, List> options) { var title = Text('Pick your reaction'); var cancelWidget = Text('Cancel'); switch (theme) { case AppThemeType.cupertino: return showCupertinoDialog( context: context, builder: (BuildContext context) { return CupertinoAlertDialog( title: title, actions: options.map((option) { return CupertinoDialogAction( child: option.widget, onPressed: () { Navigator.pop(context, option.value); }, ); }).toList() ..add( CupertinoDialogAction( child: cancelWidget, isDestructiveAction: true, onPressed: () { Navigator.pop(context, null); }, ), ), ); }, ); default: return showDialog( context: context, builder: (BuildContext context) { return SimpleDialog( title: title, children: options.map((option) { return SimpleDialogOption( child: option.widget, onPressed: () { Navigator.pop(context, option.value); }, ); }).toList() ..add(SimpleDialogOption( child: cancelWidget, onPressed: () { Navigator.pop(context, null); }, )), ); }, ); } } showSelector({ @required BuildContext context, @required Iterable> items, @required T selected, }) async { switch (theme) { case AppThemeType.cupertino: default: return showMenu( context: context, initialValue: selected, items: items .map((item) => PopupMenuItem(value: item.value, child: Text(item.text))) .toList(), position: RelativeRect.fromLTRB(1, 10, 0, 0), ); } } static Timer _debounce; String _selectedItem; showPicker(BuildContext context, PickerGroupItem groupItem) async { switch (theme) { case AppThemeType.cupertino: default: await showCupertinoModalPopup( context: context, builder: (context) { return Container( height: 216, child: CupertinoPicker( backgroundColor: paletteOf(context).background, children: groupItem.items.map((v) => Text(v.text)).toList(), itemExtent: 40, scrollController: FixedExtentScrollController( initialItem: groupItem.items .toList() .indexWhere((v) => v.value == groupItem.value)), onSelectedItemChanged: (index) { _selectedItem = groupItem.items[index].value; if (groupItem.onChange != null) { if (_debounce?.isActive ?? false) { _debounce.cancel(); } _debounce = Timer(const Duration(milliseconds: 500), () { groupItem.onChange(_selectedItem); }); } }, ), ); }, ); if (groupItem.onClose != null) { groupItem.onClose(_selectedItem); } } } showActions(BuildContext context, List actionItems) async { if (actionItems == null) return; final value = await showCupertinoModalPopup( context: context, builder: (BuildContext context) { return CupertinoActionSheet( title: Text('Actions'), actions: actionItems.asMap().entries.map((entry) { return CupertinoActionSheetAction( child: Text(entry.value.text), onPressed: () { Navigator.pop(context, entry.key); }, ); }).toList(), cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), isDefaultAction: true, onPressed: () { Navigator.pop(context); }, ), ); }, ); if (value != null) { actionItems[value].onTap(context); } } }