Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Implement custom primary color #106

Merged
merged 19 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion assets/l10n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"general.delete.all": "Delete all",
"general.copy.success": "Copied to clipboard",
"general.copy.clickToCopy": "Click to copy",
"general.confirm.okay": "Okay",

"setup.getStarted": "Get started",
"setup.next": "Next",
Expand Down Expand Up @@ -111,6 +112,9 @@
"preferences.themeMode.light": "Light",
"preferences.themeMode.dark": "Dark",
"preferences.themeMode.system": "Auto (system)",
"preferences.theme.setPrimaryColor": "Set Primary Color",
"preferences.theme.setPrimaryColorDialog.title": "Flow has to be restarted in order to apply the changes.",
"preferences.theme.setPrimaryColorDialog.content": "Flow has to be restarted in order to apply the changes.",
"preferences.numpad": "Numpad",
"preferences.numpad.layout": "Numpad layout",
"preferences.numpad.layout.classic": "Classic",
Expand Down Expand Up @@ -269,5 +273,6 @@
"error.sync.invalidBackupFile": "Invalid backup file",
"error.sync.safetyBackupFailed": "Unable to start import",
"error.sync.exportFailed": "Unable to export, please contact developer.",
"error.transaction.missingAccount": "Please select an account"
"error.transaction.missingAccount": "Please select an account",
"error.preferences.unknownColor": "Unknown color"
}
7 changes: 6 additions & 1 deletion assets/l10n/mn_MN.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"general.delete.all": "Бүгдийг устгах",
"general.copy.success": "Амжилттай хууллаа",
"general.copy.clickToCopy": "Дарвал бичвэр хуулна",
"general.confirm.okay": "Зөв",

"setup.getStarted": "Эхэлцгээе",
"setup.next": "Үргэлжлүүлэх",
Expand Down Expand Up @@ -111,6 +112,9 @@
"preferences.themeMode.light": "Гэгээлэг",
"preferences.themeMode.dark": "Харанхуй",
"preferences.themeMode.system": "Авто (систем)",
"preferences.theme.setPrimaryColor": "Үндсэн өнгө оноох",
"preferences.theme.setPrimaryColorDialog.title": "Өөрийгөө өөрчлөлтийг хэрэглэхийн тулд Flow-г дахин эхлүүлж давтах ёстой.",
"preferences.theme.setPrimaryColorDialog.content": "Өөрийгөө өөрчлөлтийг хэрэглэхийн тулд Flow-г дахин эхлүүлж давтах ёстой.",
"preferences.numpad": "Тоон товчлуур",
"preferences.numpad.layout": "Тооны байрлал",
"preferences.numpad.layout.classic": "Хуучны",
Expand Down Expand Up @@ -269,5 +273,6 @@
"error.sync.invalidBackupFile": "Нөөц файл алдаатай байна",
"error.sync.safetyBackupFailed": "Сэргээх үйлдэл эхлэх боломжгүй",
"error.sync.exportFailed": "Нөөцлөх явцад алдаа гарлаа, хөгжүүлэгчид хандана уу.",
"error.transaction.missingAccount": "Гүйлгээ хийх данс сонгоно уу"
"error.transaction.missingAccount": "Гүйлгээ хийх данс сонгоно уу",
"error.preferences.unknownColor": "Тодорхойгүй өнгө"
}
61 changes: 58 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import 'package:flow/objectbox.dart';
import 'package:flow/objectbox/actions.dart';
import 'package:flow/prefs.dart';
import 'package:flow/routes.dart';
import 'package:flow/theme/navbar_theme.dart';
import 'package:flow/theme/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
Expand Down Expand Up @@ -74,13 +75,25 @@ class FlowState extends State<Flow> {
return useDarkTheme ? pieThemeDark : pieThemeLight;
}

Color? primaryColorFromPrefs = Colors.white;

@override
void initState() {
super.initState();

_reloadLocale();
_reloadTheme();

_checkPrefsForPrimaryColor().then((color) {
setState(() {
if (color != null) {
primaryColorFromPrefs = color;
} else {
primaryColorFromPrefs = context.colorScheme.primary;
}
});
});

LocalPreferences().localeOverride.addListener(_reloadLocale);
LocalPreferences().themeMode.addListener(_reloadTheme);

Expand Down Expand Up @@ -111,11 +124,14 @@ class FlowState extends State<Flow> {
GlobalWidgetsLocalizations.delegate,
FlowLocalizations.delegate,
],
supportedLocales: FlowLocalizations.supportedLanguages,
locale: LocalPreferences().localeOverride.value,
routerConfig: router,
theme: lightTheme,
darkTheme: darkTheme,
theme: _overrideThemeWithPrimaryColor(lightTheme, primaryColorFromPrefs,
isLightMode: true),
darkTheme:
_overrideThemeWithPrimaryColor(darkTheme, primaryColorFromPrefs),
// theme: lightTheme,
// darkTheme: darkTheme,
themeMode: _themeMode,
debugShowCheckedModeBanner: false,
);
Expand All @@ -136,3 +152,42 @@ class FlowState extends State<Flow> {
setState(() {});
}
}

ThemeData _overrideThemeWithPrimaryColor(ThemeData theme, Color? primaryColor,
{bool isLightMode = false}) {
return theme.copyWith(
colorScheme: theme.colorScheme.copyWith(primary: primaryColor),
listTileTheme: theme.listTileTheme.copyWith(iconColor: primaryColor),
extensions: _mapExtensions(theme, primaryColor),
textSelectionTheme: theme.textSelectionTheme.copyWith(
cursorColor: primaryColor,
selectionColor: primaryColor,
selectionHandleColor: primaryColor,
),
);
}

Iterable<ThemeExtension<dynamic>> _mapExtensions(
ThemeData theme, Color? primaryColor) {
return theme.extensions.entries.map((entry) {
if (entry.value is NavbarTheme) {
var navbarTheme = entry.value as NavbarTheme;
return NavbarTheme(
backgroundColor: theme.colorScheme.onSurface,
activeIconColor: primaryColor ?? navbarTheme.activeIconColor,
inactiveIconOpacity: 0.5,
transactionButtonBackgroundColor:
primaryColor ?? navbarTheme.transactionButtonBackgroundColor,
transactionButtonForegroundColor:
navbarTheme.transactionButtonForegroundColor,
);
} else {
return entry.value;
}
});
}

Future<Color?> _checkPrefsForPrimaryColor() async {
final prefs = LocalPreferences();
return await prefs.getPrimaryColor();
}
25 changes: 25 additions & 0 deletions lib/prefs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,31 @@ class LocalPreferences {
}
}

Future<bool> setPrimaryColor(Color? color) async {
const prefixKey = "flow.primaryColor";

if (color == null) {
return await _prefs.remove(prefixKey);
} else {
final colorValue = color.value.toRadixString(16);
return await _prefs.setString(
prefixKey,
colorValue.toString(),
);
}
}

Future<Color?> getPrimaryColor() async {
const prefixKey = "flow.primaryColor";
final String? colorString = _prefs.getString(prefixKey);

if (colorString == null) {
return null;
}

return Color(int.parse(colorString, radix: 16) + 0xFF000000);
}

String getPrimaryCurrency() {
String? primaryCurrencyName = primaryCurrency.value;

Expand Down
108 changes: 108 additions & 0 deletions lib/routes/preferences_page.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:flex_color_picker/flex_color_picker.dart';
import 'package:flow/l10n/flow_localizations.dart';
import 'package:flow/main.dart';
import 'package:flow/prefs.dart';
import 'package:flow/routes/preferences/language_selection_sheet.dart';
import 'package:flow/theme/theme.dart';
import 'package:flow/widgets/select_currency_sheet.dart';
import 'package:flutter/material.dart' hide Flow;
import 'package:colornames/colornames.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';

Expand Down Expand Up @@ -48,6 +50,13 @@ class _PreferencesPageState extends State<PreferencesPage> {
onLongPress: () => updateTheme(ThemeMode.system),
trailing: const Icon(Symbols.chevron_right_rounded),
),
ListTile(
title: Text("preferences.theme.setPrimaryColor".t(context)),
leading: const Icon(Icons.color_lens),
trailing: const Icon(Icons.chevron_right),
subtitle: _buildCurrentPrimaryThemeName(context),
onTap: () => updatePrimaryColor(context),
),
ListTile(
title: Text("preferences.language".t(context)),
leading: const Icon(Symbols.language_rounded),
Expand Down Expand Up @@ -92,6 +101,18 @@ class _PreferencesPageState extends State<PreferencesPage> {
);
}

Widget _buildCurrentPrimaryThemeName(BuildContext context) {
try {
final Color primaryColor = Theme.of(context).colorScheme.primary;

final primaryColorName = ColorNames.guess(primaryColor);

return Text(primaryColorName);
} catch (e) {
return Text("error.preferences.unknownColor".t(context));
}
}

void updateTheme([ThemeMode? force]) async {
if (_themeBusy) return;

Expand Down Expand Up @@ -147,6 +168,93 @@ class _PreferencesPageState extends State<PreferencesPage> {
}
}

void updatePrimaryColor(BuildContext context) async {
if (_themeBusy) return;

setState(() {
_themeBusy = true;
});

try {
final selected = await openColorPickerDialog(context);
if (selected) {
_openConfirmDialog();
}
} finally {
_themeBusy = false;
}
}

void _openConfirmDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("preferences.theme.setPrimaryColorDialog.title".t(context)),
content:
Text("preferences.theme.setPrimaryColorDialog.content".t(context)),
// Text("".t(context)),
// content:
// Text("preferences.primaryColor.confirmDialogMessage".t(context)),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("general.confirm.okay",
style: Theme.of(context).textTheme.titleSmall),
),
],
),
);
}

Future<bool> openColorPickerDialog(BuildContext context) async {
return ColorPicker(
// Use the dialogPickerColor as start color.
color: context.colorScheme.primary,
// Update the dialogPickerColor using the callback.
onColorChanged: (Color color) async {
await LocalPreferences().setPrimaryColor(color);
},
width: 40,
height: 40,
borderRadius: 4,
spacing: 5,
runSpacing: 5,
wheelDiameter: 155,
showMaterialName: true,
showColorName: true,
showColorCode: true,
copyPasteBehavior: const ColorPickerCopyPasteBehavior(
longPressMenu: true,
copyButton: true,
pasteButton: true,
),
materialNameTextStyle: Theme.of(context).textTheme.bodySmall,
colorNameTextStyle: Theme.of(context).textTheme.bodySmall,
colorCodeTextStyle: Theme.of(context).textTheme.bodySmall,
pickersEnabled: const <ColorPickerType, bool>{
ColorPickerType.wheel: true,
ColorPickerType.accent: false,
},
).showPickerDialog(
context,
transitionBuilder: (BuildContext context, Animation<double> a1,
Animation<double> a2, Widget widget) {
final double curvedValue =
Curves.easeInOutBack.transform(a1.value) - 1.0;
return Transform(
transform: Matrix4.translationValues(0.0, curvedValue * 200, 0.0),
child: Opacity(
opacity: a1.value,
child: widget,
),
);
},
transitionDuration: const Duration(milliseconds: 400),
constraints:
const BoxConstraints(minHeight: 460, minWidth: 300, maxWidth: 320),
);
}

void updatePrimaryCurrency() async {
if (_currencyBusy) return;

Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/general/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class Button extends StatelessWidget {

return Surface(
shape: RoundedRectangleBorder(borderRadius: borderRadius),
// color: backgroundColor ?? context.colorScheme.primary,
color: backgroundColor ?? context.colorScheme.primary,
builder: (context) => InkWell(
onTap: onTap,
onLongPress: onLongPress,
Expand Down
24 changes: 24 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
colornames:
dependency: "direct main"
description:
name: colornames
sha256: c42ee28edfc65391d7cdb40577c57390e1ea2ff78d73a9983a452a72dacfbdbc
url: "https://pub.dev"
source: hosted
version: "0.1.0"
convert:
dependency: transitive
description:
Expand Down Expand Up @@ -345,6 +353,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.5"
flex_color_picker:
dependency: "direct main"
description:
name: flex_color_picker
sha256: "5c846437069fb7afdd7ade6bf37e628a71d2ab0787095ddcb1253bf9345d5f3a"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
flex_seed_scheme:
dependency: transitive
description:
name: flex_seed_scheme
sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
flutter:
dependency: "direct main"
description: flutter
Expand Down
4 changes: 3 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ description: A personal finance managing app

publish_to: "none" # Remove this line if you wish to publish to pub.dev

version: "0.3.3+35"
version: "0.3.4+0"

environment:
sdk: ">=3.1.3 <4.0.0"

dependencies:
auto_size_text: ^3.0.0
colornames: ^0.1.0
crop_image: ^1.0.12
cross_file: ^0.3.3+8
csv: ^5.1.1
cupertino_icons: ^1.0.6
desktop_drop: ^0.4.4
file_picker: ^6.1.1
file_saver: ^0.2.9
flex_color_picker: ^3.4.1
fl_chart: ^0.66.2
flutter:
sdk: flutter
Expand Down