mirror of
https://github.com/koloideal/Casha.git
synced 2026-08-08 09:41:13 +03:00
step
This commit is contained in:
@@ -167,6 +167,7 @@ class AppStrings {
|
|||||||
|
|
||||||
String get colorPrimary => _ru ? 'Основной' : 'Primary';
|
String get colorPrimary => _ru ? 'Основной' : 'Primary';
|
||||||
String get colorSecondary => _ru ? 'Второй' : 'Secondary';
|
String get colorSecondary => _ru ? 'Второй' : 'Secondary';
|
||||||
|
String get colorSecond => _ru ? 'Второй' : 'Second';
|
||||||
String get colorSolid => _ru ? 'Однотон' : 'Solid';
|
String get colorSolid => _ru ? 'Однотон' : 'Solid';
|
||||||
String get gradientLinear => _ru ? 'Линейный' : 'Linear';
|
String get gradientLinear => _ru ? 'Линейный' : 'Linear';
|
||||||
String get gradientReverse => _ru ? 'Обратный' : 'Reverse';
|
String get gradientReverse => _ru ? 'Обратный' : 'Reverse';
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final mq = MediaQuery.of(widget.context);
|
final mq = MediaQuery.of(widget.context);
|
||||||
final cardTop = mq.padding.top + kToolbarHeight + 16;
|
final cardTop = mq.padding.top + kToolbarHeight + 16;
|
||||||
const cardHeight = 230.0;
|
const cardHeight = 190.0;
|
||||||
const editorPanelHeight = 102.0;
|
const editorPanelHeight = 102.0;
|
||||||
final editorPanelTop = cardTop + cardHeight + 20;
|
final editorPanelTop = cardTop + cardHeight + 20;
|
||||||
final colorPanelTop = editorPanelTop + editorPanelHeight + 12;
|
final colorPanelTop = editorPanelTop + editorPanelHeight + 12;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import '../../../../core/constants.dart';
|
import '../../../../core/constants.dart';
|
||||||
import '../../../../shared/widgets/byn_sign.dart';
|
import '../../../../shared/widgets/byn_sign.dart';
|
||||||
|
|
||||||
class AccountEditorPanel extends ConsumerWidget {
|
class AccountEditorPanel extends ConsumerStatefulWidget {
|
||||||
final TextEditingController nameController;
|
final TextEditingController nameController;
|
||||||
final String selectedCurrency;
|
final String selectedCurrency;
|
||||||
final bool showCurrencyDropdown;
|
final bool showCurrencyDropdown;
|
||||||
@@ -26,15 +26,29 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<AccountEditorPanel> createState() => _AccountEditorPanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountEditorPanelState extends ConsumerState<AccountEditorPanel> {
|
||||||
|
bool _showNameError = false;
|
||||||
|
|
||||||
|
void _triggerNameError() {
|
||||||
|
setState(() => _showNameError = true);
|
||||||
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
|
if (mounted) setState(() => _showNameError = false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: panelHeight,
|
height: widget.panelHeight,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(dashboardContext).colorScheme.surface,
|
color: Theme.of(widget.dashboardContext).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.1),
|
).colorScheme.onSurface.withOpacity(0.1),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
),
|
),
|
||||||
@@ -58,7 +72,7 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.6),
|
).colorScheme.onSurface.withOpacity(0.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -70,7 +84,7 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: nameController,
|
controller: widget.nameController,
|
||||||
buildCounter:
|
buildCounter:
|
||||||
(
|
(
|
||||||
context, {
|
context, {
|
||||||
@@ -84,23 +98,23 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.4),
|
).colorScheme.onSurface.withOpacity(0.4),
|
||||||
),
|
),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Theme.of(
|
fillColor: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.05),
|
).colorScheme.onSurface.withOpacity(0.05),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color:
|
color:
|
||||||
(showLimitError ||
|
(widget.showLimitError ||
|
||||||
showDuplicateError ||
|
widget.showDuplicateError ||
|
||||||
nameController.text.trim().isEmpty)
|
_showNameError)
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: Theme.of(
|
: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.15),
|
).colorScheme.onSurface.withOpacity(0.15),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
),
|
),
|
||||||
@@ -109,12 +123,12 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color:
|
color:
|
||||||
(showLimitError ||
|
(widget.showLimitError ||
|
||||||
showDuplicateError ||
|
widget.showDuplicateError ||
|
||||||
nameController.text.trim().isEmpty)
|
_showNameError)
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: Theme.of(
|
: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.15),
|
).colorScheme.onSurface.withOpacity(0.15),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
),
|
),
|
||||||
@@ -123,9 +137,9 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color:
|
color:
|
||||||
(showLimitError ||
|
(widget.showLimitError ||
|
||||||
showDuplicateError ||
|
widget.showDuplicateError ||
|
||||||
nameController.text.trim().isEmpty)
|
_showNameError)
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: const Color(0xFF7C6DED),
|
: const Color(0xFF7C6DED),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
@@ -141,19 +155,19 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: onCurrencyDropdownToggle,
|
onTap: widget.onCurrencyDropdownToggle,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.05),
|
).colorScheme.onSurface.withOpacity(0.05),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: showCurrencyDropdown
|
color: widget.showCurrencyDropdown
|
||||||
? const Color(0xFF7C6DED)
|
? const Color(0xFF7C6DED)
|
||||||
: Theme.of(
|
: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.15),
|
).colorScheme.onSurface.withOpacity(0.15),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
),
|
),
|
||||||
@@ -162,17 +176,17 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
selectedCurrency == 'BYN'
|
widget.selectedCurrency == 'BYN'
|
||||||
? BynSign(
|
? BynSign(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface,
|
).colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
kDisplayCurrencies
|
kDisplayCurrencies
|
||||||
.firstWhere(
|
.firstWhere(
|
||||||
(c) => c.$1 == selectedCurrency,
|
(c) => c.$1 == widget.selectedCurrency,
|
||||||
)
|
)
|
||||||
.$2,
|
.$2,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -182,12 +196,12 @@ class AccountEditorPanel extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Icon(
|
Icon(
|
||||||
showCurrencyDropdown
|
widget.showCurrencyDropdown
|
||||||
? Icons.arrow_drop_up
|
? Icons.arrow_drop_up
|
||||||
: Icons.arrow_drop_down,
|
: Icons.arrow_drop_down,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
dashboardContext,
|
widget.dashboardContext,
|
||||||
).colorScheme.onSurface.withOpacity(0.6),
|
).colorScheme.onSurface.withOpacity(0.6),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class BalanceCardState extends ConsumerState<BalanceCard>
|
|||||||
..rotateY(_tiltY * 0.42),
|
..rotateY(_tiltY * 0.42),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 220,
|
height: 180,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
gradient: _buildGradient(primary, secondary, gradientType),
|
gradient: _buildGradient(primary, secondary, gradientType),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 230,
|
height: 190,
|
||||||
child: OverflowBox(
|
child: OverflowBox(
|
||||||
maxWidth: MediaQuery.of(context).size.width,
|
maxWidth: MediaQuery.of(context).size.width,
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
@@ -154,14 +154,14 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading: () => const SizedBox(
|
loading: () => const SizedBox(
|
||||||
height: 220,
|
height: 180,
|
||||||
child: Center(child: CircularProgressIndicator()),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
),
|
),
|
||||||
error: (error, stack) {
|
error: (error, stack) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 220,
|
height: 180,
|
||||||
child: BalanceCard(
|
child: BalanceCard(
|
||||||
balance: widget.balance,
|
balance: widget.balance,
|
||||||
currencyInfo: widget.currencyInfo,
|
currencyInfo: widget.currencyInfo,
|
||||||
@@ -198,7 +198,7 @@ class AddAccountCard extends StatelessWidget {
|
|||||||
painter: _DashedBorderPainter(),
|
painter: _DashedBorderPainter(),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 205,
|
height: 165,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.4),
|
color: Theme.of(context).colorScheme.surface.withOpacity(0.4),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
|||||||
+181
-1
@@ -2,10 +2,188 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
import 'app/app.dart';
|
import 'app/app.dart';
|
||||||
import 'core/services/haptic_service.dart';
|
import 'core/services/haptic_service.dart';
|
||||||
import 'data/database/app_database.dart';
|
import 'data/database/app_database.dart' hide Account, Transaction;
|
||||||
|
import 'data/repositories/account_repository.dart';
|
||||||
|
import 'data/repositories/transaction_repository.dart';
|
||||||
import 'features/dashboard/provider.dart';
|
import 'features/dashboard/provider.dart';
|
||||||
|
import 'shared/models/account.dart';
|
||||||
|
import 'shared/models/transaction.dart';
|
||||||
|
|
||||||
|
Future<void> seedTestData(AppDatabase database) async {
|
||||||
|
final accountRepo = AccountRepository(database);
|
||||||
|
final transactionRepo = TransactionRepository(database);
|
||||||
|
|
||||||
|
final existingAccounts = await accountRepo.getAll();
|
||||||
|
if (existingAccounts.length > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
final uuid = const Uuid();
|
||||||
|
|
||||||
|
final cashId = await accountRepo.add(
|
||||||
|
Account(
|
||||||
|
id: 0,
|
||||||
|
name: 'Cash',
|
||||||
|
isMain: false,
|
||||||
|
sortOrder: 1,
|
||||||
|
currency: 'USD',
|
||||||
|
createdAt: now,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final cardId = await accountRepo.add(
|
||||||
|
Account(
|
||||||
|
id: 0,
|
||||||
|
name: 'Card',
|
||||||
|
isMain: false,
|
||||||
|
sortOrder: 2,
|
||||||
|
currency: 'USD',
|
||||||
|
createdAt: now,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final savingsId = await accountRepo.add(
|
||||||
|
Account(
|
||||||
|
id: 0,
|
||||||
|
name: 'Savings',
|
||||||
|
isMain: false,
|
||||||
|
sortOrder: 3,
|
||||||
|
currency: 'USD',
|
||||||
|
createdAt: now,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final transactions = [
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 3500.0,
|
||||||
|
category: 'Salary',
|
||||||
|
type: TransactionType.income,
|
||||||
|
date: now.subtract(const Duration(days: 28)),
|
||||||
|
note: 'Monthly salary',
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 85.50,
|
||||||
|
category: 'Groceries',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 27)),
|
||||||
|
note: 'Weekly shopping',
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 45.00,
|
||||||
|
category: 'Transportation',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 25)),
|
||||||
|
accountId: cashId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 120.00,
|
||||||
|
category: 'Utilities',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 22)),
|
||||||
|
note: 'Electricity bill',
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 200.0,
|
||||||
|
category: 'Freelance',
|
||||||
|
type: TransactionType.income,
|
||||||
|
date: now.subtract(const Duration(days: 20)),
|
||||||
|
note: 'Side project',
|
||||||
|
accountId: cashId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 500.0,
|
||||||
|
category: 'Savings',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 18)),
|
||||||
|
note: 'Monthly savings',
|
||||||
|
accountId: savingsId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 65.25,
|
||||||
|
category: 'Dining',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 15)),
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 30.00,
|
||||||
|
category: 'Entertainment',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 12)),
|
||||||
|
note: 'Movie tickets',
|
||||||
|
accountId: cashId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 95.00,
|
||||||
|
category: 'Groceries',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 10)),
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 150.0,
|
||||||
|
category: 'Bonus',
|
||||||
|
type: TransactionType.income,
|
||||||
|
date: now.subtract(const Duration(days: 8)),
|
||||||
|
note: 'Performance bonus',
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 75.00,
|
||||||
|
category: 'Shopping',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 6)),
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 40.00,
|
||||||
|
category: 'Transportation',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 4)),
|
||||||
|
accountId: cashId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 55.80,
|
||||||
|
category: 'Dining',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: now.subtract(const Duration(days: 2)),
|
||||||
|
note: 'Dinner with friends',
|
||||||
|
accountId: cardId,
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
id: uuid.v4(),
|
||||||
|
amount: 100.0,
|
||||||
|
category: 'Gift',
|
||||||
|
type: TransactionType.income,
|
||||||
|
date: now.subtract(const Duration(days: 1)),
|
||||||
|
accountId: cashId,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (final transaction in transactions) {
|
||||||
|
await transactionRepo.add(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -19,6 +197,8 @@ void main() async {
|
|||||||
await HapticService.init();
|
await HapticService.init();
|
||||||
|
|
||||||
final database = AppDatabase();
|
final database = AppDatabase();
|
||||||
|
|
||||||
|
await seedTestData(database);
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
|
|||||||
Reference in New Issue
Block a user