feat: dynamic colors (Material You)

This commit is contained in:
Tien Do Nam
2023-05-29 03:31:22 +02:00
parent 07ec52ee52
commit 6b0832a78b
80 changed files with 463 additions and 177 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
## 1.9.2 (2023-)
## 1.10.0 (2023-)
- feat: dynamic colors (Material You) (by @Tienisto)
- fix: cancellation fixes during active file transfer (by @SelaseKay)
- fix: possible settings corruption on Windows (by @TheGB0077, @Tienisto)
@@ -21,6 +21,11 @@
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
},
"saveWindowPlacement": "Quit: Save window placement"
},
"receive": {
@@ -21,6 +21,11 @@
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
},
"saveWindowPlacement": "Quit: Save window placement"
},
"receive": {
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <cs>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=cs' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <es-ES>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=es-ES' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <fa>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=fa' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <fr-FR>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=fr-FR' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <hu>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=hu' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
@@ -21,6 +21,11 @@
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
},
"saveWindowPlacement": "Quit: Save window placement"
},
"receive": {
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <it>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=it' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
@@ -21,6 +21,11 @@
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
},
"saveWindowPlacement": "Quit: Save window placement"
},
"receive": {
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <ja>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=ja' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <ko>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=ko' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
@@ -21,6 +21,11 @@
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
},
"saveWindowPlacement": "Quit: Save window placement"
},
"receive": {
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <nl>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=nl' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <pl>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=pl' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <pt-BR>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=pt-BR' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <ru>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=ru' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
@@ -21,6 +21,11 @@
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
},
"saveWindowPlacement": "Quit: Save window placement"
},
"receive": {
@@ -21,6 +21,11 @@
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
},
"saveWindowPlacement": "Quit: Save window placement"
},
"receive": {
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <uk>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=uk' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
@@ -3,6 +3,15 @@
"Here are translations that exist in <en> but not in <ur>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=ur' to quickly apply the newly added translations."
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
},
"apkPickerPage": {
"title": "Apps (APK)",
"excludeSystemApps": "Exclude system apps",
+10 -1
View File
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <zh-Hans>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=zh-Hans' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <zh-Hant-HK>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=zh-Hant-HK' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
@@ -2,5 +2,14 @@
"@@info": [
"Here are translations that exist in <en> but not in <zh-Hant-TW>.",
"After editing this file, you can run 'flutter pub run slang apply --locale=zh-Hant-TW' to quickly apply the newly added translations."
]
],
"settingsTab": {
"general": {
"brightness": "Brightness",
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
}
+14
View File
@@ -11,6 +11,20 @@
},
"sendTab": {
"thisDevice": "This Device"
},
"settingsTab": {
"general": {
"brightness": "Brightness",
"brightnessOptions": {
"system": "System",
"dark": "Dark",
"light": "Light"
},
"color": "Color",
"colorOptions": {
"system": "System"
}
}
}
},
"ar": {},
+6 -2
View File
@@ -78,12 +78,16 @@
"title": "Settings",
"general": {
"title": "General",
"theme": "Theme",
"themeOptions": {
"brightness": "Brightness",
"brightnessOptions": {
"system": "System",
"dark": "Dark",
"light": "Light"
},
"color": "Color",
"colorOptions": {
"system": "System"
},
"language": "Language",
"languageOptions": {
"system": "System"
+1 -2
View File
@@ -68,8 +68,7 @@
"title": "إعدادات",
"general": {
"title": "عام",
"theme": "مظهر",
"themeOptions": {
"brightnessOptions": {
"system": "نظام",
"dark": "داكن",
"light": "فاتح"
+1 -2
View File
@@ -68,8 +68,7 @@
"title": "সেটিংস",
"general": {
"title": "জেনারেল",
"theme": "থিম",
"themeOptions": {
"brightnessOptions": {
"system": "সিস্টেম",
"dark": "ডার্ক",
"light": "লাইট"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Nastavení",
"general": {
"title": "Všeobecné",
"theme": "Téma",
"themeOptions": {
"brightnessOptions": {
"system": "Systémové",
"dark": "Tmavé",
"light": "Světlé"
+6 -2
View File
@@ -78,12 +78,16 @@
"title": "Einstellungen",
"general": {
"title": "Allgemein",
"theme": "Oberfläche",
"themeOptions": {
"brightness": "Helligkeit",
"brightnessOptions": {
"system": "System",
"dark": "Dunkel",
"light": "Hell"
},
"color": "Farbe",
"colorOptions": {
"system": "System"
},
"language": "Sprache",
"languageOptions": {
"system": "System"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Ajustes",
"general": {
"title": "General",
"theme": "Tema",
"themeOptions": {
"brightnessOptions": {
"system": "Sistema",
"dark": "Oscuro",
"light": "Claro"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "تنظیمات",
"general": {
"title": "عمومی",
"theme": "قالب",
"themeOptions": {
"brightnessOptions": {
"system": "سیستم",
"dark": "تیره",
"light": "روشن"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Paramètres",
"general": {
"title": "Général",
"theme": "Thème",
"themeOptions": {
"brightnessOptions": {
"system": "Système",
"dark": "Sombre",
"light": "Clair"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Beállítások",
"general": {
"title": "Általános",
"theme": "Téma",
"themeOptions": {
"brightnessOptions": {
"system": "Rendszer",
"dark": "Sötét",
"light": "Világos"
+1 -2
View File
@@ -68,8 +68,7 @@
"title": "Pengaturan",
"general": {
"title": "Umum",
"theme": "Tema",
"themeOptions": {
"brightnessOptions": {
"system": "Sistem",
"dark": "Gelap",
"light": "Terang"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Impostazioni",
"general": {
"title": "Generale",
"theme": "Tema",
"themeOptions": {
"brightnessOptions": {
"system": "Sistema",
"dark": "Scuro",
"light": "Chiaro"
+1 -2
View File
@@ -68,8 +68,7 @@
"title": "הגדרות",
"general": {
"title": "כללי",
"theme": "ערכת נושא",
"themeOptions": {
"brightnessOptions": {
"system": "מערכת",
"dark": "כֵּהֶה",
"light": "בָּהִיר"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "設定",
"general": {
"title": "一般",
"theme": "テーマ",
"themeOptions": {
"brightnessOptions": {
"system": "システム",
"dark": "ダーク",
"light": "ライト"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "설정",
"general": {
"title": "일반",
"theme": "테마",
"themeOptions": {
"brightnessOptions": {
"system": "시스템",
"dark": "어두움",
"light": "밝음"
+1 -2
View File
@@ -68,8 +68,7 @@
"title": "सेटिङहरू",
"general": {
"title": "सामान्य",
"theme": "विषयवस्तु",
"themeOptions": {
"brightnessOptions": {
"system": "सिस्टम",
"dark": "अँध्यारो",
"light": "उज्यालो"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Instellingen",
"general": {
"title": "Algemeen",
"theme": "Thema",
"themeOptions": {
"brightnessOptions": {
"system": "Systeem",
"dark": "Donker",
"light": "Licht"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Ustawienia",
"general": {
"title": "Ogólne",
"theme": "Motyw",
"themeOptions": {
"brightnessOptions": {
"system": "System",
"dark": "Ciemny",
"light": "Jasny"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Configurações",
"general": {
"title": "Geral",
"theme": "Tema",
"themeOptions": {
"brightnessOptions": {
"system": "Sistema",
"dark": "Escuro",
"light": "Claro"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Настройки",
"general": {
"title": "Общие",
"theme": "Тема",
"themeOptions": {
"brightnessOptions": {
"system": "Системная",
"dark": "Тёмная",
"light": "Светлая"
+1 -2
View File
@@ -68,8 +68,7 @@
"title": "Inställningar",
"general": {
"title": "Allmänt",
"theme": "Tema",
"themeOptions": {
"brightnessOptions": {
"system": "System",
"dark": "Mörkt",
"light": "Ljust"
+1 -2
View File
@@ -68,8 +68,7 @@
"title": "Ayarlar",
"general": {
"title": "Genel",
"theme": "Tema",
"themeOptions": {
"brightnessOptions": {
"system": "Sistem teması",
"dark": "Koyu",
"light": "Açık"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "Налаштування",
"general": {
"title": "Загальні",
"theme": "Тема",
"themeOptions": {
"brightnessOptions": {
"system": "Системна",
"dark": "Темна",
"light": "Світла"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "ترتیبات",
"general": {
"title": "جنرل",
"theme": "تھیم",
"themeOptions": {
"brightnessOptions": {
"system": "سسٹم",
"dark": "اندھیرا",
"light": "روشنی"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "设置",
"general": {
"title": "通用",
"theme": "主题",
"themeOptions": {
"brightnessOptions": {
"system": "跟随系统",
"dark": "暗色",
"light": "亮色"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "@:general.settings",
"general": {
"title": "一般",
"theme": "主題",
"themeOptions": {
"brightnessOptions": {
"system": "跟機",
"dark": "暗色",
"light": "亮色"
+1 -2
View File
@@ -78,8 +78,7 @@
"title": "設定",
"general": {
"title": "一般",
"theme": "主題",
"themeOptions": {
"brightnessOptions": {
"system": "系統",
"dark": "深色",
"light": "淺色"
+23 -16
View File
@@ -1,10 +1,12 @@
import 'dart:io';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/init.dart';
import 'package:localsend_app/model/persistence/color_mode.dart';
import 'package:localsend_app/pages/home_page.dart';
import 'package:localsend_app/provider/app_arguments_provider.dart';
import 'package:localsend_app/provider/device_info_provider.dart';
@@ -43,6 +45,7 @@ class LocalSendApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(settingsProvider.select((settings) => settings.theme));
final colorMode = ref.watch(settingsProvider.select((settings) => settings.colorMode));
return TrayWatcher(
child: WindowWatcher(
onClose: () async {
@@ -66,22 +69,26 @@ class LocalSendApp extends ConsumerWidget {
}
},
child: ShortcutWatcher(
child: MaterialApp(
title: t.appName,
locale: TranslationProvider.of(context).flutterLocale,
supportedLocales: AppLocaleUtils.supportedLocales,
localizationsDelegates: GlobalMaterialLocalizations.delegates,
debugShowCheckedModeBanner: false,
theme: getTheme(Brightness.light),
darkTheme: getTheme(Brightness.dark),
themeMode: themeMode,
navigatorKey: Routerino.navigatorKey,
home: RouterinoHome(
builder: () => const HomePage(
initialTab: HomeTab.receive,
appStart: true,
),
),
child: DynamicColorBuilder(
builder: (lightColor, darkColor) {
return MaterialApp(
title: t.appName,
locale: TranslationProvider.of(context).flutterLocale,
supportedLocales: AppLocaleUtils.supportedLocales,
localizationsDelegates: GlobalMaterialLocalizations.delegates,
debugShowCheckedModeBanner: false,
theme: getTheme(Brightness.light, colorMode == ColorMode.system ? lightColor : null),
darkTheme: getTheme(Brightness.dark, colorMode == ColorMode.system ? darkColor : null),
themeMode: themeMode,
navigatorKey: Routerino.navigatorKey,
home: RouterinoHome(
builder: () => const HomePage(
initialTab: HomeTab.receive,
appStart: true,
),
),
);
},
),
),
),
+4
View File
@@ -0,0 +1,4 @@
enum ColorMode {
system, // dynamic colors
localsend,
}
+2
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/model/persistence/color_mode.dart';
import 'package:localsend_app/model/send_mode.dart';
part 'settings_state.freezed.dart';
@@ -11,6 +12,7 @@ class SettingsState with _$SettingsState {
required String showToken, // the token to show / maximize the window because only one instance is allowed
required String alias,
required ThemeMode theme,
required ColorMode colorMode,
required AppLocale? locale,
required int port,
required String multicastGroup,
+1 -1
View File
@@ -34,7 +34,7 @@ class AboutPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 15),
children: [
const SizedBox(height: 20),
const LocalSendLogo(),
const LocalSendLogo(withText: true),
Text(
'© ${DateTime.now().year} Tien Do Nam',
textAlign: TextAlign.center,
+4 -5
View File
@@ -190,7 +190,7 @@ class _ProgressPageState extends ConsumerState<ProgressPage> {
TextSpan(
text: receiveSession.destinationDirectory,
style: TextStyle(
color: checkPlatform([TargetPlatform.iOS]) ? Colors.grey : Theme.of(context).colorScheme.tertiary,
color: checkPlatform([TargetPlatform.iOS]) ? Colors.grey : Theme.of(context).colorScheme.primary,
),
recognizer: checkPlatform([TargetPlatform.iOS])
? null
@@ -348,7 +348,6 @@ class _ProgressPageState extends ConsumerState<ProgressPage> {
CustomProgressBar(
progress: _totalBytes == 0 ? 0 : currBytes / _totalBytes,
borderRadius: 5,
color: Theme.of(context).colorScheme.tertiaryContainer,
),
AnimatedCrossFade(
crossFadeState: _advanced ? CrossFadeState.showSecond : CrossFadeState.showFirst,
@@ -435,15 +434,15 @@ extension on FileStatus {
Color getColor(BuildContext context) {
switch (this) {
case FileStatus.queue:
return Theme.of(context).colorScheme.tertiaryContainer;
return Theme.of(context).colorScheme.primary;
case FileStatus.skipped:
return Colors.grey;
case FileStatus.sending:
return Theme.of(context).colorScheme.tertiaryContainer;
return Theme.of(context).colorScheme.primary;
case FileStatus.failed:
return Theme.of(context).colorScheme.warning;
case FileStatus.finished:
return Theme.of(context).colorScheme.tertiaryContainer;
return Theme.of(context).colorScheme.primary;
}
}
}
+1 -1
View File
@@ -145,7 +145,7 @@ class ReceiveOptionsPage extends ConsumerWidget {
color: !selectState.containsKey(file.file.id)
? Colors.grey
: (selectState[file.file.id] == file.file.fileName
? Theme.of(context).colorScheme.tertiaryContainer
? Theme.of(context).colorScheme.onSecondaryContainer
: Colors.orange)),
)
],
+3 -3
View File
@@ -117,14 +117,14 @@ class _ReceivePageState extends ConsumerState<ReceivePage> {
setState(() => _showFullIp = !_showFullIp);
},
child: DeviceBadge(
color: Theme.of(context).colorScheme.tertiaryContainer,
color: Theme.of(context).colorScheme.onSecondaryContainer,
label: _showFullIp ? receiveSession.sender.ip : '#${receiveSession.sender.ip.visualId}',
),
),
if (receiveSession.sender.deviceModel != null) ...[
const SizedBox(width: 10),
DeviceBadge(
color: Theme.of(context).colorScheme.tertiaryContainer,
color: Theme.of(context).colorScheme.onSecondaryContainer,
label: receiveSession.sender.deviceModel!,
),
],
@@ -252,7 +252,7 @@ class _ReceivePageState extends ConsumerState<ReceivePage> {
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
foregroundColor: Colors.white, // wrong in dark mode, so we hard code this
foregroundColor: Theme.of(context).colorScheme.onError,
),
onPressed: () {
_decline(ref);
+2 -5
View File
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localsend_app/gen/assets.gen.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/pages/receive_history_page.dart';
import 'package:localsend_app/provider/network/server/server_provider.dart';
@@ -11,6 +10,7 @@ import 'package:localsend_app/util/sleep.dart';
import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
import 'package:localsend_app/widget/custom_icon_button.dart';
import 'package:localsend_app/widget/dialogs/quick_save_notice.dart';
import 'package:localsend_app/widget/local_send_logo.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:localsend_app/widget/rotating_widget.dart';
import 'package:routerino/routerino.dart';
@@ -62,10 +62,7 @@ class _ReceiveTagState extends ConsumerState<ReceiveTab> with AutomaticKeepAlive
child: RotatingWidget(
duration: const Duration(seconds: 15),
spinning: serverState != null,
child: SizedBox(
height: 200,
child: Assets.img.logo512.image(height: 200),
),
child: const LocalSendLogo(withText: false),
),
),
Text(serverState?.alias ?? settings.alias, style: const TextStyle(fontSize: 48)),
+35 -5
View File
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localsend_app/constants.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/model/persistence/color_mode.dart';
import 'package:localsend_app/pages/about_page.dart';
import 'package:localsend_app/pages/changelog_page.dart';
import 'package:localsend_app/pages/language_page.dart';
@@ -59,7 +60,7 @@ class _SettingsTabState extends ConsumerState<SettingsTab> {
title: t.settingsTab.general.title,
children: [
_SettingsEntry(
label: t.settingsTab.general.theme,
label: t.settingsTab.general.brightness,
child: CustomDropdownButton<ThemeMode>(
value: settings.theme,
items: ThemeMode.values.map((theme) {
@@ -80,6 +81,24 @@ class _SettingsTabState extends ConsumerState<SettingsTab> {
},
),
),
_SettingsEntry(
label: t.settingsTab.general.color,
child: CustomDropdownButton<ColorMode>(
value: settings.colorMode,
items: ColorMode.values.map((colorMode) {
return DropdownMenuItem(
value: colorMode,
alignment: Alignment.center,
child: Text(colorMode.humanName),
);
}).toList(),
onChanged: (colorMode) async {
if (colorMode != null) {
await ref.read(settingsProvider.notifier).setColorMode(colorMode);
}
},
),
),
_SettingsEntry(
label: t.settingsTab.general.language,
child: TextButton(
@@ -380,7 +399,7 @@ class _SettingsTabState extends ConsumerState<SettingsTab> {
),
),
const SizedBox(height: 40),
const LocalSendLogo(),
const LocalSendLogo(withText: true),
const SizedBox(height: 5),
ref.watch(versionProvider).maybeWhen(
data: (version) => Text(
@@ -494,11 +513,22 @@ extension on ThemeMode {
String get humanName {
switch (this) {
case ThemeMode.system:
return t.settingsTab.general.themeOptions.system;
return t.settingsTab.general.brightnessOptions.system;
case ThemeMode.light:
return t.settingsTab.general.themeOptions.light;
return t.settingsTab.general.brightnessOptions.light;
case ThemeMode.dark:
return t.settingsTab.general.themeOptions.dark;
return t.settingsTab.general.brightnessOptions.dark;
}
}
}
extension on ColorMode {
String get humanName {
switch (this) {
case ColorMode.system:
return t.settingsTab.general.colorOptions.system;
case ColorMode.localsend:
return t.appName;
}
}
}
+1 -1
View File
@@ -230,7 +230,7 @@ class _WebSendPageState extends ConsumerState<WebSendPage> {
child: Text(
t.general.accepted,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.tertiaryContainer,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
),
+15 -1
View File
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localsend_app/constants.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/model/persistence/color_mode.dart';
import 'package:localsend_app/model/persistence/stored_security_context.dart';
import 'package:localsend_app/model/receive_history_entry.dart';
import 'package:localsend_app/model/send_mode.dart';
@@ -37,7 +38,8 @@ const _saveWindowPlacement = 'ls_save_window_placement';
// Settings
const _showToken = 'ls_show_token';
const _aliasKey = 'ls_alias';
const _themeKey = 'ls_theme';
const _themeKey = 'ls_theme'; // now called brightness
const _colorKey = 'ls_color';
const _localeKey = 'ls_locale';
const _portKey = 'ls_port';
const _multicastGroupKey = 'ls_multicast_group';
@@ -128,6 +130,18 @@ class PersistenceService {
await _prefs.setString(_themeKey, theme.name);
}
ColorMode getColorMode() {
final value = _prefs.getString(_colorKey);
if (value == null) {
return ColorMode.system;
}
return ColorMode.values.firstWhereOrNull((color) => color.name == value) ?? ColorMode.system;
}
Future<void> setColorMode(ColorMode color) async {
await _prefs.setString(_colorKey, color.name);
}
AppLocale? getLocale() {
final value = _prefs.getString(_localeKey);
if (value == null) {
+9
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/model/persistence/color_mode.dart';
import 'package:localsend_app/model/send_mode.dart';
import 'package:localsend_app/model/state/settings_state.dart';
import 'package:localsend_app/provider/persistence_provider.dart';
@@ -21,6 +22,7 @@ class SettingsNotifier extends Notifier<SettingsState> {
showToken: _service.getShowToken(),
alias: _service.getAlias(),
theme: _service.getTheme(),
colorMode: _service.getColorMode(),
locale: _service.getLocale(),
port: _service.getPort(),
multicastGroup: _service.getMulticastGroup(),
@@ -49,6 +51,13 @@ class SettingsNotifier extends Notifier<SettingsState> {
);
}
Future<void> setColorMode(ColorMode mode) async {
await _service.setColorMode(mode);
state = state.copyWith(
colorMode: mode,
);
}
Future<void> setLocale(AppLocale? locale) async {
await _service.setLocale(locale);
state = state.copyWith(
+26 -21
View File
@@ -5,38 +5,43 @@ import 'package:localsend_app/util/native/platform_check.dart';
final _borderRadius = BorderRadius.circular(5);
final _lightInputColor = Color.lerp(Colors.teal.shade100, Colors.white, 0.4)!;
final _lightInputBorder = OutlineInputBorder(
borderSide: BorderSide(color: _lightInputColor),
borderRadius: _borderRadius,
);
ThemeData getTheme(Brightness brightness, ColorScheme? colorScheme) {
colorScheme ??= ColorScheme.fromSwatch(
primarySwatch: Colors.teal,
brightness: brightness,
backgroundColor: brightness == Brightness.light ? Colors.white : Colors.grey.shade900,
).copyWith(
// overrides for LocalSend theme
onError: Colors.white,
secondaryContainer:
brightness == Brightness.light ? Color.lerp(Colors.teal.shade100, Colors.white, 0.2) : Color.lerp(Colors.teal.shade100, Colors.black, 0.6),
onSecondaryContainer: brightness == Brightness.light ? Colors.black : Colors.white,
);
final _darkInputColor = Color.lerp(Colors.teal.shade100, Colors.black, 0.6)!;
final _darkInputBorder = OutlineInputBorder(
borderSide: BorderSide(color: _darkInputColor),
borderRadius: _borderRadius,
);
final lightInputBorder = OutlineInputBorder(
borderSide: BorderSide(color: colorScheme.secondaryContainer),
borderRadius: _borderRadius,
);
final darkInputBorder = OutlineInputBorder(
borderSide: BorderSide(color: colorScheme.secondaryContainer),
borderRadius: _borderRadius,
);
ThemeData getTheme(Brightness brightness) {
return ThemeData(
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.teal,
brightness: brightness,
backgroundColor: brightness == Brightness.light ? Colors.white : Colors.grey.shade900,
),
colorScheme: colorScheme,
useMaterial3: true,
navigationBarTheme: brightness == Brightness.dark
? NavigationBarThemeData(
indicatorColor: Colors.teal,
iconTheme: MaterialStateProperty.all(const IconThemeData(color: Colors.white)),
)
: null,
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: brightness == Brightness.light ? _lightInputColor : _darkInputColor,
border: brightness == Brightness.light ? _lightInputBorder : _darkInputBorder,
focusedBorder: brightness == Brightness.light ? _lightInputBorder : _darkInputBorder,
enabledBorder: brightness == Brightness.light ? _lightInputBorder : _darkInputBorder,
fillColor: colorScheme.secondaryContainer,
border: brightness == Brightness.light ? lightInputBorder : darkInputBorder,
focusedBorder: brightness == Brightness.light ? lightInputBorder : darkInputBorder,
enabledBorder: brightness == Brightness.light ? lightInputBorder : darkInputBorder,
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
),
elevatedButtonTheme: ElevatedButtonThemeData(
+1 -1
View File
@@ -13,7 +13,7 @@ class CustomProgressBar extends StatelessWidget {
borderRadius: BorderRadius.circular(borderRadius),
child: LinearProgressIndicator(
value: progress,
color: color,
color: color ?? Theme.of(context).colorScheme.primary,
minHeight: 10,
),
);
@@ -185,9 +185,6 @@ class _AddressInputDialogState extends ConsumerState<AddressInputDialog> {
),
actions: [
TextButton(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).brightness == Brightness.dark ? Theme.of(context).buttonTheme.colorScheme!.onPrimary : null,
),
onPressed: () => context.pop(),
child: Text(t.general.cancel),
),
@@ -55,9 +55,6 @@ class _FileNameInputDialogState extends State<FileNameInputDialog> {
),
actions: [
TextButton(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).brightness == Brightness.dark ? Theme.of(context).buttonTheme.colorScheme!.onPrimary : null,
),
onPressed: () => context.pop(),
child: Text(t.general.cancel),
),
@@ -55,9 +55,6 @@ class _MessageInputDialogState extends State<MessageInputDialog> {
),
actions: [
TextButton(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).brightness == Brightness.dark ? Theme.of(context).buttonTheme.colorScheme!.onPrimary : null,
),
onPressed: () => context.pop(),
child: Text(t.general.cancel),
),
@@ -107,9 +107,6 @@ class _QuickActionsDialogState extends ConsumerState<QuickActionsDialog> {
),
actions: [
TextButton(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).brightness == Brightness.dark ? Theme.of(context).buttonTheme.colorScheme!.onPrimary : null,
),
onPressed: () => context.pop(),
child: Text(t.general.cancel),
),
+3 -6
View File
@@ -27,19 +27,16 @@ class DeviceListTile extends StatelessWidget {
else if (progress != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: CustomProgressBar(
progress: progress!,
color: Theme.of(context).colorScheme.tertiaryContainer,
),
child: CustomProgressBar(progress: progress!),
)
else ...[
DeviceBadge(
color: Theme.of(context).colorScheme.tertiaryContainer,
color: Theme.of(context).colorScheme.onSecondaryContainer,
label: '#${device.ip.visualId}',
),
if (device.deviceModel != null)
DeviceBadge(
color: Theme.of(context).colorScheme.tertiaryContainer,
color: Theme.of(context).colorScheme.onSecondaryContainer,
label: device.deviceModel!,
),
],
@@ -29,11 +29,11 @@ class DevicePlaceholderListTile extends StatelessWidget {
spacing: 10,
children: [
DeviceBadge(
color: Theme.of(context).colorScheme.tertiaryContainer.withOpacity(0.5),
color: Theme.of(context).colorScheme.onSecondaryContainer.withOpacity(0.5),
label: ' ',
),
DeviceBadge(
color: Theme.of(context).colorScheme.tertiaryContainer.withOpacity(0.5),
color: Theme.of(context).colorScheme.onSecondaryContainer.withOpacity(0.5),
label: ' ',
),
],
+27 -13
View File
@@ -2,22 +2,36 @@ import 'package:flutter/material.dart';
import 'package:localsend_app/gen/assets.gen.dart';
class LocalSendLogo extends StatelessWidget {
const LocalSendLogo({Key? key}) : super(key: key);
final bool withText;
const LocalSendLogo({required this.withText});
@override
Widget build(BuildContext context) {
return Column(
children: [
Assets.img.logo512.image(
width: 200,
height: 200,
),
const Text(
'LocalSend',
style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
],
final logo = ColorFiltered(
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary,
BlendMode.srcATop,
),
child: Assets.img.logo512.image(
width: 200,
height: 200,
),
);
if (withText) {
return Column(
children: [
logo,
const Text(
'LocalSend',
style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
],
);
} else {
return logo;
}
}
}
@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <dynamic_color/dynamic_color_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <system_tray/system_tray_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
@@ -16,6 +17,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
+1
View File
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
dynamic_color
screen_retriever
system_tray
url_launcher_linux
@@ -8,6 +8,7 @@ import Foundation
import connectivity_plus
import desktop_drop
import device_info_plus
import dynamic_color
import network_info_plus
import package_info_plus
import path_provider_foundation
@@ -24,6 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
+35 -14
View File
@@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.dev"
source: hosted
version: "2.11.0"
version: "2.10.0"
basic_utils:
dependency: "direct main"
description:
@@ -353,6 +353,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.1"
dynamic_color:
dependency: "direct main"
description:
name: dynamic_color
sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1"
url: "https://pub.dev"
source: hosted
version: "1.6.5"
extended_image:
dependency: transitive
description:
@@ -369,6 +377,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.4.2"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
@@ -459,6 +475,11 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.6"
flutter_test:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -708,10 +729,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.13"
material_color_utilities:
dependency: transitive
description:
@@ -1266,10 +1287,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.9.1"
stack_trace:
dependency: transitive
description:
@@ -1338,26 +1359,26 @@ packages:
dependency: "direct dev"
description:
name: test
sha256: "4f92f103ef63b1bbac6f4bd1930624fca81b2574464482512c4f0896319be575"
sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d
url: "https://pub.dev"
source: hosted
version: "1.24.2"
version: "1.22.0"
test_api:
dependency: transitive
description:
name: test_api
sha256: daadc9baabec998b062c9091525aa95786508b1c48e9c30f1f891b8bf6ff2e64
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev"
source: hosted
version: "0.5.2"
version: "0.4.16"
test_core:
dependency: transitive
description:
name: test_core
sha256: "3642b184882f79e76ca57a9230fb971e494c3c1fd09c21ae3083ce891bcc0aa1"
sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888"
url: "https://pub.dev"
source: hosted
version: "0.5.2"
version: "0.4.20"
time:
dependency: transitive
description:
@@ -1515,10 +1536,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: d1ba6ce3fa60807433511f943b51607bd7073f8fe5c14286d66fec8e05c8d24c
sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7
url: "https://pub.dev"
source: hosted
version: "11.5.0"
version: "9.4.0"
wakelock:
dependency: "direct main"
description:
+2 -1
View File
@@ -19,6 +19,7 @@ dependencies:
device_apps: 2.2.0
device_info_plus: 8.2.2
dio: 5.1.1
dynamic_color: 1.6.5
file_picker: 5.2.9
flutter:
sdk: flutter
@@ -72,7 +73,7 @@ dev_dependencies:
msix: 3.11.1
riverpod_lint: 1.3.1
slang_build_runner: 3.17.0
test: 1.24.2
test: 1.22.0
dependency_overrides:
share_handler_platform_interface:
@@ -8,6 +8,7 @@
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <network_info_plus/network_info_plus_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
@@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DesktopDropPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
NetworkInfoPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("NetworkInfoPlusWindowsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
+1
View File
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
desktop_drop
dynamic_color
network_info_plus
permission_handler_windows
screen_retriever