diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fff412..7e46129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,18 @@ ## Unreleased ### Added - Full Material 3 Support +- add pure dark background option +- add use gridview option ### Changed -- \[BREAK\] +- \[BREAK CHANGE\] ⚠️ App signature changed. Uninstall old app and install new one. (REALLY SORRY FOR THIS CHANGE) - app tile long press to open context menu +- all tools use monochrome icons ### Fixed - fix search view not hide when scroll - fix searchView title +- use wrong mono font ## 2.0.1-dev+37 - 2023-11-21 ### Added diff --git a/assets/fonts/NotoSansMono-Regular.ttf b/assets/fonts/NotoSansMono-Regular.ttf deleted file mode 100644 index ff9389a..0000000 Binary files a/assets/fonts/NotoSansMono-Regular.ttf and /dev/null differ diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt deleted file mode 100644 index 0e6dea2..0000000 --- a/assets/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/icons/dart.svg b/assets/icons/dart.svg index b9ddcdf..c49b354 100644 --- a/assets/icons/dart.svg +++ b/assets/icons/dart.svg @@ -1,23 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/icons/js.svg b/assets/icons/js.svg index 64b2440..8db86fd 100644 --- a/assets/icons/js.svg +++ b/assets/icons/js.svg @@ -1,31 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/icons/json.svg b/assets/icons/json.svg index fca9b87..12682fe 100644 --- a/assets/icons/json.svg +++ b/assets/icons/json.svg @@ -1,104 +1 @@ - - - - - JSON logo - - - - - - - - - - - - + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index b398d1b..3dd2ce8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -26,6 +26,10 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSPhotoLibraryUsageDescription + Request Photo Library + NSCameraUsageDescription + Request Camera UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7cf4610..c64d2f9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -20,6 +20,7 @@ "colorPrimary": "Primary", "colorString": "Color String", "colorWheel": "Wheel", + "commonConverter": "Common Converter", "commonInfo": "Common Info", "compress": "Compress", "computerName": "Computer Name", @@ -75,6 +76,7 @@ "generatorUUID": "UUID Generator", "generators": "Generators", "github": "github", + "gridLayout": "Use Grid Layout", "hashAlgorithm": "Hash Algorithm", "hashHMAC": "HMAC", "hashHMACDes": "Keyed-hash message authentication code", @@ -159,6 +161,7 @@ "previousMonth": "Previous Month", "previousSecond": "Previous Second", "previousYear": "Previous Year", + "pureBlack": "Use Pure Black Background", "pythonDictFormatter": "Python Dict formatter", "qrAutoVersion": "Auto", "qrCodeTool": "QR Code Tool", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 7ca677b..e5c79c9 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2,6 +2,7 @@ "about": "についてです", "absoluteLengthConverter": "絶対長変換ツールです", "activeCPUs": "アクティブなCPU", + "addFavorite": "お気に入りに追加", "allApps": "あらゆる応用", "allTools": "あらゆるツール", "appName": "Alga", @@ -11,8 +12,15 @@ "clear": "クリアです", "cleared": "クリアしました", "clickToCopy": "クリックしてコピーする", + "colorAccent": "二次色", + "colorBoth": "Both", + "colorBw": "黒 & 白", "colorConverter": "色変換ツールです", + "colorCustom": "カスタム", + "colorPrimary": "メインカラー", "colorString": "色文字列", + "colorWheel": "Wheel", + "commonConverter": "Common Converter", "commonInfo": "共通情報", "compress": "圧縮です", "computerName": "デバイス名", @@ -39,7 +47,9 @@ "deviceArch": "CPUアーキテクチャ", "deviceInfo": "デバイス情報", "deviceModel": "装置の型番です", + "emptyValue": "Empty content", "encode": "エンコードです", + "encoded": "Encoded", "encoderDecoderApp": "コード化と復号化", "encoderDecoderBase64": "Base64符号化/復号化ツール", "encoderDecoderGzip": "GZipコード/デコードツール", @@ -50,6 +60,8 @@ "favorite": "コレクション", "followSystem": "追従システム", "format": "フォーマット", + "formatException": "Format Error", + "formatTable": "Foramt Table", "formattedDateString": "フォーマットされた日付", "formatterApp": "フォーマット", "formatterDart": "Dartフォーマット", @@ -69,6 +81,7 @@ "hashHMACDes": "鍵に基づくメッセージ識別認証ハッシュ", "hashSM3": "国密SM3ハッシュ", "hashSecretkey": "鍵", + "help": "Help", "hexdecimal": "16進法", "hostName": "ホストネーム", "hypens": "ハイフン", @@ -124,6 +137,7 @@ "octal": "8進法", "operatingSystem": "オペレーティングシステム", "operatingSystemVersion": "osバージョン", + "original": "Original", "osRelease": "os版", "output": "アウトプットです", "parsedDate": "解析の日付", @@ -160,6 +174,7 @@ "regexText": "テキスト", "regexUnicode": "Unicode", "regularExpression": "正規表現", + "removeFavorite": "Remove from Favorites", "sassCssGenerator": "sass/cssジェネレータ", "sassResult": "出力", "sassSource": "ソースコード", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f0fd2f1..123c19a 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -12,8 +12,15 @@ "clear": "清除", "cleared": "已清除", "clickToCopy": "点击复制", + "colorAccent": "次色调", + "colorBoth": "Both", + "colorBw": "黑 & 白", "colorConverter": "颜色转换工具", + "colorCustom": "自定义", + "colorPrimary": "主色调", "colorString": "颜色字符串", + "colorWheel": "滑轮", + "commonConverter": "通用转换工具", "commonInfo": "通用信息", "compress": "压缩", "computerName": "设备名", @@ -54,6 +61,7 @@ "followSystem": "跟随系统", "format": "格式化", "formatException": "格式错误", + "formatTable": "支持的格式化", "formattedDateString": "格式化的日期", "formatterApp": "格式化", "formatterDart": "Dart 格式化", diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 97ca556..3cff36b 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -35,3 +35,18 @@ extension L10nX on BuildContext { MaterialLocalizations get mtr => MaterialLocalizations.of(this); } + +extension LocaleExt on Locale? { + String getName(BuildContext context) { + switch (this) { + case const Locale('zh', null): + return '简体中文'; + case const Locale('en', null): + return 'English'; + case const Locale('ja', null): + return '日本語'; + default: + return context.tr.followSystem; + } + } +} diff --git a/lib/l10n/untranslated/desired.json b/lib/l10n/untranslated/desired.json index 000416f..9b1ee00 100644 --- a/lib/l10n/untranslated/desired.json +++ b/lib/l10n/untranslated/desired.json @@ -1,28 +1,11 @@ { "ja": [ - "addFavorite", - "colorAccent", - "colorBoth", - "colorBw", - "colorCustom", - "colorPrimary", - "colorWheel", - "emptyValue", - "encoded", - "formatException", - "formatTable", - "help", - "original", - "removeFavorite" + "gridLayout", + "pureBlack" ], "zh": [ - "colorAccent", - "colorBoth", - "colorBw", - "colorCustom", - "colorPrimary", - "colorWheel", - "formatTable" + "gridLayout", + "pureBlack" ] } diff --git a/lib/main.dart b/lib/main.dart index 79f8314..92be6ac 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,20 +29,14 @@ class MyApp extends ConsumerWidget { valueListenable: AppConfigBox.keys(), builder: (context, _, __) { return DynamicColorBuilder(builder: (light, dark) { - ColorScheme? lightScheme; - ColorScheme? darkScheme; - if (light != null && dark != null) { - lightScheme = light.harmonized(); - darkScheme = dark.harmonized(); - } - lightScheme ??= kDefaultLightColorScheme; - darkScheme ??= kDefaultDarkColorScheme; + light = light?.harmonized() ?? kDefaultLightColorScheme; + dark = dark?.harmonized() ?? kDefaultDarkColorScheme; return MaterialApp.router( routerConfig: ref.watch(appRouterProvider), onGenerateTitle: (context) => S.of(context).appName, - theme: ThemeUtil(lightScheme).getTheme(Brightness.light), - darkTheme: ThemeUtil(darkScheme).getTheme(Brightness.dark), - themeMode: AppConfigBox.themeMode, + theme: ref.watch(appThemeDataProvider(light, Brightness.light)), + darkTheme: ref.watch(appThemeDataProvider(dark, Brightness.dark)), + themeMode: ref.watch(appThemeModeProvider), localizationsDelegates: S.localizationsDelegates, supportedLocales: S.supportedLocales, locale: AppConfigBox.locale, diff --git a/lib/models/app_atom.dart b/lib/models/app_atom.dart index e206539..620d391 100644 --- a/lib/models/app_atom.dart +++ b/lib/models/app_atom.dart @@ -135,7 +135,7 @@ class AppAtom { ); static final sass2CssGenerator = AppAtom( - icon: const SvgAssetIcon('assets/icons/sass.svg', colorIcon: true), + icon: const SvgAssetIcon('assets/icons/sass.svg'), title: (context) => context.tr.sassCssGenerator, path: Sass2cssRoute().location, categories: [ @@ -224,7 +224,7 @@ class AppAtom { ); static final jwtDecoder = AppAtom( - icon: const SvgAssetIcon('assets/icons/JWT.svg', colorIcon: true), + icon: const SvgAssetIcon('assets/icons/JWT.svg'), title: (context) => context.tr.decoderJWT, path: JwtDecoderRoute().location, categories: [AppCategory.encodersDecoders], @@ -238,13 +238,13 @@ class AppAtom { ); static final jsonFormatter = AppAtom( - icon: const SvgAssetIcon('assets/icons/json.svg', colorIcon: true), + icon: const SvgAssetIcon('assets/icons/json.svg'), title: (context) => context.tr.formatterJson, path: JsonFormatterRoute().location, categories: [AppCategory.formatter], ); static final dartFormatter = AppAtom( - icon: const SvgAssetIcon('assets/icons/dart.svg', colorIcon: true), + icon: const SvgAssetIcon('assets/icons/dart.svg'), title: (context) => context.tr.formatterDart, path: DartFormatterRoute().location, categories: [AppCategory.formatter], @@ -270,7 +270,7 @@ class AppAtom { categories: [AppCategory.infomation], ); static final quickJs = AppAtom( - icon: const SvgAssetIcon('assets/icons/js.svg', colorIcon: true), + icon: const SvgAssetIcon('assets/icons/js.svg'), title: (context) => 'Quick JS Tool', path: QuickJsRoute().location, categories: [AppCategory.frontEnd], diff --git a/lib/routers/app_router.dart b/lib/routers/app_router.dart index 61939a9..5a705f0 100644 --- a/lib/routers/app_router.dart +++ b/lib/routers/app_router.dart @@ -55,9 +55,7 @@ GoRouter appRouter(AppRouterRef ref) { ]), TypedGoRoute(path: '/favorite'), TypedGoRoute(path: '/search'), - TypedGoRoute(path: '/settings', routes: [ - TypedGoRoute(path: 'licenses'), - ]), + TypedGoRoute(path: '/settings'), ], ) class RootRoute extends ShellRouteData { diff --git a/lib/tools/converters/color_converter/color_converter.dart b/lib/tools/converters/color_converter/color_converter.dart index 3a384fb..4d8b3ed 100644 --- a/lib/tools/converters/color_converter/color_converter.dart +++ b/lib/tools/converters/color_converter/color_converter.dart @@ -2,6 +2,7 @@ import 'package:alga/l10n/l10n.dart'; import 'package:alga/tools/converters/color_converter/color_picker.dart'; import 'package:alga/tools/converters/color_converter/color_view_background_patiner.dart'; import 'package:alga/tools/tools.provider.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/configurations/configurations.dart'; import 'package:alga/ui/widgets/custom_icon_button.dart'; import 'package:alga/ui/widgets/scaffold/scrollable_scaffold.dart'; @@ -71,7 +72,7 @@ class _ColorConverterPageState extends ConsumerState { ), ], ), - TextField( + AppInput( controller: _controller, decoration: InputDecoration(errorText: result.$2), ), diff --git a/lib/tools/converters/common_converter/common_converter.dart b/lib/tools/converters/common_converter/common_converter.dart index 8a98b69..b60ced2 100644 --- a/lib/tools/converters/common_converter/common_converter.dart +++ b/lib/tools/converters/common_converter/common_converter.dart @@ -170,10 +170,9 @@ class ConverterInput extends ConsumerWidget { ], ), Expanded( - child: TextField( + child: AppInput( expands: true, maxLines: null, - textAlignVertical: TextAlignVertical.top, controller: CommonConverterPage.of(context), ), ), diff --git a/lib/tools/encoders_decoders/base_64_encoder_decoder/base64.dart b/lib/tools/encoders_decoders/base_64_encoder_decoder/base64.dart index 64ff09a..bce7eff 100644 --- a/lib/tools/encoders_decoders/base_64_encoder_decoder/base64.dart +++ b/lib/tools/encoders_decoders/base_64_encoder_decoder/base64.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:alga/l10n/l10n.dart'; import 'package:alga/tools/tools.provider.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/buttons/clear_button.dart'; import 'package:alga/ui/widgets/buttons/copy_button.dart'; import 'package:alga/ui/widgets/configurations/configurations.dart'; @@ -72,7 +73,7 @@ class _Base64CodecPageState extends ConsumerState { ], ), Expanded( - child: TextField( + child: AppInput( expands: true, maxLines: null, controller: _originalInput, @@ -96,7 +97,7 @@ class _Base64CodecPageState extends ConsumerState { ], ), Expanded( - child: TextField( + child: AppInput( expands: true, maxLines: null, controller: _encodedInput, diff --git a/lib/tools/encoders_decoders/gzip_compress_decompress/gzip_compress.dart b/lib/tools/encoders_decoders/gzip_compress_decompress/gzip_compress.dart index e3da5c3..4dba64f 100644 --- a/lib/tools/encoders_decoders/gzip_compress_decompress/gzip_compress.dart +++ b/lib/tools/encoders_decoders/gzip_compress_decompress/gzip_compress.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:alga/l10n/l10n.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/buttons/clear_button.dart'; import 'package:alga/ui/widgets/buttons/copy_button.dart'; import 'package:alga/ui/widgets/toolbar/alga_toolbar.dart'; @@ -61,7 +62,7 @@ class _GZipCompressPageState extends State { ], ), Expanded( - child: TextField( + child: AppInput( expands: true, maxLines: null, controller: _originalInput, @@ -85,7 +86,7 @@ class _GZipCompressPageState extends State { ], ), Expanded( - child: TextField( + child: AppInput( expands: true, maxLines: null, controller: _encodedInput, diff --git a/lib/tools/encoders_decoders/jwt_decoder/jwt_decoder.dart b/lib/tools/encoders_decoders/jwt_decoder/jwt_decoder.dart index f1bd608..425c424 100644 --- a/lib/tools/encoders_decoders/jwt_decoder/jwt_decoder.dart +++ b/lib/tools/encoders_decoders/jwt_decoder/jwt_decoder.dart @@ -62,7 +62,7 @@ class _JwtDecoderPageState extends ConsumerState { ClearButton(controller: _controller), ], ), - TextField( + AppInput( controller: _controller, minLines: 3, maxLines: 12, diff --git a/lib/tools/encoders_decoders/uri_encoder_decoder/uri_codec.dart b/lib/tools/encoders_decoders/uri_encoder_decoder/uri_codec.dart index f0440f4..d79c774 100644 --- a/lib/tools/encoders_decoders/uri_encoder_decoder/uri_codec.dart +++ b/lib/tools/encoders_decoders/uri_encoder_decoder/uri_codec.dart @@ -1,4 +1,5 @@ import 'package:alga/l10n/l10n.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/buttons/clear_button.dart'; import 'package:alga/ui/widgets/buttons/copy_button.dart'; import 'package:alga/ui/widgets/configurations/configurations.dart'; @@ -103,7 +104,7 @@ class _UriCodecPageState extends ConsumerState { ], ), Expanded( - child: TextField( + child: AppInput( expands: true, maxLines: null, controller: _originalInput, @@ -127,7 +128,7 @@ class _UriCodecPageState extends ConsumerState { ], ), Expanded( - child: TextField( + child: AppInput( expands: true, maxLines: null, controller: _encodedInput, diff --git a/lib/tools/encoders_decoders/uri_parser/uri_parser.dart b/lib/tools/encoders_decoders/uri_parser/uri_parser.dart index d8c79ff..a76ab9c 100644 --- a/lib/tools/encoders_decoders/uri_parser/uri_parser.dart +++ b/lib/tools/encoders_decoders/uri_parser/uri_parser.dart @@ -63,7 +63,7 @@ class _UriParserPageState extends ConsumerState { ClearButton(controller: _controller), ], ), - TextField( + AppInput( controller: _controller, decoration: InputDecoration(errorText: result.$2), ), diff --git a/lib/tools/formatters/dart/dart_formatter.dart b/lib/tools/formatters/dart/dart_formatter.dart index 027b484..ab220f0 100644 --- a/lib/tools/formatters/dart/dart_formatter.dart +++ b/lib/tools/formatters/dart/dart_formatter.dart @@ -1,4 +1,5 @@ import 'package:alga/l10n/l10n.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/buttons/copy_button.dart'; import 'package:alga/ui/widgets/configurations/configurations.dart'; import 'package:alga/ui/widgets/custom_icon_button.dart'; @@ -76,7 +77,7 @@ class _DartFormatterPageState extends ConsumerState { child: ValueListenableBuilder( valueListenable: _errorMessage, builder: (context, message, _) { - return TextField( + return AppInput( expands: true, maxLines: null, minLines: null, diff --git a/lib/tools/formatters/json/json_formatter.dart b/lib/tools/formatters/json/json_formatter.dart index 2d36880..866e510 100644 --- a/lib/tools/formatters/json/json_formatter.dart +++ b/lib/tools/formatters/json/json_formatter.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:alga/l10n/l10n.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/buttons/copy_button.dart'; import 'package:alga/ui/widgets/configurations/configurations.dart'; import 'package:alga/ui/widgets/custom_icon_button.dart'; @@ -89,7 +90,7 @@ class _JsonFormatterPageState extends ConsumerState { child: ValueListenableBuilder( valueListenable: _errorMessage, builder: (context, message, _) { - return TextField( + return AppInput( expands: true, maxLines: null, minLines: null, diff --git a/lib/tools/generators/hash_generator/hash_gen.dart b/lib/tools/generators/hash_generator/hash_gen.dart index b33d52e..2ae1ca7 100644 --- a/lib/tools/generators/hash_generator/hash_gen.dart +++ b/lib/tools/generators/hash_generator/hash_gen.dart @@ -90,14 +90,14 @@ class _HashGenPageState extends ConsumerState { ClearButton(controller: _input), ], ), - TextField( + AppInput( minLines: 1, maxLines: 12, controller: _input, ), CrossFade( state: ref.watch(useHmac), - first: TextField( + first: AppInput( minLines: 1, maxLines: 12, controller: _hmac, diff --git a/lib/tools/generators/sass_css_generator/sass2css.dart b/lib/tools/generators/sass_css_generator/sass2css.dart index e8b40e8..eced75c 100644 --- a/lib/tools/generators/sass_css_generator/sass2css.dart +++ b/lib/tools/generators/sass_css_generator/sass2css.dart @@ -89,7 +89,7 @@ class _Sass2cssPageState extends ConsumerState { ), Consumer( builder: (context, ref, _) { - return TextField( + return AppInput( minLines: 3, maxLines: 8, controller: _input, diff --git a/lib/tools/image_tools/blur_hash_tool/blur_hash.dart b/lib/tools/image_tools/blur_hash_tool/blur_hash.dart index 8461979..d4d1e39 100644 --- a/lib/tools/image_tools/blur_hash_tool/blur_hash.dart +++ b/lib/tools/image_tools/blur_hash_tool/blur_hash.dart @@ -203,7 +203,7 @@ class _DecodeViewState extends State { return ListView( padding: const EdgeInsets.all(8), children: [ - TextField( + AppInput( controller: controller, decoration: InputDecoration( errorText: result.$2, diff --git a/lib/tools/image_tools/qrcode_tool/qrcode.dart b/lib/tools/image_tools/qrcode_tool/qrcode.dart index 196054c..8a580dc 100644 --- a/lib/tools/image_tools/qrcode_tool/qrcode.dart +++ b/lib/tools/image_tools/qrcode_tool/qrcode.dart @@ -1,5 +1,6 @@ import 'package:alga/l10n/l10n.dart'; import 'package:alga/tools/tools.provider.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/configurations/configurations.dart'; import 'package:alga/ui/widgets/scaffold/scrollable_scaffold.dart'; import 'package:alga/ui/widgets/toolbar/alga_toolbar.dart'; @@ -86,7 +87,7 @@ class _QrCodePageState extends ConsumerState { PasteButton(controller: _input), ], ), - TextField( + AppInput( maxLines: 2, controller: _input, ), diff --git a/lib/tools/js_tools/quick_js_tool/quick_js_view.dart b/lib/tools/js_tools/quick_js_tool/quick_js_view.dart index a566197..8b74d6d 100644 --- a/lib/tools/js_tools/quick_js_tool/quick_js_view.dart +++ b/lib/tools/js_tools/quick_js_tool/quick_js_view.dart @@ -32,7 +32,7 @@ class QuickJsView extends ConsumerWidget { child: Row( children: [ Expanded( - child: TextField( + child: AppInput( maxLines: 3, minLines: 1, controller: ref.watch(jsInputControllerProvider), diff --git a/lib/tools/text_tools/date_parser/date_parser.dart b/lib/tools/text_tools/date_parser/date_parser.dart index 8283f2d..4636070 100644 --- a/lib/tools/text_tools/date_parser/date_parser.dart +++ b/lib/tools/text_tools/date_parser/date_parser.dart @@ -102,7 +102,7 @@ class _DateParserPageState extends ConsumerState { ), ], ), - TextField( + AppInput( controller: _input, decoration: InputDecoration(errorText: dateResult.$2), ), @@ -146,7 +146,7 @@ class _DateParserPageState extends ConsumerState { HelpButton(onPressed: () => DateFormatHelper.show(context)), ], ), - TextField(controller: _formatInput), + AppInput(controller: _formatInput), AlgaToolbar( actions: [ CopyButton(() => ref.read(formatDateResultProvider)), diff --git a/lib/tools/text_tools/markdown_preview/markdown_preview.dart b/lib/tools/text_tools/markdown_preview/markdown_preview.dart index e9ae492..a298863 100644 --- a/lib/tools/text_tools/markdown_preview/markdown_preview.dart +++ b/lib/tools/text_tools/markdown_preview/markdown_preview.dart @@ -1,5 +1,6 @@ import 'package:alga/l10n/l10n.dart'; import 'package:alga/tools/tools.provider.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/buttons/clear_button.dart'; import 'package:alga/ui/widgets/buttons/copy_button.dart'; import 'package:alga/ui/widgets/toolbar/alga_toolbar.dart'; @@ -156,7 +157,7 @@ class MarkdownEditor extends StatelessWidget { ], ), Expanded( - child: TextField( + child: AppInput( controller: controller, expands: true, maxLines: null, diff --git a/lib/tools/text_tools/regex_tester/regex_tester.dart b/lib/tools/text_tools/regex_tester/regex_tester.dart index 857458f..625d365 100644 --- a/lib/tools/text_tools/regex_tester/regex_tester.dart +++ b/lib/tools/text_tools/regex_tester/regex_tester.dart @@ -2,6 +2,7 @@ import 'package:alga/l10n/l10n.dart'; import 'package:alga/tools/text_tools/regex_tester/regex_tester.provider.dart'; import 'package:alga/tools/text_tools/regex_tester/regex_tester_text_builder.dart'; import 'package:alga/tools/tools.provider.dart'; +import 'package:alga/ui/widgets/app_text_field.dart'; import 'package:alga/ui/widgets/buttons/clear_button.dart'; import 'package:alga/ui/widgets/configurations/configurations.dart'; import 'package:alga/ui/widgets/scaffold/scrollable_scaffold.dart'; @@ -89,7 +90,7 @@ class _RegexTesterPageState extends ConsumerState { ClearButton(controller: _regexController), ], ), - TextField( + AppInput( minLines: 2, maxLines: 4, controller: _regexController, @@ -102,7 +103,7 @@ class _RegexTesterPageState extends ConsumerState { ClearButton(controller: _textController), ], ), - TextField( + AppInput( minLines: 2, maxLines: 12, controller: _textController, diff --git a/lib/ui/alga_view/alga_view.dart b/lib/ui/alga_view/alga_view.dart index 484eb76..cf3b667 100644 --- a/lib/ui/alga_view/alga_view.dart +++ b/lib/ui/alga_view/alga_view.dart @@ -1,7 +1,7 @@ -import 'package:alga/ui/alga_view/widgets/alga_navigation_bar.dart'; import 'package:alga/ui/alga_view/widgets/alga_panel.dart'; import 'package:alga/ui/global.provider.dart'; -import 'package:alga/utils/constants/import_helper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; class AlgaShellRouteView extends ConsumerWidget { const AlgaShellRouteView({super.key, required this.child}); @@ -25,9 +25,9 @@ class AlgaShellRouteView extends ConsumerWidget { ), ); } else { - return Scaffold( - body: child, - bottomNavigationBar: const AlgaNavigationBar(), + return Material( + color: Theme.of(context).scaffoldBackgroundColor, + child: child, ); } } diff --git a/lib/ui/alga_view/all_apps/alga_app_item.dart b/lib/ui/alga_view/all_apps/alga_app_item.dart index 5bdab26..ce352d3 100644 --- a/lib/ui/alga_view/all_apps/alga_app_item.dart +++ b/lib/ui/alga_view/all_apps/alga_app_item.dart @@ -4,22 +4,23 @@ import 'package:alga/utils/hive_boxes/favorite_box.dart'; import 'package:auto_size_text/auto_size_text.dart'; class AlgaAppItem extends StatelessWidget { - const AlgaAppItem(this.item, {super.key, this.listenable = true}); + const AlgaAppItem(this.item, + {super.key, this.listenable = true, this.useGrid = true}); final AppAtom item; final bool listenable; + final bool useGrid; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - Color iconColor = colorScheme.tertiary; + Color iconColor = colorScheme.primary; + if (useGrid) { + return ValueListenableBuilder( + valueListenable: FavoriteBox.listener(item.path), + builder: (context, _, child) { + final state = FavoriteBox.get(item); - return ValueListenableBuilder( - valueListenable: FavoriteBox.listener(item.path), - builder: (context, _, child) { - final state = FavoriteBox.get(item); - return GestureDetector( - onLongPressStart: (detail) {}, - child: Material( + return Material( color: colorScheme.surfaceVariant, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -40,51 +41,73 @@ class AlgaAppItem extends StatelessWidget { }, child: child, ), - ), - ); - }, - child: Stack( - children: [ - Positioned( - left: 16 - 48, - bottom: 16, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: IconTheme.merge( - data: IconThemeData( - size: 84, - color: iconColor.withOpacity(0.4), + ); + }, + child: Stack( + children: [ + Positioned( + top: 0, + left: 0, + bottom: 0, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: IconTheme.merge( + data: IconThemeData( + size: 48, + color: iconColor.withOpacity(0.4), + ), + child: item.icon, ), - child: item.icon, ), ), - ), - Positioned( - left: 48, - right: 16, - top: 8, - bottom: 16, - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Align( - alignment: Alignment.bottomRight, - child: AutoSizeText( - item.title(context), - maxLines: 2, - minFontSize: 12, - textAlign: TextAlign.end, - style: TextStyle( - color: colorScheme.onSecondaryContainer, - fontSize: 16, - fontWeight: FontWeight.bold, + Positioned( + left: 56, + right: 16, + top: 8, + bottom: 16, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Align( + alignment: Alignment.bottomRight, + child: AutoSizeText( + item.title(context), + maxLines: 2, + minFontSize: 12, + textAlign: TextAlign.end, + style: TextStyle( + color: colorScheme.onSecondaryContainer, + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), ), ), ), - ), - ], - ), - ); + ], + ), + ); + } else { + return ValueListenableBuilder( + valueListenable: FavoriteBox.listener(item.path), + builder: (context, _, __) { + final state = FavoriteBox.get(item); + return GestureDetector( + onSecondaryTapUp: (detail) { + _showMenu(context, detail.localPosition, state); + }, + child: ListTile( + leading: item.icon, + title: Text(item.title(context)), + onTap: () { + GoRouter.of(context).go(item.path); + }, + onLongPress: () { + _showMenuModal(context, state); + }, + ), + ); + }); + } } _showMenu(BuildContext context, Offset offset, bool like) async { @@ -138,6 +161,7 @@ class AlgaAppItem extends StatelessWidget { showModalBottomSheet( context: context, showDragHandle: true, + useSafeArea: false, builder: (context) { return Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/ui/alga_view/all_apps/alga_app_view.dart b/lib/ui/alga_view/all_apps/alga_app_view.dart index 81d484a..85d17ec 100644 --- a/lib/ui/alga_view/all_apps/alga_app_view.dart +++ b/lib/ui/alga_view/all_apps/alga_app_view.dart @@ -4,7 +4,9 @@ import 'package:alga/models/app_atom.dart'; import 'package:alga/models/app_category.dart'; import 'package:alga/routers/app_router.dart'; import 'package:alga/ui/global.provider.dart'; +import 'package:alga/ui/views/favorite_view.dart'; import 'package:alga/ui/views/search_view.dart'; +import 'package:alga/ui/views/settings_view.dart'; import 'package:alga/utils/constants/import_helper.dart'; import 'alga_app_item.dart'; @@ -30,20 +32,18 @@ class AlgaAppViewState extends ConsumerState Widget build(BuildContext context) { final isDesktop = ref.watch(isDesktopProvider); return Scaffold( - appBar: AppBar( - title: Text(S.of(context).appName), - centerTitle: Platform.isIOS, - bottom: const AppCategoriesPanel(), - actions: [ - if (!isDesktop) - IconButton( - onPressed: () { - SearchRoute().push(context); - }, - icon: const Icon(Icons.search_rounded), + appBar: isDesktop + ? AppBar( + title: Text(S.of(context).appName), + centerTitle: Platform.isIOS, + bottom: const AppCategoriesPanel(), + ) + : AppBar( + // title: Text(S.of(context).appName), + title: const MobileSearchBar(), + centerTitle: Platform.isIOS, + bottom: const AppCategoriesPanel(), ), - ], - ), body: Consumer(builder: (context, ref, _) { return TabBarView( controller: ref.watch(appTabControllerProvider(vsync: this)), @@ -56,6 +56,49 @@ class AlgaAppViewState extends ConsumerState } } +class MobileSearchBar extends StatelessWidget { + const MobileSearchBar({super.key}); + + @override + Widget build(BuildContext context) { + return SearchAnchor.bar( + barHintText: '${context.tr.appName} ${context.tr.search}', + isFullScreen: true, + suggestionsBuilder: (BuildContext context, SearchController controller) { + final items = SearchViewState.findAtom(context, controller.text); + return items + .map( + (e) => ListTile( + leading: e.icon, + title: Text(e.title(context)), + onTap: () { + GoRouter.of(context).go(e.path); + }, + ), + ) + .toList(); + }, + barElevation: const MaterialStatePropertyAll(0), + barBackgroundColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.surfaceVariant), + barTrailing: [ + IconButton( + onPressed: () { + FavoriteRoute().push(context); + }, + icon: const Icon(Icons.favorite_outline_rounded), + ), + IconButton( + onPressed: () { + SettingsRoute().push(context); + }, + icon: const Icon(Icons.settings_outlined), + ), + ], + ); + } +} + class AppCategoriesPanel extends StatefulWidget implements PreferredSizeWidget { const AppCategoriesPanel({super.key}); @@ -87,7 +130,7 @@ class _AppCategoriesPanelState extends State { } } -class AppCategoryView extends StatelessWidget { +class AppCategoryView extends ConsumerWidget { const AppCategoryView(this.category, {super.key}); final AppCategory category; @@ -95,20 +138,37 @@ class AppCategoryView extends StatelessWidget { List get items => categoryMapping[category.uuid] ?? []; @override - Widget build(BuildContext context) { - return GridView.builder( - padding: const EdgeInsets.all(16), - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 220, - childAspectRatio: 2.0, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - ), - itemBuilder: (BuildContext context, int index) { - final item = items[index]; - return AlgaAppItem(item); - }, - itemCount: items.length, - ); + Widget build(BuildContext context, WidgetRef ref) { + if (ref.watch(useGridLayoutProvider)) { + return GridView.builder( + keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, + padding: const EdgeInsets.all(16), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 220, + childAspectRatio: 2.0, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + ), + itemBuilder: (BuildContext context, int index) { + final item = items[index]; + return AlgaAppItem(item); + }, + itemCount: items.length, + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 480), + child: ListView.builder( + itemBuilder: (context, index) { + final item = items[index]; + return AlgaAppItem(item, useGrid: false); + }, + itemCount: items.length, + ), + ), + ); + } } } diff --git a/lib/ui/alga_view/widgets/alga_navigation_bar.dart b/lib/ui/alga_view/widgets/alga_navigation_bar.dart deleted file mode 100644 index 324d6ce..0000000 --- a/lib/ui/alga_view/widgets/alga_navigation_bar.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:alga/routers/app_router.dart'; -import 'package:alga/ui/alga_view/all_apps/alga_app_view.dart'; -import 'package:alga/ui/views/favorite_view.dart'; -import 'package:alga/ui/views/search_view.dart'; -import 'package:alga/ui/views/settings_view.dart'; -import 'package:alga/utils/constants/import_helper.dart'; - -class AlgaNavigationBar extends StatefulWidget { - const AlgaNavigationBar({super.key}); - - @override - State createState() => _AlgaNavigationBarState(); -} - -class _AlgaNavigationBarState extends State { - int getIndex(BuildContext context) { - final location = GoRouterState.of(context).matchedLocation; - if (location.startsWith(AppsRoute().location)) return 0; - if (location.startsWith(FavoriteRoute().location)) return 1; - if (location.startsWith(SearchRoute().location)) return 0; - if (location.startsWith(SettingsRoute().location)) return 2; - return 0; - } - - @override - Widget build(BuildContext context) { - return NavigationBar( - selectedIndex: getIndex(context), - onDestinationSelected: (index) { - switch (index) { - case 0: - AppsRoute().go(context); - case 1: - FavoriteRoute().go(context); - case 2: - SettingsRoute().go(context); - default: - } - }, - destinations: [ - NavigationDestination( - icon: const Icon(Icons.category_outlined), - selectedIcon: const Icon(Icons.category_rounded), - label: context.tr.allApps, - ), - NavigationDestination( - icon: const Icon(Icons.favorite_outline_rounded), - selectedIcon: const Icon(Icons.favorite_rounded), - label: context.tr.favorite, - ), - NavigationDestination( - icon: const Icon(Icons.settings_outlined), - selectedIcon: const Icon(Icons.settings), - label: context.tr.settings, - ), - ], - ); - } -} diff --git a/lib/ui/views/search_view.dart b/lib/ui/views/search_view.dart index d4564d5..ed38a1e 100644 --- a/lib/ui/views/search_view.dart +++ b/lib/ui/views/search_view.dart @@ -16,10 +16,10 @@ class SearchView extends StatefulWidget { const SearchView({super.key}); @override - State createState() => _SearchViewState(); + State createState() => SearchViewState(); } -class _SearchViewState extends State { +class SearchViewState extends State { final _textController = TextEditingController(); List _items = []; @@ -27,10 +27,10 @@ class _SearchViewState extends State { @override void initState() { super.initState(); - _items = _findAtom(_textController.text); + _items = findAtom(context, _textController.text); _textController.addListener(() { - _items = _findAtom(_textController.text); + _items = findAtom(context, _textController.text); if (mounted) setState(() {}); }); } @@ -41,7 +41,7 @@ class _SearchViewState extends State { super.dispose(); } - List _findAtom(String query) { + static List findAtom(BuildContext context, String query) { if (query.isEmpty) return AppAtom.items.toList(); query = query.toLowerCase(); final results = {}; diff --git a/lib/ui/views/settings_view.dart b/lib/ui/views/settings_view.dart index 6ce750a..703430a 100644 --- a/lib/ui/views/settings_view.dart +++ b/lib/ui/views/settings_view.dart @@ -1,4 +1,3 @@ -import 'package:alga/routers/app_router.dart'; import 'package:alga/ui/widgets/alga_logo.dart'; import 'package:alga/ui/widgets/app_show_menu.dart'; import 'package:alga/ui/widgets/setting_title.dart'; @@ -13,13 +12,6 @@ class SettingsRoute extends GoRouteData { } } -class LicensesRoute extends GoRouteData { - @override - Widget build(BuildContext context, GoRouterState state) { - return const LicensePage(); - } -} - class SettingsView extends StatefulWidget { const SettingsView({super.key}); @@ -41,9 +33,8 @@ class _SettingsViewState extends State { SliverList( delegate: SliverChildListDelegate([ SettingTitle(Text(context.tr.lookAndFeel)), - ValueListenableBuilder( - valueListenable: AppConfigBox.key(AppConfigType.themeMode), - builder: (context, _, __) { + Consumer( + builder: (context, ref, child) { return AppShowMenu( items: ThemeMode.values .map((e) => PopupMenuItem( @@ -51,44 +42,79 @@ class _SettingsViewState extends State { child: Text(e.getName(context)), )) .toList(), - initialValue: AppConfigBox.themeMode, + initialValue: ref.watch(appThemeModeProvider), onSelected: (item) { - AppConfigBox.themeMode = item; + ref.read(appThemeModeProvider.notifier).change(item); }, childBuilder: (context, open) { return ListTile( onTap: open, leading: const Icon(Icons.dark_mode), - title: Text(S.of(context).themeMode), - trailing: Text(AppConfigBox.themeMode.getName(context)), + title: Text(context.tr.themeMode), + trailing: Text( + ref.watch(appThemeModeProvider).getName(context), + ), ); }, ); }, ), - ValueListenableBuilder( - valueListenable: AppConfigBox.key(AppConfigType.locale), - builder: (context, _, __) { - return AppShowMenu( - items: AppConfigBox.localCodes.map((e) { - return PopupMenuItem( - value: e, - child: Text(S.getlang(context, e)), - ); - }).toList(), - initialValue: AppConfigBox.localeValue, - onSelected: (item) { - AppConfigBox.localeValue = item; - }, + Consumer( + builder: (context, ref, _) { + return AppShowMenu( + items: [ + PopupMenuItem( + value: null, + onTap: () { + ref.read(appLocaleProvider.notifier).change(null); + }, + child: Text(context.tr.followSystem), + ), + ...S.supportedLocales.map((e) { + return PopupMenuItem( + value: e, + child: Text(e.getName(context)), + ); + }), + ], + initialValue: ref.watch(appLocaleProvider), childBuilder: (context, open) { return ListTile( - onTap: open, leading: const Icon(Icons.language), - title: Text(S.of(context).language), + title: Text(context.tr.language), trailing: - Text(S.getlang(context, AppConfigBox.localeValue)), + Text(ref.watch(appLocaleProvider).getName(context)), + onTap: open, ); }, + onSelected: (t) { + ref.read(appLocaleProvider.notifier).change(t); + }, + ); + }, + ), + if (isDark(context)) + Consumer( + builder: (context, ref, _) { + return SwitchListTile( + secondary: const Icon(Icons.night_shelter_rounded), + title: Text(context.tr.pureBlack), + value: ref.watch(pureBlackBackgroundProvider), + onChanged: (t) { + ref.read(pureBlackBackgroundProvider.notifier).change(t); + }, + ); + }, + ), + Consumer( + builder: (context, ref, _) { + return SwitchListTile( + secondary: const Icon(Icons.grid_view_rounded), + title: Text(context.tr.gridLayout), + value: ref.watch(useGridLayoutProvider), + onChanged: (t) { + ref.read(useGridLayoutProvider.notifier).change(t); + }, ); }, ), @@ -113,22 +139,22 @@ class _SettingsViewState extends State { ); }, ), - ListTile( - leading: const Icon(Icons.info_rounded), - title: Text(context.tr.licenses), - onTap: () { - LicensesRoute().go(context); - }, - ), - ListTile( - leading: const AlgaLogo(radius: 24), - title: Text(context.tr.appName), - subtitle: Text('${context.tr.version}$_buildName+$_buildNumber'), + AboutListTile( + icon: const AlgaLogo(radius: 24), + applicationName: context.tr.appName, + applicationIcon: const AlgaLogo(radius: 24), + applicationLegalese: 'MIT', + applicationVersion: + '${context.tr.version}$_buildName+$_buildNumber', + aboutBoxChildren: [], ), ])), ], ); - result = Material(child: result); + result = Material( + color: Theme.of(context).scaffoldBackgroundColor, + child: result, + ); return result; } } diff --git a/lib/ui/widgets/alga_logo.dart b/lib/ui/widgets/alga_logo.dart index 9cafaac..9355e94 100644 --- a/lib/ui/widgets/alga_logo.dart +++ b/lib/ui/widgets/alga_logo.dart @@ -13,6 +13,7 @@ class AlgaLogo extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; return Material( color: Theme.of(context).colorScheme.primaryContainer, shape: ContinuousRectangleBorder( @@ -20,8 +21,13 @@ class AlgaLogo extends StatelessWidget { ), child: Padding( padding: padding, - child: SvgPicture.asset('assets/logo/logo.svg', - height: radius, width: radius), + child: SvgPicture.asset( + 'assets/logo/logo.svg', + height: radius, + width: radius, + colorFilter: + ColorFilter.mode(colorScheme.onBackground, BlendMode.srcIn), + ), ), ); } diff --git a/lib/ui/widgets/app_text_field.dart b/lib/ui/widgets/app_text_field.dart index 9df1d9f..835e47d 100644 --- a/lib/ui/widgets/app_text_field.dart +++ b/lib/ui/widgets/app_text_field.dart @@ -1,3 +1,4 @@ +import 'package:alga/utils/theme_util.dart'; import 'package:flutter/material.dart'; import 'package:language_textfield/lang_special_builder.dart'; import 'package:language_textfield/language_textfield.dart'; @@ -67,6 +68,40 @@ class _AppTextFieldState extends State { textAlign: TextAlign.start, textAlignVertical: TextAlignVertical.top, decoration: widget.decoration, + style: getMonoTextStyle(context), + ); + } +} + +class AppInput extends StatelessWidget { + const AppInput({ + super.key, + required this.controller, + this.minLines, + this.maxLines = 1, + this.decoration = const InputDecoration(), + this.expands = false, + this.textAlignVertical = TextAlignVertical.top, + this.onChanged, + }); + final TextEditingController controller; + final int? minLines; + final int? maxLines; + final bool expands; + final InputDecoration? decoration; + final TextAlignVertical textAlignVertical; + final ValueChanged? onChanged; + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + minLines: minLines, + maxLines: maxLines, + style: getMonoTextStyle(context), + textAlignVertical: textAlignVertical, + decoration: decoration, + onChanged: onChanged, + expands: expands, ); } } diff --git a/lib/ui/widgets/scaffold/scrollable_scaffold.dart b/lib/ui/widgets/scaffold/scrollable_scaffold.dart index 807ee7a..7cc722f 100644 --- a/lib/ui/widgets/scaffold/scrollable_scaffold.dart +++ b/lib/ui/widgets/scaffold/scrollable_scaffold.dart @@ -40,6 +40,7 @@ class ScrollableScaffold extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( + keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, slivers: [ SliverAppBar.medium(title: title, actions: actions), if (configurations.isNotEmpty) diff --git a/lib/ui/widgets/svg_asset_icon.dart b/lib/ui/widgets/svg_asset_icon.dart index 07ffa08..791c5c9 100644 --- a/lib/ui/widgets/svg_asset_icon.dart +++ b/lib/ui/widgets/svg_asset_icon.dart @@ -8,8 +8,7 @@ class SvgAssetIcon extends StatelessWidget { const SvgAssetIcon(this.svg, {super.key, this.colorIcon = false}); Color? _color(BuildContext context) { - if (colorIcon) return null; - return Theme.of(context).colorScheme.secondary; + return Theme.of(context).colorScheme.primary; } @override @@ -21,9 +20,8 @@ class SvgAssetIcon extends StatelessWidget { svg, width: iconSize, height: iconSize, - colorFilter: color == null - ? null - : ColorFilter.mode(iconColor ?? color, BlendMode.srcIn), + colorFilter: + colorIcon ? null : ColorFilter.mode(iconColor!, BlendMode.srcIn), ); return SizedBox.square( dimension: iconSize, diff --git a/lib/ui/widgets/tool_view_config.dart b/lib/ui/widgets/tool_view_config.dart index badcde9..655b0eb 100644 --- a/lib/ui/widgets/tool_view_config.dart +++ b/lib/ui/widgets/tool_view_config.dart @@ -20,10 +20,7 @@ class ToolViewConfig extends StatelessWidget { final scheme = Theme.of(context).colorScheme; return ListTile( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - title: DefaultTextStyle.merge( - style: TextStyle(color: scheme.onSecondaryContainer, fontFamily: ''), - child: title, - ), + title: title, subtitle: subtitle, leading: leading, trailing: trailing, diff --git a/lib/utils/hive_boxes/app_config_box.dart b/lib/utils/hive_boxes/app_config_box.dart index 1ded59c..caed348 100644 --- a/lib/utils/hive_boxes/app_config_box.dart +++ b/lib/utils/hive_boxes/app_config_box.dart @@ -66,6 +66,46 @@ class AppConfigBox { } return HiveUtil.appConfigBox.listenable(keys: listenKeys); } + + static String? get _languageCode => + HiveUtil.appConfigBox.get(AppConfigType.languageCode.name); + static set _languageCode(String? data) { + HiveUtil.appConfigBox.put(AppConfigType.languageCode.name, data); + } + + static String? get _countryCode => + HiveUtil.appConfigBox.get(AppConfigType.countryCode.name); + static set _countryCode(String? data) { + HiveUtil.appConfigBox.put(AppConfigType.countryCode.name, data); + } + + static Locale? get appLocale { + if (_languageCode == null) return null; + return Locale(_languageCode!, _countryCode); + } + + static set appLocale(Locale? locale) { + if (locale == null) { + _languageCode = null; + _countryCode = null; + } else { + _languageCode = locale.languageCode; + _countryCode = locale.countryCode; + } + } + + static bool get pureBlackBackground => + HiveUtil.appConfigBox.get(AppConfigType.pureBlackBackground.name) ?? + false; + + static set pureBlackBackground(bool state) => + HiveUtil.appConfigBox.put(AppConfigType.pureBlackBackground.name, state); + + static bool get useGridLayout => + HiveUtil.appConfigBox.get(AppConfigType.useGridLayout.name) ?? true; + + static set useGridLayout(bool state) => + HiveUtil.appConfigBox.put(AppConfigType.useGridLayout.name, state); } extension ThemeModeX on ThemeMode { @@ -85,4 +125,8 @@ enum AppConfigType { themeColor, themeMode, locale, + languageCode, + countryCode, + pureBlackBackground, + useGridLayout, } diff --git a/lib/utils/theme_util.dart b/lib/utils/theme_util.dart index 19d1f05..bc63a74 100644 --- a/lib/utils/theme_util.dart +++ b/lib/utils/theme_util.dart @@ -1,6 +1,10 @@ +import 'package:alga/utils/hive_boxes/app_config_box.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'theme_util.g.dart'; bool isDark(BuildContext context) => Theme.of(context).brightness == Brightness.dark; @@ -20,45 +24,91 @@ final kDefaultDarkColorScheme = ColorScheme.fromSeed( brightness: Brightness.dark, ); -class ThemeUtil { - late ColorScheme colorScheme; - ThemeUtil(this.colorScheme); +@Riverpod(keepAlive: true) +class AppThemeMode extends _$AppThemeMode { + @override + ThemeMode build() => AppConfigBox.themeMode; - static final _inputDecorationTheme = InputDecorationTheme( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - ); + void change(ThemeMode mode) { + state = mode; + AppConfigBox.themeMode = mode; + } +} - ThemeData getTheme(Brightness brightness) { - ColorScheme scheme = colorScheme; - final typo = Typography.material2021( - platform: defaultTargetPlatform, colorScheme: colorScheme); - final baseTextStyle = switch (brightness) { - Brightness.dark => typo.white, - Brightness.light => typo.black, - }; - return ThemeData( - colorScheme: colorScheme, - splashFactory: InkSparkle.splashFactory, - inputDecorationTheme: _inputDecorationTheme.copyWith( - contentPadding: const EdgeInsets.all(12), - ), - listTileTheme: ListTileThemeData( - iconColor: scheme.secondary, - ), - snackBarTheme: SnackBarThemeData( - actionTextColor: scheme.background, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), +@Riverpod(keepAlive: true) +class AppLocale extends _$AppLocale { + @override + Locale? build() => AppConfigBox.appLocale; + + void change(Locale? locale) { + AppConfigBox.appLocale = locale; + state = locale; + } +} + +@Riverpod(keepAlive: true) +class PureBlackBackground extends _$PureBlackBackground { + @override + bool build() => AppConfigBox.pureBlackBackground; + + void change(bool value) { + state = value; + AppConfigBox.pureBlackBackground = value; + } +} + +@Riverpod(keepAlive: true) +class UseGridLayout extends _$UseGridLayout { + @override + bool build() => AppConfigBox.useGridLayout; + + void change(bool value) { + state = value; + AppConfigBox.useGridLayout = value; + } +} + +@Riverpod(keepAlive: true) +ThemeData appThemeData( + AppThemeDataRef ref, ColorScheme colorScheme, Brightness brightness) { + Color? scaffoldColor; + if (brightness == Brightness.dark) { + final isPureDark = ref.watch(pureBlackBackgroundProvider); + if (isPureDark) scaffoldColor = Colors.black; + } + + return ThemeData( + colorScheme: colorScheme, + splashFactory: InkSparkle.splashFactory, + scaffoldBackgroundColor: scaffoldColor, + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), ), - textTheme: TextTheme( - bodyLarge: GoogleFonts.notoSansMonoTextTheme(baseTextStyle) - .bodyLarge - ?.copyWith(fontSize: 12), + ).copyWith( + contentPadding: const EdgeInsets.all(12), + ), + listTileTheme: ListTileThemeData( + iconColor: colorScheme.secondary, + ), + snackBarTheme: SnackBarThemeData( + actionTextColor: colorScheme.background, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - useMaterial3: true, - ); - } + ), + ); +} + +TextStyle getMonoTextStyle(BuildContext context) { + final theme = Theme.of(context); + final typo = Typography.material2021( + platform: defaultTargetPlatform, colorScheme: theme.colorScheme); + final baseTextStyle = switch (theme.brightness) { + Brightness.dark => typo.white, + Brightness.light => typo.black, + }; + return GoogleFonts.notoSansMonoTextTheme(baseTextStyle) + .bodyLarge! + .copyWith(fontSize: 12); } diff --git a/pubspec.lock b/pubspec.lock index 701e780..48da220 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -552,10 +552,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: d261b0f2461e0595b96f92ed807841eb72cea84a6b12b8fd0c76e5ed803e7921 + sha256: da9591d1f8d5881628ccd5c25c40e74fc3eef50ba45e40c3905a06e1712412d5 url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.8" + version: "2.4.9" flutter_svg: dependency: "direct main" description: @@ -1168,10 +1168,10 @@ packages: dependency: transitive description: name: riverpod - sha256: "08451ddbaad6eae73e2422d8109775885623340d721c6637b8719c9f4b478848" + sha256: "942999ee48b899f8a46a860f1e13cee36f2f77609eb54c5b7a669bb20d550b11" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.8" + version: "2.4.9" riverpod_analyzer_utils: dependency: transitive description: @@ -1184,26 +1184,26 @@ packages: dependency: "direct main" description: name: riverpod_annotation - sha256: "02c9bced96ed3ed8d9970820d1ce7b16600955bc01aa8b2276f09dd3d9d29ed9" + sha256: b70e95fbd5ca7ce42f5148092022971bb2e9843b6ab71e97d479e8ab52e98979 url: "https://pub.flutter-io.cn" source: hosted - version: "2.3.2" + version: "2.3.3" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: "94b6c49bba879729611d690d434796e3b4e7c72a27e88b482b92c505e90f90d9" + sha256: ff8f064f1d7ef3cc6af481bba8e9a3fcdb4d34df34fac1b39bbc003167065be0 url: "https://pub.flutter-io.cn" source: hosted - version: "2.3.8" + version: "2.3.9" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "6fc64ae102ba39b0889b7aa7f4ef6c5a8f71a2ad215b90c787f319a9407a128b" + sha256: "944929ef82c9bfeaa455ccab97920abcf847a0ffed5c9f6babc520a95db25176" url: "https://pub.flutter-io.cn" source: hosted - version: "2.3.6" + version: "2.3.7" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7ba4005..265812e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: flutter_markdown: ^0.6.18+2 url_launcher: ^6.2.1 animations: ^2.0.8 - flutter_riverpod: ^2.4.8 + flutter_riverpod: ^2.4.9 sass: ^1.69.5 file_selector: ^1.0.1 shelf_static: ^1.1.2 @@ -67,7 +67,7 @@ dependencies: multi_split_view: ^2.4.0 auto_size_text: ^3.0.0 filesize: ^2.0.1 - riverpod_annotation: ^2.3.2 + riverpod_annotation: ^2.3.3 file: ^7.0.0 path_provider: ^2.1.1 quick_actions: ^1.0.6 @@ -88,9 +88,9 @@ dev_dependencies: flutter_lints: ^3.0.1 build_runner: ^2.4.6 msix: ^3.16.6 - riverpod_generator: ^2.3.8 + riverpod_generator: ^2.3.9 go_router_builder: ^2.3.4 - riverpod_lint: ^2.3.6 + riverpod_lint: ^2.3.7 custom_lint: ^0.5.7 grinder: ^0.9.5 cli_util: ^0.4.0 @@ -105,11 +105,6 @@ flutter: - assets/images/ - assets/android/ - fonts: - - family: Noto Sans Mono - fonts: - - asset: assets/fonts/NotoSansMono-Regular.ttf - import_sorter: emojis: true comments: false