added oled theme and theme presets
Android CI / Build and Test (pull_request) Failing after 11m39s
Flutter / build (BalanceFlutter/build/ios/iphoneos/*.app, macos-latest, ios) (push) Canceled after 0s
Flutter / build (BalanceFlutter/build/macos/Build/Products/Release/*.app, macos-latest, macos) (push) Canceled after 0s
Flutter / build (BalanceFlutter/build/windows/x64/runner/Release, windows-latest, windows) (push) Canceled after 0s
Flutter / watchos (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
Flutter / checks (push) Successful in 25m8s
Flutter / build (BalanceFlutter/build/app/outputs/flutter-apk/*.apk, ubuntu-latest, android) (push) Failing after 38m6s
Flutter / build (BalanceFlutter/build/linux/x64/release/bundle, ubuntu-latest, linux) (push) Successful in 32m37s
Flutter / build (BalanceFlutter/build/web, ubuntu-latest, web) (push) Successful in 31m39s
iOS CI / Build and Test SwiftUI App (pull_request) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (pull_request) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (pull_request) Canceled after 0s
Flutter / build (BalanceFlutter/build/ios/iphoneos/*.app, macos-latest, ios) (pull_request) Canceled after 0s
Flutter / build (BalanceFlutter/build/macos/Build/Products/Release/*.app, macos-latest, macos) (pull_request) Canceled after 0s
Flutter / build (BalanceFlutter/build/windows/x64/runner/Release, windows-latest, windows) (pull_request) Canceled after 0s
Flutter / watchos (pull_request) Canceled after 0s
Flutter / checks (pull_request) Successful in 24m59s
Flutter / build (BalanceFlutter/build/app/outputs/flutter-apk/*.apk, ubuntu-latest, android) (pull_request) Failing after 31m24s
Flutter / build (BalanceFlutter/build/linux/x64/release/bundle, ubuntu-latest, linux) (pull_request) Successful in 32m34s
Flutter / build (BalanceFlutter/build/web, ubuntu-latest, web) (pull_request) Successful in 24m54s
Android CI / Build and Test (pull_request) Failing after 11m39s
Flutter / build (BalanceFlutter/build/ios/iphoneos/*.app, macos-latest, ios) (push) Canceled after 0s
Flutter / build (BalanceFlutter/build/macos/Build/Products/Release/*.app, macos-latest, macos) (push) Canceled after 0s
Flutter / build (BalanceFlutter/build/windows/x64/runner/Release, windows-latest, windows) (push) Canceled after 0s
Flutter / watchos (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
Flutter / checks (push) Successful in 25m8s
Flutter / build (BalanceFlutter/build/app/outputs/flutter-apk/*.apk, ubuntu-latest, android) (push) Failing after 38m6s
Flutter / build (BalanceFlutter/build/linux/x64/release/bundle, ubuntu-latest, linux) (push) Successful in 32m37s
Flutter / build (BalanceFlutter/build/web, ubuntu-latest, web) (push) Successful in 31m39s
iOS CI / Build and Test SwiftUI App (pull_request) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (pull_request) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (pull_request) Canceled after 0s
Flutter / build (BalanceFlutter/build/ios/iphoneos/*.app, macos-latest, ios) (pull_request) Canceled after 0s
Flutter / build (BalanceFlutter/build/macos/Build/Products/Release/*.app, macos-latest, macos) (pull_request) Canceled after 0s
Flutter / build (BalanceFlutter/build/windows/x64/runner/Release, windows-latest, windows) (pull_request) Canceled after 0s
Flutter / watchos (pull_request) Canceled after 0s
Flutter / checks (pull_request) Successful in 24m59s
Flutter / build (BalanceFlutter/build/app/outputs/flutter-apk/*.apk, ubuntu-latest, android) (pull_request) Failing after 31m24s
Flutter / build (BalanceFlutter/build/linux/x64/release/bundle, ubuntu-latest, linux) (pull_request) Successful in 32m34s
Flutter / build (BalanceFlutter/build/web, ubuntu-latest, web) (pull_request) Successful in 24m54s
This commit is contained in:
1 parent
2c278044bb
commit
a2aa714a83
16 files changed
+436
-42
No files matched your search
@@ -0,0 +1,15 @@
|
||||
/// Device-local appearance preferences; these never enter finance sync payloads.
|
||||
enum ColorPreset {
|
||||
forest('forest', 'Лес', 0xff356859),
|
||||
indigo('indigo', 'Индиго', 0xff4f46a5),
|
||||
ocean('ocean', 'Океан', 0xff006b9a),
|
||||
rose('rose', 'Роза', 0xff984563),
|
||||
oled('oled', 'OLED — чёрный', 0xff51bda0);
|
||||
|
||||
const ColorPreset(this.id, this.label, this.seedArgb);
|
||||
final String id, label;
|
||||
final int seedArgb;
|
||||
|
||||
static ColorPreset fromId(String? id) =>
|
||||
values.firstWhere((preset) => preset.id == id, orElse: () => forest);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../domain/finance.dart';
|
||||
import '../domain/appearance.dart';
|
||||
import 'storage.dart';
|
||||
import 'runtime_lock.dart';
|
||||
import 'sync_api.dart';
|
||||
@@ -62,6 +63,7 @@ class AppStore extends ChangeNotifier with WidgetsBindingObserver {
|
||||
String serverUrl = '';
|
||||
String currency = 'RUB';
|
||||
String theme = 'system';
|
||||
String themePreset = 'forest';
|
||||
AuthSession? session;
|
||||
String? sessionPersistenceWarning;
|
||||
String get email => session?.email ?? '';
|
||||
@@ -121,6 +123,7 @@ class AppStore extends ChangeNotifier with WidgetsBindingObserver {
|
||||
serverUrl = _preferences!.getString('serverUrl') ?? serverUrl;
|
||||
currency = _preferences!.getString('currency') ?? currency;
|
||||
theme = _preferences!.getString('theme') ?? theme;
|
||||
themePreset = ColorPreset.fromId(_preferences!.getString('themePreset')).id;
|
||||
_deviceId = _preferences!.getString('deviceId') ?? newId();
|
||||
await _preferences!.setString('deviceId', _deviceId);
|
||||
_storage ??= await LedgerStorage.open();
|
||||
@@ -147,6 +150,7 @@ class AppStore extends ChangeNotifier with WidgetsBindingObserver {
|
||||
}
|
||||
currency = _preferences!.getString('currency') ?? 'RUB';
|
||||
theme = _preferences!.getString('theme') ?? 'system';
|
||||
themePreset = ColorPreset.fromId(_preferences!.getString('themePreset')).id;
|
||||
_deviceId = _preferences!.getString('deviceId') ?? _deviceId;
|
||||
await _restoreSession();
|
||||
await _loadScope();
|
||||
@@ -481,10 +485,26 @@ class AppStore extends ChangeNotifier with WidgetsBindingObserver {
|
||||
_notify();
|
||||
});
|
||||
Future<void> setTheme(String value) => _exclusive(() async {
|
||||
if (!const ['system', 'light', 'dark'].contains(value)) {
|
||||
throw ArgumentError('Неизвестный режим темы');
|
||||
}
|
||||
theme = value;
|
||||
await _preferences!.setString('theme', value);
|
||||
_notify();
|
||||
});
|
||||
Future<void> setThemePreset(String value) => _exclusive(() async {
|
||||
if (!ColorPreset.values.any((preset) => preset.id == value)) {
|
||||
throw ArgumentError('Неизвестный пресет');
|
||||
}
|
||||
await _preferences!.setString('themePreset', value);
|
||||
themePreset = value;
|
||||
if (value == ColorPreset.oled.id) {
|
||||
await _preferences!.setString('theme', 'dark');
|
||||
theme = 'dark';
|
||||
}
|
||||
_notify();
|
||||
});
|
||||
|
||||
Future<void> setServerUrl(String value) => _exclusive(() async {
|
||||
final normalized = value.trim().replaceAll(RegExp(r'/+$'), '');
|
||||
final uri = Uri.tryParse(normalized);
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'package:intl/intl.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import '../domain/finance.dart';
|
||||
import '../domain/appearance.dart';
|
||||
import 'app_theme.dart';
|
||||
import '../services/app_store.dart';
|
||||
import 'design.dart';
|
||||
import 'editors.dart';
|
||||
@@ -22,8 +24,14 @@ class BalanceApp extends StatelessWidget {
|
||||
locale: const Locale('ru'),
|
||||
supportedLocales: const [Locale('ru'), Locale('en')],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
theme: makeTheme(Brightness.light),
|
||||
darkTheme: makeTheme(Brightness.dark),
|
||||
theme: BalanceThemes.build(
|
||||
Brightness.light,
|
||||
ColorPreset.fromId(store.themePreset),
|
||||
),
|
||||
darkTheme: BalanceThemes.build(
|
||||
Brightness.dark,
|
||||
ColorPreset.fromId(store.themePreset),
|
||||
),
|
||||
themeMode: store.theme == 'dark'
|
||||
? ThemeMode.dark
|
||||
: store.theme == 'light'
|
||||
@@ -34,28 +42,6 @@ class BalanceApp extends StatelessWidget {
|
||||
: const Scaffold(body: Center(child: CircularProgressIndicator())),
|
||||
),
|
||||
);
|
||||
ThemeData makeTheme(Brightness brightness) {
|
||||
final scheme = ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xff356859),
|
||||
brightness: brightness,
|
||||
);
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
fontFamily: 'BalanceRoboto',
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: scheme.surface,
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: scheme.surface,
|
||||
centerTitle: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BalanceShell extends StatefulWidget {
|
||||
@@ -1105,6 +1091,7 @@ class _BalanceShellState extends State<BalanceShell> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<String>(
|
||||
key: ValueKey('theme-mode-${store.theme}'),
|
||||
initialValue: store.theme,
|
||||
decoration: const InputDecoration(labelText: 'Тема'),
|
||||
items: const [
|
||||
@@ -1116,6 +1103,45 @@ class _BalanceShellState extends State<BalanceShell> {
|
||||
if (v != null) run(() => store.setTheme(v));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Цветовой пресет',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: ColorPreset.values
|
||||
.map(
|
||||
(preset) => ChoiceChip(
|
||||
label: Text(preset.label),
|
||||
avatar: CircleAvatar(
|
||||
radius: 9,
|
||||
backgroundColor: preset == ColorPreset.oled
|
||||
? Colors.black
|
||||
: Color(preset.seedArgb),
|
||||
child: preset == ColorPreset.oled
|
||||
? const Icon(
|
||||
Icons.nightlight_round,
|
||||
size: 13,
|
||||
color: Colors.white,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
selected: store.themePreset == preset.id,
|
||||
onSelected: (_) =>
|
||||
run(() => store.setThemePreset(preset.id)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
if (store.themePreset == 'oled') ...[
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'OLED: чистый чёрный фон #000000 в тёмном режиме. При выборе пресета тёмная тема включается автоматически. Можно затем выбрать системный режим.',
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Валюта задаёт формат сумм. Конвертация валют не выполняется.',
|
||||
@@ -1288,7 +1314,7 @@ class _BalanceShellState extends State<BalanceShell> {
|
||||
onPressed: () => showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'Баланс',
|
||||
applicationVersion: '4.0.0',
|
||||
applicationVersion: '4.1.0',
|
||||
applicationIcon: const Icon(
|
||||
Icons.account_balance_wallet_outlined,
|
||||
size: 40,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../domain/appearance.dart';
|
||||
|
||||
class BalanceThemes {
|
||||
static ThemeData build(Brightness brightness, ColorPreset preset) {
|
||||
final oled = brightness == Brightness.dark && preset == ColorPreset.oled;
|
||||
var scheme = ColorScheme.fromSeed(
|
||||
seedColor: Color(preset.seedArgb),
|
||||
brightness: brightness,
|
||||
);
|
||||
if (oled) {
|
||||
scheme = scheme.copyWith(
|
||||
surface: Colors.black,
|
||||
surfaceDim: Colors.black,
|
||||
surfaceBright: Colors.black,
|
||||
surfaceContainerLowest: Colors.black,
|
||||
surfaceContainerLow: Colors.black,
|
||||
surfaceContainer: Colors.black,
|
||||
surfaceContainerHigh: Colors.black,
|
||||
surfaceContainerHighest: Colors.black,
|
||||
surfaceTint: Colors.transparent,
|
||||
primaryContainer: Colors.black,
|
||||
onPrimaryContainer: scheme.onSurface,
|
||||
secondaryContainer: const Color(0xff171b19),
|
||||
outlineVariant: const Color(0xff303633),
|
||||
);
|
||||
}
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
fontFamily: 'BalanceRoboto',
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: scheme.surface,
|
||||
canvasColor: scheme.surface,
|
||||
applyElevationOverlayColor: !oled,
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
surfaceTintColor: oled ? Colors.transparent : null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
side: oled
|
||||
? BorderSide(color: scheme.outlineVariant)
|
||||
: BorderSide.none,
|
||||
),
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: scheme.surface,
|
||||
surfaceTintColor: oled ? Colors.transparent : null,
|
||||
centerTitle: false,
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: oled ? Colors.black : null,
|
||||
surfaceTintColor: oled ? Colors.transparent : null,
|
||||
),
|
||||
navigationRailTheme: NavigationRailThemeData(
|
||||
backgroundColor: oled ? Colors.black : null,
|
||||
),
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: oled ? Colors.black : null,
|
||||
surfaceTintColor: oled ? Colors.transparent : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user