diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 393abf51..de99da9b 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -12,7 +12,7 @@ abstract class AppConfig { static double bubbleSizeFactor = 1; static double fontSizeFactor = 1; static const Color chatColor = primaryColor; - static Color? colorSchemeSeed; + static Color? colorSchemeSeed = primaryColor; static const double messageFontSize = 15.75; static const bool allowOtherHomeservers = true; static const bool enableRegistration = true; @@ -64,7 +64,7 @@ abstract class AppConfig { static void loadFromJson(Map json) { if (json['chat_color'] != null) { try { - colorSchemeSeed = Color(json['application_name']); + colorSchemeSeed = Color(json['chat_color']); } catch (e) { Logs().w( 'Invalid color in config.json! Please make sure to define the color in this format: "0xffdd0000"', diff --git a/lib/config/themes.dart b/lib/config/themes.dart index 989b2634..d462b821 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -74,11 +74,13 @@ abstract class FluffyThemes { subtitle2: fallbackTextStyle, ); - static ThemeData get light => ThemeData( + static ThemeData light([ColorScheme? colorScheme]) => ThemeData( visualDensity: VisualDensity.standard, useMaterial3: true, brightness: Brightness.light, - colorSchemeSeed: AppConfig.colorSchemeSeed, + colorSchemeSeed: AppConfig.colorSchemeSeed ?? + colorScheme?.primary ?? + AppConfig.chatColor, scaffoldBackgroundColor: Colors.white, textTheme: PlatformInfos.isDesktop ? Typography.material2018().black.merge(fallbackTextTheme) @@ -124,11 +126,13 @@ abstract class FluffyThemes { ), ); - static ThemeData get dark => ThemeData( + static ThemeData dark([ColorScheme? colorScheme]) => ThemeData( visualDensity: VisualDensity.standard, useMaterial3: true, brightness: Brightness.dark, - colorSchemeSeed: AppConfig.colorSchemeSeed, + colorSchemeSeed: AppConfig.colorSchemeSeed ?? + colorScheme?.primary ?? + AppConfig.chatColor, scaffoldBackgroundColor: Colors.black, textTheme: PlatformInfos.isDesktop ? Typography.material2018().white.merge(fallbackTextTheme) diff --git a/lib/main.dart b/lib/main.dart index c324f03b..7a0d60f9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter_app_lock/flutter_app_lock.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; @@ -99,69 +100,71 @@ class _FluffyChatAppState extends State { @override Widget build(BuildContext context) { - return AdaptiveTheme( - light: FluffyThemes.light, - dark: FluffyThemes.dark, - initial: AdaptiveThemeMode.system, - builder: (theme, darkTheme) => LayoutBuilder( - builder: (context, constraints) { - const maxColumns = 3; - var newColumns = - (constraints.maxWidth / FluffyThemes.columnWidth).floor(); - if (newColumns > maxColumns) newColumns = maxColumns; - columnMode ??= newColumns > 1; - _router ??= GlobalKey(); - if (columnMode != newColumns > 1) { - Logs().v('Set Column Mode = $columnMode'); - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _initialUrl = _router?.currentState?.url; - columnMode = newColumns > 1; - _router = GlobalKey(); - }); - }); - } - return VRouter( - key: _router, - title: AppConfig.applicationName, - theme: theme, - scrollBehavior: CustomScrollBehavior(), - logs: kReleaseMode ? VLogs.none : VLogs.info, - darkTheme: darkTheme, - localizationsDelegates: const [ - ...L10n.localizationsDelegates, - ], - supportedLocales: L10n.supportedLocales, - initialUrl: _initialUrl ?? '/', - routes: AppRoutes(columnMode ?? false).routes, - builder: (context, child) { - LoadingDialog.defaultTitle = L10n.of(context)!.loadingPleaseWait; - LoadingDialog.defaultBackLabel = L10n.of(context)!.close; - LoadingDialog.defaultOnError = - (e) => (e as Object?)!.toLocalizedString(context); - WidgetsBinding.instance.addPostFrameCallback((_) { - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - systemNavigationBarColor: - Theme.of(context).appBarTheme.backgroundColor, - systemNavigationBarIconBrightness: - Theme.of(context).brightness == Brightness.light - ? Brightness.dark - : Brightness.light, - ), - ); - }); - return Matrix( - context: context, - router: _router, - clients: widget.clients, - child: child, - ); - }, - ); - }, - ), - ); + return DynamicColorBuilder( + builder: (lightColorScheme, darkColorScheme) => AdaptiveTheme( + light: FluffyThemes.light(lightColorScheme), + dark: FluffyThemes.dark(darkColorScheme), + initial: AdaptiveThemeMode.system, + builder: (theme, darkTheme) => LayoutBuilder( + builder: (context, constraints) { + const maxColumns = 3; + var newColumns = + (constraints.maxWidth / FluffyThemes.columnWidth).floor(); + if (newColumns > maxColumns) newColumns = maxColumns; + columnMode ??= newColumns > 1; + _router ??= GlobalKey(); + if (columnMode != newColumns > 1) { + Logs().v('Set Column Mode = $columnMode'); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _initialUrl = _router?.currentState?.url; + columnMode = newColumns > 1; + _router = GlobalKey(); + }); + }); + } + return VRouter( + key: _router, + title: AppConfig.applicationName, + theme: theme, + scrollBehavior: CustomScrollBehavior(), + logs: kReleaseMode ? VLogs.none : VLogs.info, + darkTheme: darkTheme, + localizationsDelegates: const [ + ...L10n.localizationsDelegates, + ], + supportedLocales: L10n.supportedLocales, + initialUrl: _initialUrl ?? '/', + routes: AppRoutes(columnMode ?? false).routes, + builder: (context, child) { + LoadingDialog.defaultTitle = + L10n.of(context)!.loadingPleaseWait; + LoadingDialog.defaultBackLabel = L10n.of(context)!.close; + LoadingDialog.defaultOnError = + (e) => (e as Object?)!.toLocalizedString(context); + WidgetsBinding.instance.addPostFrameCallback((_) { + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarColor: + Theme.of(context).appBarTheme.backgroundColor, + systemNavigationBarIconBrightness: + Theme.of(context).brightness == Brightness.light + ? Brightness.dark + : Brightness.light, + ), + ); + }); + return Matrix( + context: context, + router: _router, + clients: widget.clients, + child: child, + ); + }, + ); + }, + ), + )); } } diff --git a/lib/pages/chat/sticker_picker_dialog.dart b/lib/pages/chat/sticker_picker_dialog.dart index 6a8a6192..d76f772c 100644 --- a/lib/pages/chat/sticker_picker_dialog.dart +++ b/lib/pages/chat/sticker_picker_dialog.dart @@ -4,7 +4,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import '../../widgets/avatar.dart'; -import '../../widgets/default_app_bar_search_field.dart'; import 'events/image_bubble.dart'; class StickerPickerDialog extends StatefulWidget { @@ -117,10 +116,13 @@ class StickerPickerDialogState extends State { icon: const Icon(Icons.close), onPressed: Navigator.of(context, rootNavigator: false).pop, ), - title: DefaultAppBarSearchField( + title: TextField( autofocus: false, - hintText: L10n.of(context)!.search, - suffix: const Icon(Icons.search_outlined), + decoration: InputDecoration( + hintText: L10n.of(context)!.search, + suffix: const Icon(Icons.search_outlined), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + ), onChanged: (s) => setState(() => searchFilter = s), ), ), diff --git a/lib/pages/invitation_selection/invitation_selection_view.dart b/lib/pages/invitation_selection/invitation_selection_view.dart index 821f3136..7290f0db 100644 --- a/lib/pages/invitation_selection/invitation_selection_view.dart +++ b/lib/pages/invitation_selection/invitation_selection_view.dart @@ -6,7 +6,6 @@ import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/pages/invitation_selection/invitation_selection.dart'; import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/default_app_bar_search_field.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -29,9 +28,13 @@ class InvitationSelectionView extends StatelessWidget { .toSegments(['rooms', controller.roomId!]), ), titleSpacing: 0, - title: DefaultAppBarSearchField( + title: TextField( autofocus: true, - hintText: L10n.of(context)!.inviteContactToGroup(groupName), + decoration: InputDecoration( + hintText: L10n.of(context)!.inviteContactToGroup(groupName), + suffix: const Icon(Icons.search_outlined), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + ), onChanged: controller.searchUserWithCoolDown, ), ), diff --git a/lib/pages/search/search_view.dart b/lib/pages/search/search_view.dart index bef6476a..c75c8fff 100644 --- a/lib/pages/search/search_view.dart +++ b/lib/pages/search/search_view.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/utils/string_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/contacts_list.dart'; -import 'package:fluffychat/widgets/default_app_bar_search_field.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../utils/localized_exception_extension.dart'; import '../../utils/platform_infos.dart'; @@ -80,11 +79,14 @@ class SearchView extends StatelessWidget { appBar: AppBar( leading: const BackButton(), titleSpacing: 0, - title: DefaultAppBarSearchField( + title: TextField( autofocus: true, - hintText: L10n.of(context)!.search, - searchController: controller.controller, - suffix: const Icon(Icons.search_outlined), + controller: controller.controller, + decoration: InputDecoration( + suffix: const Icon(Icons.search_outlined), + hintText: L10n.of(context)!.search, + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + ), onChanged: controller.search, ), bottom: TabBar( diff --git a/lib/pages/settings_style/settings_style.dart b/lib/pages/settings_style/settings_style.dart index 012039c9..63cabb67 100644 --- a/lib/pages/settings_style/settings_style.dart +++ b/lib/pages/settings_style/settings_style.dart @@ -44,8 +44,8 @@ class SettingsStyleController extends State { ); AppConfig.colorSchemeSeed = color; AdaptiveTheme.of(context).setTheme( - light: FluffyThemes.light, - dark: FluffyThemes.dark, + light: FluffyThemes.light(), + dark: FluffyThemes.dark(), ); } diff --git a/lib/widgets/default_app_bar_search_field.dart b/lib/widgets/default_app_bar_search_field.dart deleted file mode 100644 index 0be43755..00000000 --- a/lib/widgets/default_app_bar_search_field.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import '../config/app_config.dart'; - -class DefaultAppBarSearchField extends StatefulWidget { - final TextEditingController? searchController; - final void Function(String)? onChanged; - final void Function(String)? onSubmit; - final Widget? suffix; - final bool autofocus; - final String? prefixText; - final String? hintText; - final String? labelText; - final EdgeInsets? padding; - final bool readOnly; - final Widget? prefixIcon; - final bool unfocusOnClear; - final bool autocorrect; - - const DefaultAppBarSearchField({ - Key? key, - this.searchController, - this.onChanged, - this.onSubmit, - this.suffix, - this.autofocus = false, - this.prefixText, - this.hintText, - this.padding, - this.labelText, - this.readOnly = false, - this.prefixIcon, - this.unfocusOnClear = true, - this.autocorrect = true, - }) : super(key: key); - - @override - DefaultAppBarSearchFieldState createState() => - DefaultAppBarSearchFieldState(); -} - -class DefaultAppBarSearchFieldState extends State { - late final TextEditingController _searchController; - bool _lastTextWasEmpty = false; - final FocusNode _focusNode = FocusNode(); - - void requestFocus() => _focusNode.requestFocus(); - - void _updateSearchController() { - final thisTextIsEmpty = _searchController.text.isEmpty; - if (_lastTextWasEmpty != thisTextIsEmpty) { - setState(() => _lastTextWasEmpty = thisTextIsEmpty); - } - } - - @override - void initState() { - super.initState(); - _searchController = widget.searchController ?? TextEditingController(); - // we need to remove the listener in the dispose method, so we need a reference to the callback - _searchController.addListener(_updateSearchController); - _focusNode.addListener(() => setState(() {})); - } - - @override - void dispose() { - _focusNode.dispose(); - _searchController.removeListener(_updateSearchController); - if (widget.searchController == null) { - // we need to dispose our own created searchController - _searchController.dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - height: 40, - padding: widget.padding ?? const EdgeInsets.only(right: 12), - child: TextField( - autofocus: widget.autofocus, - autocorrect: widget.autocorrect, - enableSuggestions: widget.autocorrect, - keyboardType: widget.autocorrect ? null : TextInputType.visiblePassword, - controller: _searchController, - onChanged: widget.onChanged, - focusNode: _focusNode, - readOnly: widget.readOnly, - onSubmitted: widget.onSubmit, - decoration: InputDecoration( - prefixText: widget.prefixText, - labelText: widget.labelText, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - borderSide: - BorderSide(color: Theme.of(context).secondaryHeaderColor), - ), - contentPadding: const EdgeInsets.only( - top: 8, - bottom: 8, - left: 16, - ), - hintText: widget.hintText, - prefixIcon: widget.prefixIcon, - suffixIcon: !widget.readOnly && - (_focusNode.hasFocus || - (widget.suffix == null && - (_searchController.text.isNotEmpty))) - ? IconButton( - tooltip: L10n.of(context)!.clearText, - icon: const Icon(Icons.backspace_outlined), - onPressed: () { - _searchController.clear(); - widget.onChanged?.call(''); - if (widget.unfocusOnClear) _focusNode.unfocus(); - }, - ) - : widget.suffix, - ), - ), - ); - } -} diff --git a/lib/widgets/lock_screen.dart b/lib/widgets/lock_screen.dart index c4633b3c..1a5a4575 100644 --- a/lib/widgets/lock_screen.dart +++ b/lib/widgets/lock_screen.dart @@ -25,8 +25,8 @@ class _LockScreenState extends State { @override Widget build(BuildContext context) { return MaterialApp( - theme: FluffyThemes.light, - darkTheme: FluffyThemes.light, + theme: FluffyThemes.light(), + darkTheme: FluffyThemes.light(), localizationsDelegates: L10n.localizationsDelegates, supportedLocales: L10n.supportedLocales, home: Builder( diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index a09de790..8be503d0 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -503,8 +503,8 @@ class MatrixState extends State with WidgetsBindingObserver { if (value != null && int.tryParse(value) != null) { AppConfig.colorSchemeSeed = Color(int.parse(value)); AdaptiveTheme.of(context).setTheme( - light: FluffyThemes.light, - dark: FluffyThemes.dark, + light: FluffyThemes.light(), + dark: FluffyThemes.dark(), ); } }); diff --git a/pubspec.lock b/pubspec.lock index 587c421d..18debc54 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -367,6 +367,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.1" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.2" email_validator: dependency: "direct main" description: @@ -2055,4 +2062,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.17.0-0 <3.0.0" - flutter: ">=2.8.0" + flutter: ">=2.13.0-0.2.pre" diff --git a/pubspec.yaml b/pubspec.yaml index a2b186b8..6999d0b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: desktop_lifecycle: ^0.1.0 desktop_notifications: ^0.6.3 device_info_plus: ^3.2.1 + dynamic_color: ^1.2.2 email_validator: ^2.0.1 emoji_picker_flutter: ^1.1.2 encrypt: ^5.0.1