feat: Add settings menu screen and rename category settings
This commit is contained in:
@@ -18,7 +18,7 @@ import '../widgets/transaction_list_item.dart';
|
||||
import '../widgets/filter_chip_widget.dart';
|
||||
import '../utils/category_utils.dart'; // Import category utils
|
||||
import 'profile_screen.dart'; // Import profile screen
|
||||
import 'settings_screen.dart'; // Import settings screen
|
||||
import 'settings_menu_screen.dart'; // Import the new settings menu screen
|
||||
import '../widgets/add_category_dialog.dart'; // Import the new dialog
|
||||
|
||||
// Enum for transaction type selection in the form
|
||||
@@ -183,11 +183,11 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
// Navigate to Settings Screen
|
||||
// Navigate to Settings Menu Screen
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SettingsScreen(database: widget.database),
|
||||
builder: (context) => SettingsMenuScreen(database: widget.database), // Navigate to the new menu screen
|
||||
),
|
||||
);
|
||||
break;
|
||||
@@ -1141,3 +1141,511 @@ class _AddTransactionFormState extends State<_AddTransactionForm> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helper widget to manage multiple streams for the main body
|
||||
class MultiStreamBuilder extends StatelessWidget {
|
||||
final List<Stream<dynamic>> streams;
|
||||
final Widget Function(BuildContext, List<AsyncSnapshot<dynamic>>) builder;
|
||||
|
||||
const MultiStreamBuilder({
|
||||
Key? key,
|
||||
required this.streams,
|
||||
required this.builder,
|
||||
}) super(key: key); // Use super constructor
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Combine streams ensuring all emit at least one value (or handle initial nulls)
|
||||
// Using StreamZip might wait until all streams emit. Behavior depends on stream types.
|
||||
// Consider using combineLatest or similar if waiting isn't desired.
|
||||
return StreamBuilder<List<dynamic>>( // Use List<dynamic> and check types later
|
||||
stream: StreamZip(streams), // StreamZip waits for all streams to emit at least once
|
||||
builder: (context, combinedSnapshot) {
|
||||
if (combinedSnapshot.connectionState == ConnectionState.waiting && !combinedSnapshot.hasData) {
|
||||
// Show loading only if waiting AND no data has arrived yet
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (combinedSnapshot.hasError) {
|
||||
return Center(child: Text('Error combining streams: ${combinedSnapshot.error}'));
|
||||
}
|
||||
|
||||
// Create AsyncSnapshot objects manually for the builder
|
||||
// This allows handling individual stream states if needed, though StreamZip simplifies it
|
||||
final snapshots = List<AsyncSnapshot<dynamic>>.generate(
|
||||
streams.length,
|
||||
(index) {
|
||||
if (combinedSnapshot.hasData) {
|
||||
// If combined stream has data, assume individual streams are done (or active with data)
|
||||
return AsyncSnapshot.withData(ConnectionState.active, combinedSnapshot.data![index]);
|
||||
} else if (combinedSnapshot.hasError) {
|
||||
// Propagate error to individual snapshots (might need refinement)
|
||||
return AsyncSnapshot.withError(ConnectionState.active, combinedSnapshot.error!);
|
||||
} else {
|
||||
// Default to waiting state if combined stream is waiting
|
||||
return const AsyncSnapshot.waiting();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Call the original builder function with the list of snapshots
|
||||
return builder(context, snapshots);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// This helper function is no longer needed as StreamZip handles the combination
|
||||
// Stream<List<AsyncSnapshot<dynamic>>> _combineStreams() { ... }
|
||||
}
|
||||
|
||||
|
||||
// --- Widget for the Add Transaction Form ---
|
||||
class _AddTransactionForm extends StatefulWidget {
|
||||
final db.AppDatabase database;
|
||||
final Stream<List<db.CategoryDb>> categoriesStream; // Receive stream
|
||||
|
||||
const _AddTransactionForm({
|
||||
Key? key,
|
||||
required this.database,
|
||||
required this.categoriesStream, // Require stream
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_AddTransactionForm> createState() => _AddTransactionFormState();
|
||||
}
|
||||
|
||||
class _AddTransactionFormState extends State<_AddTransactionForm> {
|
||||
final _formKey = GlobalKey<FormState>(); // Key for form validation
|
||||
final _amountController = TextEditingController();
|
||||
final _merchantController = TextEditingController(); // Label changes based on type
|
||||
String? _selectedCategoryName; // Store the NAME of the selected category
|
||||
DateTime _selectedDate = DateTime.now(); // Default to today, includes time
|
||||
TransactionType _selectedType = TransactionType.expense; // Default to expense
|
||||
|
||||
// No longer need static list: final List<String> _categories = CategoryUtils.getAllCategoryNames();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Set the initial category if the list is not empty and type is expense
|
||||
// We need to listen to the stream for the initial value
|
||||
// Setting initial value here is tricky with streams, better handle in StreamBuilder
|
||||
// if (_selectedType == TransactionType.expense && _categories.isNotEmpty) {
|
||||
// _selectedCategoryName = _categories[0];
|
||||
// }
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_amountController.dispose();
|
||||
_merchantController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Function to show the date and time pickers
|
||||
Future<void> _selectDateTime(BuildContext context) async {
|
||||
// 1. Pick Date
|
||||
final DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
firstDate: DateTime(2000), // Allow dates from year 2000
|
||||
lastDate: DateTime.now().add(const Duration(days: 365)), // Allow up to one year in future
|
||||
);
|
||||
|
||||
if (pickedDate != null) {
|
||||
// If date was picked, proceed to pick time
|
||||
// 2. Pick Time
|
||||
final TimeOfDay? pickedTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.fromDateTime(_selectedDate), // Use current time from state
|
||||
);
|
||||
|
||||
if (pickedTime != null) {
|
||||
// If time was also picked, combine date and time and update state
|
||||
setState(() {
|
||||
_selectedDate = DateTime(
|
||||
pickedDate.year,
|
||||
pickedDate.month,
|
||||
pickedDate.day,
|
||||
pickedTime.hour,
|
||||
pickedTime.minute,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// If only date was picked, update state with the picked date and existing time
|
||||
setState(() {
|
||||
_selectedDate = DateTime(
|
||||
pickedDate.year,
|
||||
pickedDate.month,
|
||||
pickedDate.day,
|
||||
_selectedDate.hour, // Keep existing hour
|
||||
_selectedDate.minute, // Keep existing minute
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
// If date picker was cancelled (pickedDate == null), do nothing.
|
||||
}
|
||||
|
||||
|
||||
// Function to handle form submission
|
||||
void _submitTransaction() async {
|
||||
// Validate the form
|
||||
if (_formKey.currentState!.validate()) {
|
||||
// Parse amount
|
||||
final amount = double.tryParse(_amountController.text);
|
||||
if (amount == null || amount <= 0) {
|
||||
// Show error if amount is invalid
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please enter a valid positive amount.'),
|
||||
backgroundColor: Colors.red,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine category and type string
|
||||
String categoryToSave;
|
||||
String typeString = _selectedType == TransactionType.income ? 'income' : 'expense';
|
||||
|
||||
if (_selectedType == TransactionType.income) {
|
||||
categoryToSave = 'Income'; // Use a fixed category for income
|
||||
} else {
|
||||
// Ensure a category is selected for expenses
|
||||
if (_selectedCategoryName == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please select a category for the expense.'),
|
||||
backgroundColor: Colors.red,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
categoryToSave = _selectedCategoryName!;
|
||||
}
|
||||
|
||||
// Create the transaction companion including the type
|
||||
final merchantValue = _merchantController.text.isNotEmpty
|
||||
? _merchantController.text
|
||||
: (_selectedType == TransactionType.income ? 'Unknown Source' : 'Unknown Merchant');
|
||||
|
||||
final newTransaction = db.TransactionsCompanion(
|
||||
categoryName: Value(categoryToSave),
|
||||
amount: Value(amount),
|
||||
date: Value(_selectedDate),
|
||||
merchant: Value(merchantValue),
|
||||
type: Value(typeString),
|
||||
);
|
||||
|
||||
try {
|
||||
// Add transaction to the database
|
||||
await widget.database.addTransaction(newTransaction);
|
||||
|
||||
// Close the bottom sheet
|
||||
if (mounted) Navigator.pop(context);
|
||||
|
||||
// Show success message
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${_selectedType == TransactionType.income ? "Income" : "Expense"} added: ${typeString == 'income' ? '' : '$categoryToSave - '}\$${amount.toStringAsFixed(2)}'),
|
||||
duration: const Duration(seconds: 2),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error adding transaction: $e');
|
||||
// Show error message
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error adding transaction: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Function to handle Add Category button press ---
|
||||
void _showAddCategoryDialog() async {
|
||||
// Show the dialog and wait for the result
|
||||
final newCategory = await showDialog<db.CategoryDb?>( // Expecting CategoryDb or null
|
||||
context: context,
|
||||
builder: (context) => AddCategoryDialog(database: widget.database),
|
||||
);
|
||||
|
||||
// If a new category was created and returned
|
||||
if (newCategory != null) {
|
||||
// Set the newly created category as selected in the dropdown
|
||||
setState(() {
|
||||
_selectedCategoryName = newCategory.name;
|
||||
});
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Категория "${newCategory.name}" создана и выбрана.'),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- End of Add Category Function ---
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
final bool isIncome = _selectedType == TransactionType.income;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min, // Take minimum space needed
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// --- Header ---
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Add New Transaction',
|
||||
style: theme.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context), // Close button
|
||||
tooltip: 'Close',
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// --- Transaction Type Selector ---
|
||||
Center(
|
||||
child: ToggleButtons(
|
||||
isSelected: [!isIncome, isIncome], // [Expense selected, Income selected]
|
||||
onPressed: (int index) {
|
||||
setState(() {
|
||||
_selectedType = index == 0 ? TransactionType.expense : TransactionType.income;
|
||||
// Reset category selection if switching to income
|
||||
if (_selectedType == TransactionType.income) {
|
||||
_selectedCategoryName = null;
|
||||
} else {
|
||||
// Don't reset to default here, let StreamBuilder handle initial state
|
||||
// _selectedCategoryName = _categories[0]; // Remove this
|
||||
}
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
constraints: BoxConstraints(minWidth: (MediaQuery.of(context).size.width - 60) / 2, minHeight: 40), // Adjust width based on screen
|
||||
selectedColor: Colors.white,
|
||||
fillColor: isIncome ? Colors.green.shade400 : Colors.red.shade400,
|
||||
color: isDark ? Colors.white70 : Colors.black54,
|
||||
selectedBorderColor: isIncome ? Colors.green.shade600 : Colors.red.shade600,
|
||||
borderColor: isDark ? Colors.grey.shade600 : Colors.grey.shade400,
|
||||
children: const <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [ Icon(Icons.arrow_upward_rounded, size: 18), SizedBox(width: 8), Text('Expense'), ],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [ Icon(Icons.arrow_downward_rounded, size: 18), SizedBox(width: 8), Text('Income'), ],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
|
||||
// --- Amount Field ---
|
||||
TextFormField(
|
||||
controller: _amountController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Amount',
|
||||
prefixIcon: Icon(Icons.attach_money, color: isIncome ? Colors.green : theme.colorScheme.primary),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey.shade800.withOpacity(0.5) : Colors.grey.shade100,
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter an amount';
|
||||
}
|
||||
if (double.tryParse(value) == null || double.parse(value) <= 0) {
|
||||
return 'Please enter a valid positive number';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// --- Category Dropdown and Add Button (Only for Expenses, uses StreamBuilder) ---
|
||||
if (!isIncome)
|
||||
StreamBuilder<List<db.CategoryDb>>(
|
||||
stream: widget.categoriesStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting && !snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator(strokeWidth: 2));
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Text('Error loading categories: ${snapshot.error}');
|
||||
}
|
||||
|
||||
final categoriesFromDb = snapshot.data ?? [];
|
||||
// Filter out 'Income' category for the dropdown
|
||||
final expenseCategories = categoriesFromDb.where((c) => c.name != 'Income').toList();
|
||||
|
||||
// Ensure _selectedCategoryName is valid or reset it
|
||||
if (_selectedCategoryName != null && !expenseCategories.any((c) => c.name == _selectedCategoryName)) {
|
||||
_selectedCategoryName = null; // Reset if selected category is no longer valid
|
||||
}
|
||||
// Set default selection if nothing is selected and list is not empty
|
||||
if (_selectedCategoryName == null && expenseCategories.isNotEmpty) {
|
||||
// Use WidgetsBinding to schedule state update after build
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) { // Check if widget is still mounted
|
||||
setState(() {
|
||||
_selectedCategoryName = expenseCategories[0].name;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start, // Align items to the top
|
||||
children: [
|
||||
// Dropdown takes most space
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: _selectedCategoryName, // Use the name state variable
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Category',
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey.shade800.withOpacity(0.5) : Colors.grey.shade100,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0), // Adjust padding if needed
|
||||
),
|
||||
// Map CategoryDb objects to DropdownMenuItem<String>
|
||||
items: expenseCategories.map((db.CategoryDb category) {
|
||||
// Get icon data using the new utility function
|
||||
final iconData = CategoryUtils.getIconFromString(category.icon);
|
||||
final colorData = Color(category.color);
|
||||
return DropdownMenuItem<String>(
|
||||
value: category.name, // Value is the category name (String)
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(iconData, color: colorData, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Text(category.name),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_selectedCategoryName = newValue; // Update the selected name
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
// Only validate if it's an expense
|
||||
if (_selectedType == TransactionType.expense && value == null) {
|
||||
return 'Please select a category';
|
||||
}
|
||||
return null; // No validation needed for income
|
||||
},
|
||||
),
|
||||
),
|
||||
// Add Category Button
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0, top: 8.0), // Add padding to space it out and align vertically
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.add_circle_outline, color: theme.colorScheme.primary),
|
||||
tooltip: 'Создать категорию', // Tooltip in Russian as requested
|
||||
onPressed: _showAddCategoryDialog, // Call the function to show the dialog
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!isIncome) const SizedBox(height: 16), // Spacer only if category row is shown
|
||||
|
||||
// --- Date and Time Picker ---
|
||||
InkWell(
|
||||
onTap: () => _selectDateTime(context), // Use the combined picker function
|
||||
child: InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Date & Time', // Updated label
|
||||
prefixIcon: const Icon(Icons.calendar_today_outlined),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey.shade800.withOpacity(0.5) : Colors.grey.shade100,
|
||||
),
|
||||
child: Text(
|
||||
// Format date and time nicely
|
||||
DateFormat.yMMMd().add_jm().format(_selectedDate),
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// --- Merchant / Source Field ---
|
||||
TextFormField(
|
||||
controller: _merchantController,
|
||||
decoration: InputDecoration(
|
||||
labelText: isIncome ? 'Source' : 'Merchant / Store', // Dynamic label
|
||||
prefixIcon: Icon(isIncome ? Icons.source_outlined : Icons.storefront_outlined), // Dynamic icon
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey.shade800.withOpacity(0.5) : Colors.grey.shade100,
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
// No validator needed, can be empty
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// --- Save Button ---
|
||||
SizedBox(
|
||||
width: double.infinity, // Make button full width
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _submitTransaction,
|
||||
icon: const Icon(Icons.save_alt_rounded),
|
||||
label: Text(isIncome ? 'Save Income' : 'Save Expense'), // Dynamic label
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
backgroundColor: isIncome ? Colors.green : theme.colorScheme.primary, // Dynamic color
|
||||
foregroundColor: theme.colorScheme.onPrimary, // Text color on primary
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10), // Padding at the bottom
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../database/database.dart' as db;
|
||||
import 'category_settings_screen.dart'; // Импортируем переименованный экран
|
||||
|
||||
class SettingsMenuScreen extends StatelessWidget {
|
||||
final db.AppDatabase database;
|
||||
|
||||
const SettingsMenuScreen({Key? key, required this.database}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Настройки'),
|
||||
centerTitle: true,
|
||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||
elevation: theme.appBarTheme.elevation,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
children: [
|
||||
// Опция "Редактирование категорий"
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
radius: 22,
|
||||
backgroundColor: theme.colorScheme.primary.withOpacity(0.15),
|
||||
child: Icon(Icons.category_outlined, color: theme.colorScheme.primary, size: 24),
|
||||
),
|
||||
title: Text('Редактирование категорий', style: theme.textTheme.titleMedium),
|
||||
trailing: Icon(Icons.arrow_forward_ios, size: 18, color: theme.colorScheme.onSurface.withOpacity(0.6)),
|
||||
onTap: () {
|
||||
// Переход на экран редактирования категорий
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CategorySettingsScreen(database: database),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Добавьте другие опции настроек здесь в будущем
|
||||
// ListTile(...),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,18 @@ import '../database/database.dart' as db;
|
||||
import '../utils/category_utils.dart';
|
||||
import '../widgets/edit_category_dialog.dart'; // Импортируем диалог
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
// Переименован класс SettingsScreen в CategorySettingsScreen
|
||||
class CategorySettingsScreen extends StatefulWidget {
|
||||
final db.AppDatabase database;
|
||||
|
||||
const SettingsScreen({Key? key, required this.database}) : super(key: key);
|
||||
const CategorySettingsScreen({Key? key, required this.database}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
State<CategorySettingsScreen> createState() => _CategorySettingsScreenState();
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
// Переименован класс _SettingsScreenState в _CategorySettingsScreenState
|
||||
class _CategorySettingsScreenState extends State<CategorySettingsScreen> {
|
||||
late Stream<List<db.CategoryDb>> _categoriesStream;
|
||||
|
||||
@override
|
||||
@@ -123,7 +125,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Настройки категорий'),
|
||||
title: const Text('Редактирование категорий'), // Обновленный заголовок
|
||||
centerTitle: true,
|
||||
backgroundColor: theme.appBarTheme.backgroundColor, // Ensure AppBar color matches theme
|
||||
elevation: theme.appBarTheme.elevation, // Ensure elevation matches theme
|
||||
|
||||
Reference in New Issue
Block a user