mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 06:24:07 +00:00
make it possible to change the primary color
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
c2607a99e3
commit
70ed3759d2
23 changed files with 270 additions and 50 deletions
20
lib/app.dart
20
lib/app.dart
|
|
@ -89,15 +89,17 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
Locale('de', ''),
|
||||
];
|
||||
|
||||
final settings = context.watch<SettingsChangeProvider>();
|
||||
|
||||
if (widget.storageError) {
|
||||
return MaterialApp(
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
home: const CriticalErrorView(),
|
||||
);
|
||||
}
|
||||
|
|
@ -108,9 +110,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
home: const RecoveryView(),
|
||||
);
|
||||
}
|
||||
|
|
@ -121,9 +123,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4405,6 +4405,30 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Brightness'**
|
||||
String get brightness;
|
||||
|
||||
/// No description provided for @settingsAppearancePrimaryColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Primary Color'**
|
||||
String get settingsAppearancePrimaryColor;
|
||||
|
||||
/// No description provided for @themeSystemDefault.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'System default'**
|
||||
String get themeSystemDefault;
|
||||
|
||||
/// No description provided for @themeLight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Light'**
|
||||
String get themeLight;
|
||||
|
||||
/// No description provided for @themeDark.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dark'**
|
||||
String get themeDark;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -2553,4 +2553,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get brightness => 'Helligkeit';
|
||||
|
||||
@override
|
||||
String get settingsAppearancePrimaryColor => 'Hauptfarbe';
|
||||
|
||||
@override
|
||||
String get themeSystemDefault => 'Systemstandard';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Hell';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Dunkel';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2526,4 +2526,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get brightness => 'Brightness';
|
||||
|
||||
@override
|
||||
String get settingsAppearancePrimaryColor => 'Primary Color';
|
||||
|
||||
@override
|
||||
String get themeSystemDefault => 'System default';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Light';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Dark';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 2f37f6cd8ebd07dbc113bab0d5816bd37fb4fca6
|
||||
Subproject commit 5feb6f49653741ec37c49e57b3b6c7c178ed4427
|
||||
|
|
@ -57,6 +57,12 @@ class UserData {
|
|||
@JsonKey(defaultValue: ThemeMode.system)
|
||||
ThemeMode themeMode = ThemeMode.system;
|
||||
|
||||
int? primaryColorValue;
|
||||
|
||||
Color get primaryColor => primaryColorValue != null
|
||||
? Color(primaryColorValue!)
|
||||
: const Color(0xFF57CC99);
|
||||
|
||||
int? defaultShowTime;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..themeMode =
|
||||
$enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
||||
ThemeMode.system
|
||||
..primaryColorValue = (json['primaryColorValue'] as num?)?.toInt()
|
||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||
..requestedAudioPermission =
|
||||
json['requestedAudioPermission'] as bool? ?? false
|
||||
|
|
@ -137,6 +138,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'lastPlanBallance': instance.lastPlanBallance,
|
||||
'additionalUserInvites': instance.additionalUserInvites,
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'primaryColorValue': instance.primaryColorValue,
|
||||
'defaultShowTime': instance.defaultShowTime,
|
||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||
'enableDatabaseLogging': instance.enableDatabaseLogging,
|
||||
|
|
|
|||
|
|
@ -2,18 +2,23 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
late ThemeMode _themeMode;
|
||||
late Color _primaryColor;
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
Color get primaryColor => _primaryColor;
|
||||
|
||||
void loadSettings() {
|
||||
if (userService.isUserCreated) {
|
||||
_themeMode = userService.currentUser.themeMode;
|
||||
_primaryColor = userService.currentUser.primaryColor;
|
||||
notifyListeners();
|
||||
} else {
|
||||
_themeMode = ThemeMode.system;
|
||||
_primaryColor = defaultPrimaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -28,4 +33,16 @@ class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|||
|
||||
await UserService.update((u) => u.themeMode = newThemeMode);
|
||||
}
|
||||
|
||||
Future<void> updatePrimaryColor(Color newColor) async {
|
||||
if (newColor.toARGB32() == _primaryColor.toARGB32()) return;
|
||||
|
||||
_primaryColor = newColor;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
await UserService.update(
|
||||
(u) => u.primaryColorValue = newColor.toARGB32(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class ContactRequestBadgeComp extends StatelessWidget {
|
||||
const ContactRequestBadgeComp({super.key});
|
||||
|
|
@ -26,8 +25,8 @@ class ContactRequestBadgeComp extends StatelessWidget {
|
|||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: primaryColor,
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class SelectableThumbnailComp extends StatelessWidget {
|
||||
const SelectableThumbnailComp({
|
||||
|
|
@ -19,7 +19,7 @@ class SelectableThumbnailComp extends StatelessWidget {
|
|||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.transparent,
|
||||
color: isSelected ? context.color.primary : Colors.transparent,
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
|
|
@ -49,7 +49,7 @@ class SelectableThumbnailComp extends StatelessWidget {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.black38,
|
||||
color: isSelected ? context.color.primary : Colors.black38,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
const colorVerificationBadgeYellow = Color.fromARGB(255, 0, 182, 238);
|
||||
|
||||
|
|
@ -90,7 +89,7 @@ class VerificationBadgeInfo extends StatelessWidget {
|
|||
context,
|
||||
icon: const SvgIcon(assetPath: SvgIcons.verifiedGreen, size: 40),
|
||||
description: context.lang.verificationBadgeGreenDesc,
|
||||
boldTextColor: primaryColor,
|
||||
boldTextColor: context.color.primary,
|
||||
onTap: () => context.push(
|
||||
Routes.cameraQRScanner,
|
||||
extra: {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class VerificationSuccessAnimationState
|
|||
'<svg viewBox="0 0 640 640"><path d="$_path2" fill="white"/></svg>';
|
||||
|
||||
static const _grey = Color(0xFF8E9AAF);
|
||||
static const Color _green = primaryColor;
|
||||
static const Color _green = defaultPrimaryColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/reactive_tap_feedback.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
enum MyButtonVariant {
|
||||
primary,
|
||||
|
|
@ -49,7 +48,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
switch (widget.variant) {
|
||||
case MyButtonVariant.primary:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
|
|
@ -96,7 +95,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.primaryMiddle:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
|
|
@ -115,7 +114,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.primaryDense:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/reactive_tap_feedback.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
enum MyIconButtonVariant {
|
||||
primary,
|
||||
|
|
@ -43,7 +42,7 @@ class _MyIconButtonState extends State<MyIconButton> {
|
|||
late final Color fgColor;
|
||||
|
||||
if (widget.variant == MyIconButtonVariant.primary) {
|
||||
bgColor = primaryColor;
|
||||
bgColor = context.color.primary;
|
||||
fgColor = Colors.black87;
|
||||
} else {
|
||||
bgColor = isDark ? Colors.grey[800]! : Colors.grey[200]!;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
final ThemeData darkTheme = () {
|
||||
ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) {
|
||||
final base = ThemeData.dark().copyWith(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
brightness: Brightness.dark,
|
||||
seedColor: const Color(0xFF57CC99),
|
||||
seedColor: primary,
|
||||
primary: primary,
|
||||
surface: const Color.fromARGB(255, 20, 18, 23),
|
||||
surfaceContainer: const Color.fromARGB(255, 45, 41, 54),
|
||||
surfaceContainerLow: const Color.fromARGB(255, 38, 34, 45),
|
||||
|
|
@ -21,4 +23,6 @@ final ThemeData darkTheme = () {
|
|||
fontFamilyFallback: Platform.isAndroid ? const ['NotoColorEmoji'] : null,
|
||||
),
|
||||
);
|
||||
}();
|
||||
}
|
||||
|
||||
final ThemeData darkTheme = getDarkTheme();
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
const primaryColor = Color(0xFF57CC99);
|
||||
const defaultPrimaryColor = Color(0xFF57CC99);
|
||||
|
||||
final ThemeData lightTheme = () {
|
||||
ThemeData getLightTheme([Color primary = defaultPrimaryColor]) {
|
||||
final base = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
seedColor: primary,
|
||||
primary: primary,
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
|
|
@ -19,10 +20,12 @@ final ThemeData lightTheme = () {
|
|||
fontFamilyFallback: Platform.isAndroid ? const ['NotoColorEmoji'] : null,
|
||||
),
|
||||
);
|
||||
}();
|
||||
}
|
||||
|
||||
final ThemeData lightTheme = getLightTheme();
|
||||
|
||||
final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: defaultPrimaryColor,
|
||||
foregroundColor: Colors.black87,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/connection_status.comp.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/empty_chat_list.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/group_list_item.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/news_btn.comp.dart';
|
||||
|
|
@ -196,8 +195,8 @@ class _ChatListViewState extends State<ChatListView> with AutomaticKeepAliveClie
|
|||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: primaryColor,
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
@ -329,7 +328,7 @@ class _ChatListViewState extends State<ChatListView> with AutomaticKeepAliveClie
|
|||
FloatingActionButton(
|
||||
heroTag: 'new_chat_fab',
|
||||
elevation: 2,
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
onPressed: () => context.push(Routes.chatsStartNewChat),
|
||||
child: const FaIcon(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class NewsIconButtonComp extends StatelessWidget {
|
||||
const NewsIconButtonComp({super.key});
|
||||
|
|
@ -23,7 +22,7 @@ class NewsIconButtonComp extends StatelessWidget {
|
|||
builder: (context, count, child) {
|
||||
return NotificationBadgeComp(
|
||||
count: count.toString(),
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
textColor: Colors.black87,
|
||||
child: IconButton(
|
||||
onPressed: () => context.push(Routes.settingsHelpNews),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:twonly/src/visual/components/avatar_icon.comp.dart'
|
|||
show AvatarIcon;
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/elements/my_input.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart';
|
||||
|
||||
class ProfileSetupPage extends StatefulWidget {
|
||||
|
|
@ -67,7 +66,7 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
|
|||
foregroundDecoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: primaryColor,
|
||||
color: context.color.primary,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/custom_color_picker_dialog.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/radio_button.element.dart';
|
||||
|
||||
class AppearanceView extends StatefulWidget {
|
||||
|
|
@ -36,7 +37,7 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
RadioButton<ThemeMode>(
|
||||
value: ThemeMode.system,
|
||||
groupValue: selectedValue,
|
||||
label: 'System default',
|
||||
label: context.lang.themeSystemDefault,
|
||||
onChanged: (value) {
|
||||
selectedValue = value;
|
||||
Navigator.of(context).pop();
|
||||
|
|
@ -45,7 +46,7 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
RadioButton<ThemeMode>(
|
||||
value: ThemeMode.light,
|
||||
groupValue: selectedValue,
|
||||
label: 'Light',
|
||||
label: context.lang.themeLight,
|
||||
onChanged: (value) {
|
||||
selectedValue = value;
|
||||
Navigator.of(context).pop();
|
||||
|
|
@ -54,7 +55,7 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
RadioButton<ThemeMode>(
|
||||
value: ThemeMode.dark,
|
||||
groupValue: selectedValue,
|
||||
label: 'Dark',
|
||||
label: context.lang.themeDark,
|
||||
onChanged: (value) {
|
||||
selectedValue = value;
|
||||
Navigator.of(context).pop();
|
||||
|
|
@ -72,6 +73,124 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _showSelectPrimaryColor(BuildContext context) async {
|
||||
const presetColors = <Color>[
|
||||
Color(0xFF57CC99), // Original Twonly Green
|
||||
Color(0xFF3A76F0), // Signal Blue
|
||||
Color(0xFF9D4EDD), // Purple
|
||||
Color(0xFFFF5964), // Coral Red
|
||||
Color(0xFFFF9F1C), // Amber Orange
|
||||
];
|
||||
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
builder: (context) {
|
||||
final activeColor =
|
||||
context.watch<SettingsChangeProvider>().primaryColor;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 8, 24, 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.settingsAppearancePrimaryColor,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: presetColors.map((color) {
|
||||
final isSelected =
|
||||
activeColor.toARGB32() == color.toARGB32();
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await context
|
||||
.read<SettingsChangeProvider>()
|
||||
.updatePrimaryColor(color);
|
||||
},
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withValues(alpha: 0.4),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color:
|
||||
isSelected ? Colors.white : Colors.transparent,
|
||||
width: 3,
|
||||
),
|
||||
),
|
||||
child: isSelected
|
||||
? const Icon(
|
||||
Icons.check,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const SweepGradient(
|
||||
colors: [
|
||||
Colors.red,
|
||||
Colors.yellow,
|
||||
Colors.green,
|
||||
Colors.cyan,
|
||||
Colors.blue,
|
||||
Color(0xFFFF00FF),
|
||||
Colors.red,
|
||||
],
|
||||
),
|
||||
border: Border.all(color: Colors.white24, width: 2),
|
||||
),
|
||||
),
|
||||
title: Text(context.lang.customColor),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
final pickedValue = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => CustomColorPickerDialog(
|
||||
initialColor: activeColor,
|
||||
),
|
||||
);
|
||||
if (pickedValue != null && context.mounted) {
|
||||
final pickedColor = Color(pickedValue);
|
||||
await context
|
||||
.read<SettingsChangeProvider>()
|
||||
.updatePrimaryColor(pickedColor);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> toggleShowNewsIcon() async {
|
||||
await UserService.update((u) {
|
||||
u.showNewsShortcut = !u.showNewsShortcut;
|
||||
|
|
@ -90,9 +209,22 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
});
|
||||
}
|
||||
|
||||
String _themeModeLabel(BuildContext context, ThemeMode mode) {
|
||||
switch (mode) {
|
||||
case ThemeMode.system:
|
||||
return context.lang.themeSystemDefault;
|
||||
case ThemeMode.light:
|
||||
return context.lang.themeLight;
|
||||
case ThemeMode.dark:
|
||||
return context.lang.themeDark;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedTheme = context.watch<SettingsChangeProvider>().themeMode;
|
||||
final primaryColor = context.watch<SettingsChangeProvider>().primaryColor;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.settingsAppearance),
|
||||
|
|
@ -105,13 +237,28 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
ListTile(
|
||||
title: Text(context.lang.settingsAppearanceTheme),
|
||||
subtitle: Text(
|
||||
selectedTheme.name,
|
||||
_themeModeLabel(context, selectedTheme),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
onTap: () async {
|
||||
await _showSelectThemeMode(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.lang.settingsAppearancePrimaryColor),
|
||||
trailing: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 1.5),
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
await _showSelectPrimaryColor(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.lang.hideNewsIcon),
|
||||
onTap: toggleShowNewsIcon,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'package:flutter/services.dart';
|
|||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/my_input.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class _FactorOption {
|
||||
const _FactorOption({
|
||||
|
|
@ -113,7 +112,7 @@ class SecondFactorPicker extends StatelessWidget {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.transparent,
|
||||
color: isSelected ? context.color.primary : Colors.transparent,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Column(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class ThresholdPicker extends StatelessWidget {
|
||||
const ThresholdPicker({
|
||||
|
|
@ -103,7 +102,7 @@ class ThresholdPicker extends StatelessWidget {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.transparent,
|
||||
color: isSelected ? context.color.primary : Colors.transparent,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Center(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
|||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.4.3+164
|
||||
version: 0.4.3+165
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.0
|
||||
|
|
|
|||
Loading…
Reference in a new issue