mirror of
https://github.com/koloideal/Casha.git
synced 2026-08-08 09:41:13 +03:00
step
This commit is contained in:
@@ -25,10 +25,6 @@ class BiometricService {
|
||||
try {
|
||||
return await _auth.authenticate(
|
||||
localizedReason: 'Confirm your identity to open Casha',
|
||||
options: const AuthenticationOptions(
|
||||
biometricOnly: false,
|
||||
stickyAuth: true,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
|
||||
@@ -80,13 +80,22 @@ class AddTransactionState {
|
||||
bool get isEditing => editingId != null;
|
||||
}
|
||||
|
||||
class AddTransactionNotifier extends StateNotifier<AddTransactionState> {
|
||||
AddTransactionNotifier(Transaction? initial)
|
||||
: super(
|
||||
initial != null
|
||||
? AddTransactionState.fromTransaction(initial)
|
||||
: AddTransactionState.empty(),
|
||||
);
|
||||
final addTransactionProvider = NotifierProvider.autoDispose
|
||||
.family<AddTransactionNotifier, AddTransactionState, Transaction?>(
|
||||
(initial) => AddTransactionNotifier(initial),
|
||||
);
|
||||
|
||||
class AddTransactionNotifier extends Notifier<AddTransactionState> {
|
||||
AddTransactionNotifier(this._initial);
|
||||
|
||||
final Transaction? _initial;
|
||||
|
||||
@override
|
||||
AddTransactionState build() {
|
||||
return _initial != null
|
||||
? AddTransactionState.fromTransaction(_initial!)
|
||||
: AddTransactionState.empty();
|
||||
}
|
||||
|
||||
void setAmount(double? v) => state = state.copyWith(amount: v);
|
||||
|
||||
@@ -123,11 +132,6 @@ class AddTransactionNotifier extends StateNotifier<AddTransactionState> {
|
||||
void reset() => state = AddTransactionState.empty();
|
||||
}
|
||||
|
||||
final addTransactionProvider = StateNotifierProvider.autoDispose
|
||||
.family<AddTransactionNotifier, AddTransactionState, Transaction?>(
|
||||
(ref, initial) => AddTransactionNotifier(initial),
|
||||
);
|
||||
|
||||
final availableCategoriesProvider = Provider.autoDispose
|
||||
.family<List<String>, Transaction?>((ref, initial) {
|
||||
final type = ref.watch(
|
||||
@@ -135,3 +139,4 @@ final availableCategoriesProvider = Provider.autoDispose
|
||||
);
|
||||
return AppCategories.forType(type);
|
||||
});
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
|
||||
if (widget.initial!.category == 'Transfer') {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final allTxs = ref.read(transactionsProvider).valueOrNull ?? [];
|
||||
final allTxs = ref.read(transactionsProvider).value ?? [];
|
||||
|
||||
if (widget.initial!.type == TransactionType.expense) {
|
||||
final counterpart = allTxs.firstWhereOrNull(
|
||||
@@ -271,7 +271,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
currencyCode: currencyCode,
|
||||
accountId: state.selectedAccountId!,
|
||||
);
|
||||
await ref.read(transactionsProvider.notifier).update(updatedExpense);
|
||||
await ref.read(transactionsProvider.notifier).updateTransaction(updatedExpense);
|
||||
|
||||
if (_transferIncomeRecordId != null) {
|
||||
final updatedIncome = Transaction(
|
||||
@@ -285,7 +285,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
currencyCode: currencyCode,
|
||||
accountId: state.toAccountId!,
|
||||
);
|
||||
await ref.read(transactionsProvider.notifier).update(updatedIncome);
|
||||
await ref.read(transactionsProvider.notifier).updateTransaction(updatedIncome);
|
||||
}
|
||||
|
||||
if (mounted) context.pop();
|
||||
@@ -349,7 +349,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
);
|
||||
|
||||
if (state.isEditing) {
|
||||
await ref.read(transactionsProvider.notifier).update(tx);
|
||||
await ref.read(transactionsProvider.notifier).updateTransaction(tx);
|
||||
} else {
|
||||
final res = await ref.read(transactionsProvider.notifier).add(tx);
|
||||
|
||||
@@ -486,7 +486,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
.delete(counterpartId);
|
||||
} else {
|
||||
final allTxs =
|
||||
ref.read(transactionsProvider).valueOrNull ??
|
||||
ref.read(transactionsProvider).value ??
|
||||
[];
|
||||
final oppositeType =
|
||||
widget.initial!.type ==
|
||||
|
||||
@@ -41,7 +41,7 @@ class AccountRow extends ConsumerWidget {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final state = ref.watch(addTransactionProvider(initial));
|
||||
final accountsAsync = ref.watch(accountsProvider);
|
||||
final accounts = accountsAsync.valueOrNull ?? [];
|
||||
final accounts = accountsAsync.value ?? [];
|
||||
final isTransfer = state.type == TransactionType.transfer;
|
||||
|
||||
if (isTransfer && accounts.length == 2 && state.selectedAccountId != null) {
|
||||
|
||||
@@ -21,7 +21,7 @@ class TypeToggle extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final accountsAsync = ref.watch(accountsProvider);
|
||||
final accounts = accountsAsync.valueOrNull ?? [];
|
||||
final accounts = accountsAsync.value ?? [];
|
||||
final transferDisabled = accounts.length <= 1;
|
||||
|
||||
return Container(
|
||||
|
||||
@@ -4,7 +4,7 @@ import '../dashboard/provider.dart';
|
||||
|
||||
final categoryExpenseProvider = Provider<Map<String, double>>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = txsAsync.value ?? [];
|
||||
final filtered = txs.where((t) => t.type == TransactionType.expense);
|
||||
|
||||
final map = <String, double>{};
|
||||
@@ -16,7 +16,7 @@ final categoryExpenseProvider = Provider<Map<String, double>>((ref) {
|
||||
|
||||
final categoryIncomeProvider = Provider<Map<String, double>>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = txsAsync.value ?? [];
|
||||
final filtered = txs.where((t) => t.type == TransactionType.income);
|
||||
|
||||
final map = <String, double>{};
|
||||
@@ -28,7 +28,7 @@ final categoryIncomeProvider = Provider<Map<String, double>>((ref) {
|
||||
|
||||
final monthlyBreakdownProvider = Provider<List<MonthlyData>>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = txsAsync.value ?? [];
|
||||
final filtered = txs.where((t) => t.type == TransactionType.expense);
|
||||
|
||||
final now = DateTime.now();
|
||||
|
||||
@@ -35,77 +35,75 @@ final storageServiceProvider = Provider<StorageService>((ref) {
|
||||
});
|
||||
|
||||
final transactionsProvider =
|
||||
StateNotifierProvider<TransactionsNotifier, AsyncValue<List<Transaction>>>((
|
||||
ref,
|
||||
) {
|
||||
final repository = ref.watch(transactionRepositoryProvider);
|
||||
return TransactionsNotifier(repository);
|
||||
});
|
||||
AsyncNotifierProvider<TransactionsNotifier, List<Transaction>>(
|
||||
TransactionsNotifier.new,
|
||||
);
|
||||
|
||||
class TransactionsNotifier
|
||||
extends StateNotifier<AsyncValue<List<Transaction>>> {
|
||||
final TransactionRepository _repository;
|
||||
class TransactionsNotifier extends AsyncNotifier<List<Transaction>> {
|
||||
@override
|
||||
Future<List<Transaction>> build() async {
|
||||
final repository = ref.watch(transactionRepositoryProvider);
|
||||
final result = await repository.getAll();
|
||||
|
||||
TransactionsNotifier(this._repository) : super(const AsyncValue.loading()) {
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
state = const AsyncValue.loading();
|
||||
final result = await _repository.getAll();
|
||||
|
||||
state = result.isSuccess
|
||||
? AsyncValue.data(result.dataOrNull!)
|
||||
: AsyncValue.error(result.errorOrNull!, StackTrace.current);
|
||||
if (result.isSuccess) {
|
||||
return result.dataOrNull!;
|
||||
} else {
|
||||
throw result.errorOrNull!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<void>> add(Transaction transaction) async {
|
||||
final result = await _repository.add(transaction);
|
||||
final repository = ref.read(transactionRepositoryProvider);
|
||||
final result = await repository.add(transaction);
|
||||
|
||||
if (result.isSuccess) {
|
||||
await _load();
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<Result<void>> update(Transaction transaction) async {
|
||||
final result = await _repository.update(transaction);
|
||||
Future<Result<void>> updateTransaction(Transaction transaction) async {
|
||||
final repository = ref.read(transactionRepositoryProvider);
|
||||
final result = await repository.update(transaction);
|
||||
|
||||
if (result.isSuccess) {
|
||||
await _load();
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<Result<void>> delete(String id) async {
|
||||
final result = await _repository.delete(id);
|
||||
final repository = ref.read(transactionRepositoryProvider);
|
||||
final result = await repository.delete(id);
|
||||
|
||||
if (result.isSuccess) {
|
||||
await _load();
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> restore(Transaction transaction) async {
|
||||
await _repository.add(transaction);
|
||||
await _load();
|
||||
final repository = ref.read(transactionRepositoryProvider);
|
||||
await repository.add(transaction);
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
Future<void> clearAll() async {
|
||||
await _repository.deleteAll();
|
||||
final repository = ref.read(transactionRepositoryProvider);
|
||||
await repository.deleteAll();
|
||||
state = const AsyncValue.data([]);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
await _load();
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
final transferPairsProvider = Provider<Map<String, Transaction>>((ref) {
|
||||
final txs = ref.watch(transactionsProvider).valueOrNull ?? [];
|
||||
final txs = ref.watch(transactionsProvider).value ?? [];
|
||||
final transfers = txs.where((t) => t.category == 'Transfer').toList();
|
||||
final Map<String, Transaction> pairs = {};
|
||||
|
||||
@@ -131,23 +129,46 @@ final transferPairsProvider = Provider<Map<String, Transaction>>((ref) {
|
||||
return pairs;
|
||||
});
|
||||
|
||||
final searchQueryProvider = StateProvider<String>((ref) => '');
|
||||
final searchQueryProvider = NotifierProvider<_SearchQueryNotifier, String>(
|
||||
_SearchQueryNotifier.new,
|
||||
);
|
||||
|
||||
class _SearchQueryNotifier extends Notifier<String> {
|
||||
@override
|
||||
String build() => '';
|
||||
|
||||
void set(String v) => state = v;
|
||||
}
|
||||
|
||||
enum TransactionFilter { all, income, expense, transfer }
|
||||
|
||||
enum TimeFilter { allTime, lastMonth }
|
||||
|
||||
final transactionFilterProvider = StateProvider<TransactionFilter>(
|
||||
(ref) => TransactionFilter.all,
|
||||
final transactionFilterProvider = NotifierProvider<_TransactionFilterNotifier, TransactionFilter>(
|
||||
_TransactionFilterNotifier.new,
|
||||
);
|
||||
|
||||
final timeFilterProvider = StateProvider<TimeFilter>(
|
||||
(ref) => TimeFilter.lastMonth,
|
||||
class _TransactionFilterNotifier extends Notifier<TransactionFilter> {
|
||||
@override
|
||||
TransactionFilter build() => TransactionFilter.all;
|
||||
|
||||
void set(TransactionFilter v) => state = v;
|
||||
}
|
||||
|
||||
final timeFilterProvider = NotifierProvider<_TimeFilterNotifier, TimeFilter>(
|
||||
_TimeFilterNotifier.new,
|
||||
);
|
||||
|
||||
class _TimeFilterNotifier extends Notifier<TimeFilter> {
|
||||
@override
|
||||
TimeFilter build() => TimeFilter.lastMonth;
|
||||
|
||||
void set(TimeFilter v) => state = v;
|
||||
}
|
||||
|
||||
final accountFilteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = txsAsync.value ?? [];
|
||||
final activeAccount = ref.watch(activeAccountProvider);
|
||||
|
||||
if (activeAccount == null) {
|
||||
@@ -158,7 +179,7 @@ final accountFilteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||
});
|
||||
|
||||
final globalTotalBalanceProvider = Provider<double>((ref) {
|
||||
final txs = ref.watch(transactionsProvider).valueOrNull ?? [];
|
||||
final txs = ref.watch(transactionsProvider).value ?? [];
|
||||
final exchangeService = ref.watch(exchangeRateServiceProvider);
|
||||
final targetCurrency = ref.watch(currencyProvider).code;
|
||||
|
||||
@@ -181,7 +202,7 @@ final totalBalanceProvider = Provider<double>((ref) {
|
||||
|
||||
String targetCurrency = globalCurrency;
|
||||
if (index > 0) {
|
||||
final accounts = accountsAsync.valueOrNull ?? [];
|
||||
final accounts = accountsAsync.value ?? [];
|
||||
if (index <= accounts.length) {
|
||||
targetCurrency = accounts[index - 1].currency;
|
||||
}
|
||||
@@ -211,7 +232,7 @@ final totalIncomeProvider = Provider<double>((ref) {
|
||||
|
||||
String targetCurrency = globalCurrency;
|
||||
if (index > 0) {
|
||||
final accounts = accountsAsync.valueOrNull ?? [];
|
||||
final accounts = accountsAsync.value ?? [];
|
||||
if (index <= accounts.length) {
|
||||
targetCurrency = accounts[index - 1].currency;
|
||||
}
|
||||
@@ -237,7 +258,7 @@ final totalExpenseProvider = Provider<double>((ref) {
|
||||
|
||||
String targetCurrency = globalCurrency;
|
||||
if (index > 0) {
|
||||
final accounts = accountsAsync.valueOrNull ?? [];
|
||||
final accounts = accountsAsync.value ?? [];
|
||||
if (index <= accounts.length) {
|
||||
targetCurrency = accounts[index - 1].currency;
|
||||
}
|
||||
@@ -267,7 +288,7 @@ final currentMonthExpenseProvider = Provider<double>((ref) {
|
||||
|
||||
String targetCurrency = globalCurrency;
|
||||
if (index > 0) {
|
||||
final accounts = accountsAsync.valueOrNull ?? [];
|
||||
final accounts = accountsAsync.value ?? [];
|
||||
if (index <= accounts.length) {
|
||||
targetCurrency = accounts[index - 1].currency;
|
||||
}
|
||||
@@ -349,7 +370,16 @@ final accountsProvider = StreamProvider<List<Account>>((ref) async* {
|
||||
}
|
||||
});
|
||||
|
||||
final activeAccountIndexProvider = StateProvider<int>((ref) => 0);
|
||||
final activeAccountIndexProvider = NotifierProvider<_ActiveAccountIndexNotifier, int>(
|
||||
_ActiveAccountIndexNotifier.new,
|
||||
);
|
||||
|
||||
class _ActiveAccountIndexNotifier extends Notifier<int> {
|
||||
@override
|
||||
int build() => 0;
|
||||
|
||||
void set(int v) => state = v;
|
||||
}
|
||||
|
||||
final activeAccountProvider = Provider<Account?>((ref) {
|
||||
final index = ref.watch(activeAccountIndexProvider);
|
||||
@@ -387,52 +417,37 @@ class CardColors {
|
||||
}
|
||||
|
||||
final cardColorsProvider =
|
||||
StateNotifierProvider<CardColorsNotifier, CardColors>((ref) {
|
||||
final notifier = CardColorsNotifier();
|
||||
notifier.setupThemeListener(ref);
|
||||
return notifier;
|
||||
});
|
||||
NotifierProvider<CardColorsNotifier, CardColors>(
|
||||
CardColorsNotifier.new,
|
||||
);
|
||||
|
||||
final accountCardColorsProvider =
|
||||
StateNotifierProvider.family<CardColorsNotifier, CardColors, int>((
|
||||
ref,
|
||||
accountId,
|
||||
) {
|
||||
final notifier = CardColorsNotifier(accountId: accountId);
|
||||
notifier.setupThemeListener(ref);
|
||||
return notifier;
|
||||
});
|
||||
|
||||
class CardColorsNotifier extends StateNotifier<CardColors> {
|
||||
final int? accountId;
|
||||
|
||||
CardColorsNotifier({this.accountId})
|
||||
: super(
|
||||
const CardColors(
|
||||
CardColorService.defaultPrimary,
|
||||
CardColorService.defaultSecondary,
|
||||
CardColorService.defaultGradientLight,
|
||||
CardColorService.defaultGradientDark,
|
||||
),
|
||||
) {
|
||||
_load();
|
||||
}
|
||||
NotifierProvider.family<AccountCardColorsNotifier, CardColors, int>(
|
||||
(accountId) => AccountCardColorsNotifier(accountId),
|
||||
);
|
||||
|
||||
class CardColorsNotifier extends Notifier<CardColors> {
|
||||
int _loadGeneration = 0;
|
||||
|
||||
void setupThemeListener(Ref ref) {
|
||||
@override
|
||||
CardColors build() {
|
||||
ref.listen<ThemeMode>(themeProvider, (previous, next) {
|
||||
if (previous != null) {
|
||||
_onThemeChanged(previous, next);
|
||||
}
|
||||
});
|
||||
_load();
|
||||
return const CardColors(
|
||||
CardColorService.defaultPrimary,
|
||||
CardColorService.defaultSecondary,
|
||||
CardColorService.defaultGradientLight,
|
||||
CardColorService.defaultGradientDark,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
final currentGeneration = ++_loadGeneration;
|
||||
final (c1, c2, lightG, darkG) = await CardColorService.load(
|
||||
accountId: accountId,
|
||||
);
|
||||
final (c1, c2, lightG, darkG) = await CardColorService.load();
|
||||
if (currentGeneration != _loadGeneration) return;
|
||||
state = CardColors(c1, c2, lightG, darkG);
|
||||
}
|
||||
@@ -450,7 +465,6 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
|
||||
secondary,
|
||||
lightGradient,
|
||||
darkGradient,
|
||||
accountId: accountId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -473,7 +487,127 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
|
||||
secondary,
|
||||
CardColorService.defaultGradientLight,
|
||||
CardColorService.defaultGradientDark,
|
||||
accountId: accountId,
|
||||
);
|
||||
}
|
||||
|
||||
void _onThemeChanged(ThemeMode previous, ThemeMode next) {
|
||||
final previousBrightness = _resolve(previous);
|
||||
final nextBrightness = _resolve(next);
|
||||
|
||||
if (previousBrightness == nextBrightness) return;
|
||||
|
||||
final oldDefaults = _defaultsFor(previousBrightness);
|
||||
final newDefaults = _defaultsFor(nextBrightness);
|
||||
|
||||
final isUsingOldDefaults =
|
||||
state.primary == oldDefaults.primary &&
|
||||
state.secondary == oldDefaults.secondary &&
|
||||
state.gradientTypeForBrightness(previousBrightness) ==
|
||||
oldDefaults.gradient;
|
||||
|
||||
if (isUsingOldDefaults) {
|
||||
_loadGeneration++;
|
||||
state = CardColors(
|
||||
newDefaults.primary,
|
||||
newDefaults.secondary,
|
||||
state.lightGradientType,
|
||||
state.darkGradientType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Brightness _resolve(ThemeMode mode) {
|
||||
if (mode == ThemeMode.system) {
|
||||
return WidgetsBinding.instance.platformDispatcher.platformBrightness;
|
||||
}
|
||||
return mode == ThemeMode.dark ? Brightness.dark : Brightness.light;
|
||||
}
|
||||
|
||||
({Color primary, Color secondary, GradientType gradient}) _defaultsFor(
|
||||
Brightness brightness,
|
||||
) {
|
||||
return brightness == Brightness.dark
|
||||
? (
|
||||
primary: CardColorService.defaultPrimary,
|
||||
secondary: CardColorService.defaultSecondary,
|
||||
gradient: CardColorService.defaultGradientDark,
|
||||
)
|
||||
: (
|
||||
primary: CardColorService.defaultPrimaryLight,
|
||||
secondary: CardColorService.defaultSecondaryLight,
|
||||
gradient: CardColorService.defaultGradientLight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccountCardColorsNotifier extends Notifier<CardColors> {
|
||||
AccountCardColorsNotifier(this._accountId);
|
||||
|
||||
final int _accountId;
|
||||
int _loadGeneration = 0;
|
||||
|
||||
@override
|
||||
CardColors build() {
|
||||
ref.listen<ThemeMode>(themeProvider, (previous, next) {
|
||||
if (previous != null) {
|
||||
_onThemeChanged(previous, next);
|
||||
}
|
||||
});
|
||||
_load(_accountId);
|
||||
return const CardColors(
|
||||
CardColorService.defaultPrimary,
|
||||
CardColorService.defaultSecondary,
|
||||
CardColorService.defaultGradientLight,
|
||||
CardColorService.defaultGradientDark,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _load(int accountId) async {
|
||||
final currentGeneration = ++_loadGeneration;
|
||||
final (c1, c2, lightG, darkG) = await CardColorService.load(
|
||||
accountId: accountId,
|
||||
);
|
||||
if (currentGeneration != _loadGeneration) return;
|
||||
state = CardColors(c1, c2, lightG, darkG);
|
||||
}
|
||||
|
||||
Future<void> save(
|
||||
Color primary,
|
||||
Color secondary,
|
||||
GradientType lightGradient,
|
||||
GradientType darkGradient,
|
||||
) async {
|
||||
_loadGeneration++;
|
||||
state = CardColors(primary, secondary, lightGradient, darkGradient);
|
||||
await CardColorService.save(
|
||||
primary,
|
||||
secondary,
|
||||
lightGradient,
|
||||
darkGradient,
|
||||
accountId: _accountId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reset(bool isDark) async {
|
||||
final primary = isDark
|
||||
? CardColorService.defaultPrimary
|
||||
: CardColorService.defaultPrimaryLight;
|
||||
final secondary = isDark
|
||||
? CardColorService.defaultSecondary
|
||||
: CardColorService.defaultSecondaryLight;
|
||||
_loadGeneration++;
|
||||
state = CardColors(
|
||||
primary,
|
||||
secondary,
|
||||
CardColorService.defaultGradientLight,
|
||||
CardColorService.defaultGradientDark,
|
||||
);
|
||||
await CardColorService.save(
|
||||
primary,
|
||||
secondary,
|
||||
CardColorService.defaultGradientLight,
|
||||
CardColorService.defaultGradientDark,
|
||||
accountId: _accountId,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
|
||||
final activeIndex = ref.watch(activeAccountIndexProvider);
|
||||
final accountsAsync = ref.watch(accountsProvider);
|
||||
final accountCount = accountsAsync.valueOrNull?.length ?? 0;
|
||||
final accountCount = accountsAsync.value?.length ?? 0;
|
||||
final isOnAddAccountPage =
|
||||
accountCount < 5 && activeIndex == accountCount + 1;
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!dash.isAddingAccount &&
|
||||
(ref.watch(accountsProvider).valueOrNull?.length ?? 0) >
|
||||
(ref.watch(accountsProvider).value?.length ?? 0) >
|
||||
1) ...[
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _showDeleteDialog = true),
|
||||
|
||||
@@ -588,7 +588,7 @@ class AccountColorPanel extends StatelessWidget {
|
||||
final accounts =
|
||||
ProviderScope.containerOf(
|
||||
dashboardContext,
|
||||
).read(accountsProvider).valueOrNull ??
|
||||
).read(accountsProvider).value ??
|
||||
[];
|
||||
|
||||
if (isDuplicateName(
|
||||
|
||||
@@ -56,7 +56,7 @@ class AccountDeleteDialog extends ConsumerWidget {
|
||||
|
||||
onConfirm();
|
||||
|
||||
final txs = ref.read(transactionsProvider).valueOrNull ?? [];
|
||||
final txs = ref.read(transactionsProvider).value ?? [];
|
||||
final accountTxs = txs
|
||||
.where((t) => t.accountId == accountId)
|
||||
.toList();
|
||||
|
||||
@@ -145,15 +145,12 @@ class BalanceCardState extends ConsumerState<BalanceCard>
|
||||
.toList();
|
||||
|
||||
final textColorMode = ref.watch(cardTextColorProvider);
|
||||
final Color onCard;
|
||||
switch (textColorMode) {
|
||||
case CardTextColorMode.white:
|
||||
onCard = Colors.white;
|
||||
case CardTextColorMode.black:
|
||||
onCard = Colors.black;
|
||||
case CardTextColorMode.adaptive:
|
||||
onCard = primary.computeLuminance() > 0.3 ? Colors.black : Colors.white;
|
||||
}
|
||||
final Color onCard = switch (textColorMode) {
|
||||
CardTextColorMode.white => Colors.white,
|
||||
CardTextColorMode.black => Colors.black,
|
||||
CardTextColorMode.adaptive => primary.computeLuminance() > 0.3 ? Colors.black : Colors.white,
|
||||
_ => Colors.white,
|
||||
};
|
||||
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
|
||||
@@ -75,7 +75,7 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
Clip.none,
|
||||
itemCount: totalPages,
|
||||
onPageChanged: (index) {
|
||||
ref.read(activeAccountIndexProvider.notifier).state = index;
|
||||
ref.read(activeAccountIndexProvider.notifier).set(index);
|
||||
if (ref.read(hapticEnabledProvider)) {
|
||||
HapticService.light();
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
);
|
||||
|
||||
final txs =
|
||||
ref.watch(transactionsProvider).valueOrNull ?? [];
|
||||
ref.watch(transactionsProvider).value ?? [];
|
||||
final accountTxs = txs
|
||||
.where((t) => t.accountId == account.id)
|
||||
.toList();
|
||||
|
||||
@@ -23,15 +23,15 @@ class FilterChips extends ConsumerWidget {
|
||||
_FilterChip(
|
||||
label: strings.filterAllTime,
|
||||
isSelected: timeFilter == TimeFilter.allTime,
|
||||
onTap: () => ref.read(timeFilterProvider.notifier).state =
|
||||
TimeFilter.allTime,
|
||||
onTap: () => ref.read(timeFilterProvider.notifier).set(
|
||||
TimeFilter.allTime),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
_FilterChip(
|
||||
label: strings.filterMonth,
|
||||
isSelected: timeFilter == TimeFilter.lastMonth,
|
||||
onTap: () => ref.read(timeFilterProvider.notifier).state =
|
||||
TimeFilter.lastMonth,
|
||||
onTap: () => ref.read(timeFilterProvider.notifier).set(
|
||||
TimeFilter.lastMonth),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
@@ -46,32 +46,32 @@ class FilterChips extends ConsumerWidget {
|
||||
_FilterChip(
|
||||
label: strings.filterAll,
|
||||
isSelected: typeFilter == TransactionFilter.all,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).state =
|
||||
TransactionFilter.all,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).set(
|
||||
TransactionFilter.all),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
_FilterChip(
|
||||
label: strings.filterIncome,
|
||||
isSelected: typeFilter == TransactionFilter.income,
|
||||
color: AppColors.income,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).state =
|
||||
TransactionFilter.income,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).set(
|
||||
TransactionFilter.income),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
_FilterChip(
|
||||
label: strings.filterExpense,
|
||||
isSelected: typeFilter == TransactionFilter.expense,
|
||||
color: AppColors.expense,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).state =
|
||||
TransactionFilter.expense,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).set(
|
||||
TransactionFilter.expense),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
_FilterChip(
|
||||
label: strings.filterTransfer,
|
||||
isSelected: typeFilter == TransactionFilter.transfer,
|
||||
color: Colors.blueAccent,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).state =
|
||||
TransactionFilter.transfer,
|
||||
onTap: () => ref.read(transactionFilterProvider.notifier).set(
|
||||
TransactionFilter.transfer),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -37,7 +37,7 @@ class SearchBar extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
onPressed: () {
|
||||
controller.clear();
|
||||
ref.read(searchQueryProvider.notifier).state = '';
|
||||
ref.read(searchQueryProvider.notifier).set('');
|
||||
},
|
||||
)
|
||||
: null,
|
||||
@@ -63,7 +63,7 @@ class SearchBar extends StatelessWidget {
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onChanged: (v) => ref.read(searchQueryProvider.notifier).state = v,
|
||||
onChanged: (v) => ref.read(searchQueryProvider.notifier).set(v),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class TransactionTile extends ConsumerWidget {
|
||||
: 0.0;
|
||||
final displaySymbol = currencyMap[displayCurrency]?.symbol ?? '';
|
||||
|
||||
final accounts = ref.watch(accountsProvider).valueOrNull ?? [];
|
||||
final accounts = ref.watch(accountsProvider).value ?? [];
|
||||
final txAccount = accounts.firstWhereOrNull(
|
||||
(a) => a.id == transaction.accountId,
|
||||
);
|
||||
|
||||
@@ -11,18 +11,20 @@ import '../../shared/utils/currency_utils.dart';
|
||||
import '../../shared/providers/amount_format_provider.dart';
|
||||
import '../dashboard/provider.dart';
|
||||
|
||||
final budgetProvider = StateNotifierProvider<BudgetNotifier, double?>((ref) {
|
||||
final storage = ref.watch(storageServiceProvider);
|
||||
return BudgetNotifier(storage.loadBudget(), storage);
|
||||
});
|
||||
final budgetProvider = NotifierProvider<BudgetNotifier, double?>(
|
||||
BudgetNotifier.new,
|
||||
);
|
||||
|
||||
class BudgetNotifier extends StateNotifier<double?> {
|
||||
final dynamic _storage;
|
||||
|
||||
BudgetNotifier(super.initialBudget, this._storage);
|
||||
class BudgetNotifier extends Notifier<double?> {
|
||||
@override
|
||||
double? build() {
|
||||
final storage = ref.watch(storageServiceProvider);
|
||||
return storage.loadBudget();
|
||||
}
|
||||
|
||||
Future<void> setBudget(double? budget) async {
|
||||
await _storage.saveBudget(budget);
|
||||
final storage = ref.read(storageServiceProvider);
|
||||
await storage.saveBudget(budget);
|
||||
state = budget;
|
||||
}
|
||||
|
||||
@@ -50,70 +52,60 @@ const Map<String, CurrencyInfo> currencyMap = {
|
||||
'RUB': CurrencyInfo('₽', 'RUB'),
|
||||
};
|
||||
|
||||
class CurrencyNotifier extends StateNotifier<CurrencyInfo> {
|
||||
final SharedPreferences _prefs;
|
||||
|
||||
CurrencyNotifier(this._prefs) : super(currencyMap['USD']!) {
|
||||
_load();
|
||||
}
|
||||
|
||||
void _load() {
|
||||
final code = _prefs.getString('currency_code') ?? 'USD';
|
||||
state = currencyMap[code] ?? currencyMap['USD']!;
|
||||
class CurrencyNotifier extends Notifier<CurrencyInfo> {
|
||||
@override
|
||||
CurrencyInfo build() {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
final code = prefs.getString('currency_code') ?? 'USD';
|
||||
return currencyMap[code] ?? currencyMap['USD']!;
|
||||
}
|
||||
|
||||
Future<void> setCurrency(String code) async {
|
||||
final prefs = ref.read(sharedPreferencesProvider);
|
||||
state = currencyMap[code] ?? currencyMap['USD']!;
|
||||
await _prefs.setString('currency_code', code);
|
||||
await prefs.setString('currency_code', code);
|
||||
}
|
||||
}
|
||||
|
||||
final currencyProvider = StateNotifierProvider<CurrencyNotifier, CurrencyInfo>((
|
||||
ref,
|
||||
) {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
return CurrencyNotifier(prefs);
|
||||
});
|
||||
final currencyProvider = NotifierProvider<CurrencyNotifier, CurrencyInfo>(
|
||||
CurrencyNotifier.new,
|
||||
);
|
||||
|
||||
class ThemeModeNotifier extends StateNotifier<ThemeMode> {
|
||||
final SharedPreferences _prefs;
|
||||
|
||||
ThemeModeNotifier(this._prefs) : super(ThemeMode.system) {
|
||||
_load();
|
||||
}
|
||||
|
||||
void _load() {
|
||||
final saved = _prefs.getString('theme_mode');
|
||||
class ThemeModeNotifier extends Notifier<ThemeMode> {
|
||||
@override
|
||||
ThemeMode build() {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
final saved = prefs.getString('theme_mode');
|
||||
if (saved == 'dark') {
|
||||
state = ThemeMode.dark;
|
||||
return ThemeMode.dark;
|
||||
} else if (saved == 'light') {
|
||||
state = ThemeMode.light;
|
||||
return ThemeMode.light;
|
||||
} else {
|
||||
state = ThemeMode.system;
|
||||
return ThemeMode.system;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setThemeMode(ThemeMode mode) async {
|
||||
final prefs = ref.read(sharedPreferencesProvider);
|
||||
state = mode;
|
||||
await _prefs.setString('theme_mode', mode.name);
|
||||
await prefs.setString('theme_mode', mode.name);
|
||||
}
|
||||
}
|
||||
|
||||
final themeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>((
|
||||
ref,
|
||||
) {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
return ThemeModeNotifier(prefs);
|
||||
});
|
||||
final themeProvider = NotifierProvider<ThemeModeNotifier, ThemeMode>(
|
||||
ThemeModeNotifier.new,
|
||||
);
|
||||
|
||||
enum CardTextColorMode { white, adaptive, black }
|
||||
|
||||
class CardTextColorNotifier extends StateNotifier<CardTextColorMode> {
|
||||
class CardTextColorNotifier extends Notifier<CardTextColorMode> {
|
||||
static const _key = 'card_text_color';
|
||||
final SharedPreferences _prefs;
|
||||
|
||||
CardTextColorNotifier(this._prefs)
|
||||
: super(_fromString(_prefs.getString(_key)));
|
||||
@override
|
||||
CardTextColorMode build() {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
return _fromString(prefs.getString(_key));
|
||||
}
|
||||
|
||||
static CardTextColorMode _fromString(String? value) {
|
||||
return CardTextColorMode.values.firstWhere(
|
||||
@@ -123,16 +115,16 @@ class CardTextColorNotifier extends StateNotifier<CardTextColorMode> {
|
||||
}
|
||||
|
||||
void set(CardTextColorMode mode) {
|
||||
final prefs = ref.read(sharedPreferencesProvider);
|
||||
state = mode;
|
||||
_prefs.setString(_key, mode.name);
|
||||
prefs.setString(_key, mode.name);
|
||||
}
|
||||
}
|
||||
|
||||
final cardTextColorProvider =
|
||||
StateNotifierProvider<CardTextColorNotifier, CardTextColorMode>((ref) {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
return CardTextColorNotifier(prefs);
|
||||
});
|
||||
NotifierProvider<CardTextColorNotifier, CardTextColorMode>(
|
||||
CardTextColorNotifier.new,
|
||||
);
|
||||
|
||||
final exchangeRateServiceProvider = Provider<ExchangeRateService>((ref) {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
@@ -143,15 +135,15 @@ final ratesInitProvider = FutureProvider<void>((ref) async {
|
||||
await ref.read(exchangeRateServiceProvider).fetchRates();
|
||||
});
|
||||
|
||||
final hapticEnabledProvider = StateNotifierProvider<HapticNotifier, bool>((
|
||||
ref,
|
||||
) {
|
||||
return HapticNotifier();
|
||||
});
|
||||
final hapticEnabledProvider = NotifierProvider<HapticNotifier, bool>(
|
||||
HapticNotifier.new,
|
||||
);
|
||||
|
||||
class HapticNotifier extends StateNotifier<bool> {
|
||||
HapticNotifier() : super(true) {
|
||||
class HapticNotifier extends Notifier<bool> {
|
||||
@override
|
||||
bool build() {
|
||||
_load();
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
@@ -166,13 +158,15 @@ class HapticNotifier extends StateNotifier<bool> {
|
||||
}
|
||||
|
||||
final showCurrencyConversionsProvider =
|
||||
StateNotifierProvider<ShowCurrencyConversionsNotifier, bool>((ref) {
|
||||
return ShowCurrencyConversionsNotifier();
|
||||
});
|
||||
NotifierProvider<ShowCurrencyConversionsNotifier, bool>(
|
||||
ShowCurrencyConversionsNotifier.new,
|
||||
);
|
||||
|
||||
class ShowCurrencyConversionsNotifier extends StateNotifier<bool> {
|
||||
ShowCurrencyConversionsNotifier() : super(true) {
|
||||
class ShowCurrencyConversionsNotifier extends Notifier<bool> {
|
||||
@override
|
||||
bool build() {
|
||||
_load();
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
@@ -198,7 +192,7 @@ class ExportService {
|
||||
|
||||
Future<String> exportToCSV() async {
|
||||
final transactionsAsync = _ref.read(transactionsProvider);
|
||||
final transactions = transactionsAsync.valueOrNull ?? [];
|
||||
final transactions = transactionsAsync.value ?? [];
|
||||
final fmt = _ref.read(amountFormatProvider);
|
||||
|
||||
final buffer = StringBuffer();
|
||||
|
||||
@@ -2,9 +2,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../core/constants.dart';
|
||||
|
||||
class AmountFormatNotifier extends StateNotifier<AmountFormat> {
|
||||
AmountFormatNotifier() : super(AmountFormat.commasDot) {
|
||||
class AmountFormatNotifier extends Notifier<AmountFormat> {
|
||||
@override
|
||||
AmountFormat build() {
|
||||
_load();
|
||||
return AmountFormat.commasDot;
|
||||
}
|
||||
|
||||
void _load() async {
|
||||
@@ -20,6 +22,6 @@ class AmountFormatNotifier extends StateNotifier<AmountFormat> {
|
||||
}
|
||||
}
|
||||
|
||||
final amountFormatProvider = StateNotifierProvider<AmountFormatNotifier, AmountFormat>(
|
||||
(ref) => AmountFormatNotifier(),
|
||||
final amountFormatProvider = NotifierProvider<AmountFormatNotifier, AmountFormat>(
|
||||
AmountFormatNotifier.new,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user