This commit is contained in:
2025-09-26 18:48:14 +07:00
parent 382a0e7909
commit 30ed6b39b5
85 changed files with 20722 additions and 112 deletions

View File

@@ -0,0 +1,269 @@
import 'package:flutter/material.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isPasswordVisible = false;
bool _isLoading = false;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
final emailRegExp = RegExp(r'^[^@]+@[^@]+\.[^@]+');
if (!emailRegExp.hasMatch(value)) {
return 'Please enter a valid email address';
}
return null;
}
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
if (value.length < 6) {
return 'Password must be at least 6 characters long';
}
return null;
}
Future<void> _handleLogin() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
// Simple demo login - accept any valid email/password
if (_emailController.text.isNotEmpty && _passwordController.text.length >= 6) {
// Navigate to home screen (will be implemented when routing is set up)
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login successful!'),
backgroundColor: Colors.green,
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login failed. Please check your credentials.'),
backgroundColor: Colors.red,
),
);
}
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Scaffold(
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// App Logo/Title
Icon(
Icons.lock_outline,
size: 80,
color: colorScheme.primary,
),
const SizedBox(height: 24),
// Welcome Text
Text(
'Welcome Back',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Sign in to your account',
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 48),
// Email Field
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: _validateEmail,
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter your email address',
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
fillColor: colorScheme.surfaceVariant.withOpacity(0.3),
),
),
const SizedBox(height: 16),
// Password Field
TextFormField(
controller: _passwordController,
obscureText: !_isPasswordVisible,
textInputAction: TextInputAction.done,
validator: _validatePassword,
onFieldSubmitted: (_) => _handleLogin(),
decoration: InputDecoration(
labelText: 'Password',
hintText: 'Enter your password',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible
? Icons.visibility_off_outlined
: Icons.visibility_outlined,
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
fillColor: colorScheme.surfaceVariant.withOpacity(0.3),
),
),
const SizedBox(height: 24),
// Forgot Password Link
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
// Handle forgot password
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Forgot password functionality not implemented yet'),
),
);
},
child: Text(
'Forgot Password?',
style: TextStyle(color: colorScheme.primary),
),
),
),
const SizedBox(height: 24),
// Login Button
FilledButton(
onPressed: _isLoading ? null : _handleLogin,
style: FilledButton.styleFrom(
minimumSize: const Size(double.infinity, 56),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _isLoading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: colorScheme.onPrimary,
),
)
: const Text(
'Sign In',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 32),
// Sign up link
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Don't have an account? ",
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
TextButton(
onPressed: () {
// Handle sign up
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Sign up functionality not implemented yet'),
),
);
},
child: Text(
'Sign Up',
style: TextStyle(
color: colorScheme.primary,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
),
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,511 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/routing/route_paths.dart';
import '../../../../core/routing/route_guards.dart';
import '../../../../shared/presentation/providers/app_providers.dart';
/// Home page with navigation to different features
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authStateProvider);
final themeMode = ref.watch(themeModeProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Base Flutter App'),
actions: [
// Theme toggle button
IconButton(
onPressed: () {
ref.read(themeModeProvider.notifier).toggleTheme();
},
icon: Icon(
themeMode == ThemeMode.dark
? Icons.light_mode
: themeMode == ThemeMode.light
? Icons.dark_mode
: Icons.brightness_auto,
),
tooltip: 'Toggle theme',
),
// Settings button
IconButton(
onPressed: () => context.push(RoutePaths.settings),
icon: const Icon(Icons.settings),
tooltip: 'Settings',
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Welcome section
_WelcomeCard(authState: authState),
const SizedBox(height: 24),
// Quick actions section
Text(
'Quick Actions',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
_QuickActionsGrid(),
const SizedBox(height: 24),
// Features section
Text(
'Features',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
_FeaturesGrid(),
const SizedBox(height: 24),
// Recent activity section
_RecentActivityCard(),
],
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => context.push(RoutePaths.addTodo),
icon: const Icon(Icons.add),
label: const Text('Add Todo'),
),
);
}
}
class _WelcomeCard extends StatelessWidget {
final AuthState authState;
const _WelcomeCard({
required this.authState,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: Icon(
Icons.person,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
authState == AuthState.authenticated
? 'Welcome back!'
: 'Welcome!',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
authState == AuthState.authenticated
? 'Ready to be productive today?'
: 'Get started with the base Flutter app.',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
),
],
),
),
],
),
if (authState == AuthState.unauthenticated) ...[
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => context.push(RoutePaths.login),
child: const Text('Login'),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: () => context.push(RoutePaths.register),
child: const Text('Register'),
),
),
],
),
],
],
),
),
);
}
}
class _QuickActionsGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
final quickActions = [
_QuickAction(
icon: Icons.add_task,
title: 'Add Todo',
subtitle: 'Create a new task',
onTap: () => context.push(RoutePaths.addTodo),
),
_QuickAction(
icon: Icons.list,
title: 'View Todos',
subtitle: 'See all tasks',
onTap: () => context.push(RoutePaths.todos),
),
_QuickAction(
icon: Icons.settings,
title: 'Settings',
subtitle: 'App preferences',
onTap: () => context.push(RoutePaths.settings),
),
_QuickAction(
icon: Icons.info,
title: 'About',
subtitle: 'App information',
onTap: () => context.push(RoutePaths.about),
),
];
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 1.2,
),
itemCount: quickActions.length,
itemBuilder: (context, index) {
return _QuickActionCard(action: quickActions[index]);
},
);
}
}
class _FeaturesGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
final features = [
_Feature(
icon: Icons.check_circle_outline,
title: 'Todo Management',
description: 'Create, edit, and track your tasks efficiently.',
color: Colors.blue,
onTap: () => context.push(RoutePaths.todos),
),
_Feature(
icon: Icons.palette,
title: 'Theming',
description: 'Switch between light, dark, and system themes.',
color: Colors.purple,
onTap: () => context.push(RoutePaths.settingsTheme),
),
_Feature(
icon: Icons.storage,
title: 'Local Storage',
description: 'Hive database integration for offline support.',
color: Colors.orange,
onTap: () => context.push(RoutePaths.settings),
),
_Feature(
icon: Icons.security,
title: 'Secure Storage',
description: 'Protected storage for sensitive data.',
color: Colors.green,
onTap: () => context.push(RoutePaths.settingsPrivacy),
),
];
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.8,
),
itemCount: features.length,
itemBuilder: (context, index) {
return _FeatureCard(feature: features[index]);
},
);
}
}
class _RecentActivityCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Recent Activity',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () => context.push(RoutePaths.todos),
child: const Text('View All'),
),
],
),
const SizedBox(height: 12),
_ActivityItem(
icon: Icons.add_task,
title: 'Welcome to Base Flutter App',
subtitle: 'Your journey starts here',
time: 'Just now',
),
_ActivityItem(
icon: Icons.info,
title: 'App initialized',
subtitle: 'Database and services ready',
time: 'A few seconds ago',
),
],
),
),
);
}
}
class _QuickActionCard extends StatelessWidget {
final _QuickAction action;
const _QuickActionCard({
required this.action,
});
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: action.onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
action.icon,
size: 32,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 8),
Text(
action.title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(
action.subtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
);
}
}
class _FeatureCard extends StatelessWidget {
final _Feature feature;
const _FeatureCard({
required this.feature,
});
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: feature.onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: feature.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
feature.icon,
color: feature.color,
size: 24,
),
),
const SizedBox(height: 12),
Text(
feature.title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Expanded(
child: Text(
feature.description,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
);
}
}
class _ActivityItem extends StatelessWidget {
final IconData icon;
final String title;
final String subtitle;
final String time;
const _ActivityItem({
required this.icon,
required this.title,
required this.subtitle,
required this.time,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
size: 16,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
),
Text(
time,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
),
),
],
),
);
}
}
class _QuickAction {
final IconData icon;
final String title;
final String subtitle;
final VoidCallback onTap;
const _QuickAction({
required this.icon,
required this.title,
required this.subtitle,
required this.onTap,
});
}
class _Feature {
final IconData icon;
final String title;
final String description;
final Color color;
final VoidCallback onTap;
const _Feature({
required this.icon,
required this.title,
required this.description,
required this.color,
required this.onTap,
});
}

View File

@@ -0,0 +1,426 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/routing/route_paths.dart';
import '../../../../core/routing/route_guards.dart';
import '../../../../shared/presentation/providers/app_providers.dart';
/// Main settings page with theme switcher and navigation to other settings
class SettingsPage extends ConsumerWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeProvider);
final authState = ref.watch(authStateProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Theme Section
_SectionHeader(title: 'Appearance'),
_ThemeSection(themeMode: themeMode, ref: ref),
const Divider(),
// Account Section
_SectionHeader(title: 'Account'),
_AccountSection(authState: authState, ref: ref),
const Divider(),
// App Settings Section
_SectionHeader(title: 'App Settings'),
_AppSettingsSection(),
const Divider(),
// Privacy & Security Section
_SectionHeader(title: 'Privacy & Security'),
_PrivacySection(),
const Divider(),
// About Section
_SectionHeader(title: 'About'),
_AboutSection(),
const SizedBox(height: 24),
],
),
),
);
}
}
class _SectionHeader extends StatelessWidget {
final String title;
const _SectionHeader({required this.title});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
child: Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
);
}
}
class _ThemeSection extends StatelessWidget {
final ThemeMode themeMode;
final WidgetRef ref;
const _ThemeSection({
required this.themeMode,
required this.ref,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
leading: Icon(
Icons.palette_outlined,
color: Theme.of(context).colorScheme.onSurface,
),
title: const Text('Theme'),
subtitle: Text(_getThemeModeText(themeMode)),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push(RoutePaths.settingsTheme),
),
ListTile(
leading: Icon(
themeMode == ThemeMode.dark
? Icons.dark_mode
: themeMode == ThemeMode.light
? Icons.light_mode
: Icons.brightness_auto,
color: Theme.of(context).colorScheme.onSurface,
),
title: const Text('Quick Theme Toggle'),
subtitle: const Text('Switch between light and dark mode'),
trailing: Switch(
value: themeMode == ThemeMode.dark,
onChanged: (value) {
ref.read(themeModeProvider.notifier).setThemeMode(
value ? ThemeMode.dark : ThemeMode.light,
);
},
),
),
],
);
}
String _getThemeModeText(ThemeMode mode) {
switch (mode) {
case ThemeMode.light:
return 'Light mode';
case ThemeMode.dark:
return 'Dark mode';
case ThemeMode.system:
return 'Follow system';
}
}
}
class _AccountSection extends StatelessWidget {
final AuthState authState;
final WidgetRef ref;
const _AccountSection({
required this.authState,
required this.ref,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
if (authState == AuthState.authenticated) ...[
ListTile(
leading: const Icon(Icons.person_outline),
title: const Text('Profile'),
subtitle: const Text('Manage your profile information'),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push(RoutePaths.profile),
),
ListTile(
leading: const Icon(Icons.logout),
title: const Text('Sign Out'),
subtitle: const Text('Sign out of your account'),
trailing: const Icon(Icons.chevron_right),
onTap: () => _showSignOutDialog(context, ref),
),
] else ...[
ListTile(
leading: const Icon(Icons.login),
title: const Text('Sign In'),
subtitle: const Text('Sign in to your account'),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push(RoutePaths.login),
),
ListTile(
leading: const Icon(Icons.person_add_outlined),
title: const Text('Create Account'),
subtitle: const Text('Sign up for a new account'),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push(RoutePaths.register),
),
],
],
);
}
void _showSignOutDialog(BuildContext context, WidgetRef ref) {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Sign Out'),
content: const Text('Are you sure you want to sign out?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () {
Navigator.of(context).pop();
ref.read(authStateProvider.notifier).logout();
},
child: const Text('Sign Out'),
),
],
);
},
);
}
}
class _AppSettingsSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
leading: const Icon(Icons.notifications_outlined),
title: const Text('Notifications'),
subtitle: const Text('Manage notification preferences'),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push(RoutePaths.settingsNotifications),
),
ListTile(
leading: const Icon(Icons.language),
title: const Text('Language'),
subtitle: const Text('English (United States)'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Language settings coming soon!')),
);
},
),
ListTile(
leading: const Icon(Icons.storage_outlined),
title: const Text('Storage'),
subtitle: const Text('Manage local data and cache'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Storage settings coming soon!')),
);
},
),
],
);
}
}
class _PrivacySection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
leading: const Icon(Icons.privacy_tip_outlined),
title: const Text('Privacy'),
subtitle: const Text('Privacy settings and data protection'),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push(RoutePaths.settingsPrivacy),
),
ListTile(
leading: const Icon(Icons.security),
title: const Text('Security'),
subtitle: const Text('App security and permissions'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Security settings coming soon!')),
);
},
),
],
);
}
}
class _AboutSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
leading: const Icon(Icons.info_outlined),
title: const Text('About'),
subtitle: const Text('App version and information'),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push(RoutePaths.about),
),
ListTile(
leading: const Icon(Icons.help_outline),
title: const Text('Help & Support'),
subtitle: const Text('Get help and contact support'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Help & Support coming soon!')),
);
},
),
ListTile(
leading: const Icon(Icons.article_outlined),
title: const Text('Terms of Service'),
subtitle: const Text('View terms and conditions'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Terms of Service coming soon!')),
);
},
),
ListTile(
leading: const Icon(Icons.policy_outlined),
title: const Text('Privacy Policy'),
subtitle: const Text('View privacy policy'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Privacy Policy coming soon!')),
);
},
),
],
);
}
}
/// Theme settings page
class ThemeSettingsPage extends ConsumerWidget {
const ThemeSettingsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Theme Settings'),
),
body: Column(
children: [
const SizedBox(height: 16),
RadioListTile<ThemeMode>(
title: const Text('Light'),
subtitle: const Text('Use light theme'),
value: ThemeMode.light,
groupValue: themeMode,
onChanged: (value) {
if (value != null) {
ref.read(themeModeProvider.notifier).setThemeMode(value);
}
},
),
RadioListTile<ThemeMode>(
title: const Text('Dark'),
subtitle: const Text('Use dark theme'),
value: ThemeMode.dark,
groupValue: themeMode,
onChanged: (value) {
if (value != null) {
ref.read(themeModeProvider.notifier).setThemeMode(value);
}
},
),
RadioListTile<ThemeMode>(
title: const Text('System'),
subtitle: const Text('Follow system theme'),
value: ThemeMode.system,
groupValue: themeMode,
onChanged: (value) {
if (value != null) {
ref.read(themeModeProvider.notifier).setThemeMode(value);
}
},
),
const SizedBox(height: 32),
Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Preview',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: FilledButton(
onPressed: () {},
child: const Text('Filled Button'),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: const Text('Outlined Button'),
),
),
],
),
const SizedBox(height: 12),
Text(
'This is how your theme looks with sample content. The theme will be applied across the entire app.',
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,352 @@
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _isLoading = false;
List<Map<String, dynamic>> _todos = [];
String _searchQuery = '';
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_loadTodos();
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
// Mock todos data - will be replaced with API call
Future<void> _loadTodos() async {
setState(() {
_isLoading = true;
});
// Simulate API call delay
await Future.delayed(const Duration(seconds: 1));
// Mock data simulating JSONPlaceholder response
final mockTodos = [
{
'id': 1,
'title': 'Complete project documentation',
'completed': false,
'userId': 1,
},
{
'id': 2,
'title': 'Review code changes',
'completed': true,
'userId': 1,
},
{
'id': 3,
'title': 'Update Flutter dependencies',
'completed': false,
'userId': 1,
},
{
'id': 4,
'title': 'Write unit tests',
'completed': false,
'userId': 2,
},
{
'id': 5,
'title': 'Fix navigation bug',
'completed': true,
'userId': 2,
},
];
if (mounted) {
setState(() {
_todos = mockTodos;
_isLoading = false;
});
}
}
List<Map<String, dynamic>> get _filteredTodos {
if (_searchQuery.isEmpty) {
return _todos;
}
return _todos.where((todo) {
return todo['title']
.toString()
.toLowerCase()
.contains(_searchQuery.toLowerCase());
}).toList();
}
void _toggleTodoStatus(int id) {
setState(() {
final todoIndex = _todos.indexWhere((todo) => todo['id'] == id);
if (todoIndex != -1) {
_todos[todoIndex]['completed'] = !_todos[todoIndex]['completed'];
}
});
}
Future<void> _refreshTodos() async {
await _loadTodos();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('My Todos'),
elevation: 0,
backgroundColor: colorScheme.surfaceVariant,
foregroundColor: colorScheme.onSurfaceVariant,
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () {
// Handle logout - will be connected to auth logic later
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Logout functionality will be implemented'),
),
);
},
),
],
),
body: Column(
children: [
// Search Bar
Container(
padding: const EdgeInsets.all(16.0),
color: colorScheme.surfaceVariant,
child: TextField(
controller: _searchController,
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
decoration: InputDecoration(
hintText: 'Search todos...',
prefixIcon: const Icon(Icons.search),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
setState(() {
_searchQuery = '';
});
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
filled: true,
fillColor: colorScheme.surface,
),
),
),
// Todos List
Expanded(
child: _isLoading
? const Center(
child: CircularProgressIndicator(),
)
: _todos.isEmpty
? _buildEmptyState()
: RefreshIndicator(
onRefresh: _refreshTodos,
child: _filteredTodos.isEmpty
? _buildNoResultsState()
: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: _filteredTodos.length,
itemBuilder: (context, index) {
final todo = _filteredTodos[index];
return _buildTodoCard(todo);
},
),
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Handle add new todo
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Add todo functionality will be implemented'),
),
);
},
icon: const Icon(Icons.add),
label: const Text('Add Todo'),
),
);
}
Widget _buildTodoCard(Map<String, dynamic> todo) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final isCompleted = todo['completed'] as bool;
return Card(
margin: const EdgeInsets.only(bottom: 8.0),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
leading: Checkbox(
value: isCompleted,
onChanged: (_) => _toggleTodoStatus(todo['id']),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
title: Text(
todo['title'],
style: theme.textTheme.bodyLarge?.copyWith(
decoration: isCompleted ? TextDecoration.lineThrough : null,
color: isCompleted
? colorScheme.onSurfaceVariant
: colorScheme.onSurface,
),
),
subtitle: Text(
'ID: ${todo['id']} • User: ${todo['userId']}',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
trailing: PopupMenuButton<String>(
onSelected: (value) {
switch (value) {
case 'edit':
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Edit functionality will be implemented'),
),
);
break;
case 'delete':
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Delete functionality will be implemented'),
),
);
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'edit',
child: Row(
children: [
Icon(Icons.edit),
SizedBox(width: 8),
Text('Edit'),
],
),
),
const PopupMenuItem(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete),
SizedBox(width: 8),
Text('Delete'),
],
),
),
],
),
),
);
}
Widget _buildEmptyState() {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.task_outlined,
size: 64,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No todos yet',
style: theme.textTheme.headlineSmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Add your first todo to get started!',
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
);
}
Widget _buildNoResultsState() {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off,
size: 64,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No todos found',
style: theme.textTheme.headlineSmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Try adjusting your search terms',
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
);
}
}