diff --git a/lib/controllers/camera_controller.dart b/lib/controllers/camera_controller.dart index 83c34f2..dd0c1e8 100644 --- a/lib/controllers/camera_controller.dart +++ b/lib/controllers/camera_controller.dart @@ -2,11 +2,15 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart' show Uint8List, kIsWeb; import 'package:image_picker/image_picker.dart'; -import 'package:get_it/get_it.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:openwardrobe/repositories/app_repository.dart'; +final cameraControllerProvider = Provider((ref) => CameraController(ref.read)); + class CameraController { - final AppRepository _appRepository = GetIt.instance(); + final AppRepository _appRepository; + + CameraController(read) : _appRepository = read(appRepositoryProvider); Future> pickImages({bool fromGallery = false}) async { if (kIsWeb) { diff --git a/lib/controllers/home_controller.dart b/lib/controllers/home_controller.dart index 1ad06d2..ac4ea47 100644 --- a/lib/controllers/home_controller.dart +++ b/lib/controllers/home_controller.dart @@ -1,9 +1,13 @@ -import 'package:get_it/get_it.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:openwardrobe/repositories/app_repository.dart'; import 'package:openwardrobe/brick/models/user_profile.model.dart'; +final homeControllerProvider = Provider((ref) => HomeController(ref.read)); + class HomeController { - final AppRepository _appRepository = GetIt.instance(); + final AppRepository _appRepository; + + HomeController(read) : _appRepository = read(appRepositoryProvider); Future fetchUserProfile() async { try { @@ -13,5 +17,4 @@ class HomeController { throw Exception('Failed to fetch user profile: $e'); } } - } diff --git a/lib/controllers/lookbook_controller.dart b/lib/controllers/lookbook_controller.dart index cd85761..2c10e5b 100644 --- a/lib/controllers/lookbook_controller.dart +++ b/lib/controllers/lookbook_controller.dart @@ -1,9 +1,14 @@ -import 'package:get_it/get_it.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:openwardrobe/repositories/app_repository.dart'; import 'package:openwardrobe/brick/models/lookbook.model.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final lookbookControllerProvider = Provider((ref) => LookbookController(ref)); class LookbookController { - final AppRepository _appRepository = GetIt.instance(); + final AppRepository _appRepository; + + LookbookController(ProviderRef ref) : _appRepository = ref.read(appRepositoryProvider); Future> fetchLookbookItems() async { try { diff --git a/lib/controllers/settings_account_controller.dart b/lib/controllers/settings_account_controller.dart index a63544f..83f563b 100644 --- a/lib/controllers/settings_account_controller.dart +++ b/lib/controllers/settings_account_controller.dart @@ -1,76 +1,28 @@ -import 'dart:io'; -import 'package:flutter/foundation.dart' show Uint8List, kIsWeb; -import 'package:get_it/get_it.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:openwardrobe/brick/models/user_profile.model.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:openwardrobe/repositories/app_repository.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; - -class SettingsAccountController { - final AppRepository _appRepository = GetIt.instance(); - - Future fetchUserProfile() async { - try { - final profiles = await _appRepository.get(); - return profiles.first; - } catch (e) { - throw Exception('Failed to fetch user profile: $e'); - } - } - - Future upsertUserProfile(UserProfile profile) async { - try { - await _appRepository.upsert(profile); - } catch (e) { - throw Exception('Failed to upsert user profile: $e'); - } - } +import 'package:openwardrobe/brick/models/user_profile.model.dart'; - Future uploadAvatar(File imageFile) async { - try { - final response = await Supabase.instance.client.storage - .from('avatars') - .upload(imageFile.path, imageFile); - return response; - } catch (e) { - throw Exception('Failed to upload avatar: $e'); - } - } +class SettingsAccountController extends StateNotifier { + final AppRepository _appRepository; - Future uploadWebAvatar(Uint8List imageBytes, String fileName) async { - try { - final response = await Supabase.instance.client.storage - .from('avatars') - .uploadBinary(fileName, imageBytes); - return response; - } catch (e) { - throw Exception('Failed to upload web avatar: $e'); - } - } + SettingsAccountController(this._appRepository) : super(null); - Future pickImage({bool fromGallery = false}) async { - final picker = ImagePicker(); - final pickedFile = await picker.pickImage( - source: fromGallery ? ImageSource.gallery : ImageSource.camera, - ); + UserProfile? get userProfile => state; - if (pickedFile != null) { - return File(pickedFile.path); - } else { - return null; + Future fetchUserProfile() async { + final profiles = await _appRepository.get(); + if (profiles.isNotEmpty) { + state = profiles.first; } } - Future pickWebImage() async { - final picker = ImagePicker(); - final pickedFile = await picker.pickImage( - source: ImageSource.gallery, - ); - - if (pickedFile != null) { - return await pickedFile.readAsBytes(); - } else { - return null; - } + Future updateUserProfile(UserProfile updatedProfile) async { + await _appRepository.upsert(updatedProfile); + state = updatedProfile; // Directly update state without using separate variables } } + +final settingsAccountControllerProvider = + StateNotifierProvider( + (ref) => SettingsAccountController(ref.read(appRepositoryProvider)), +); \ No newline at end of file diff --git a/lib/controllers/settings_controller.dart b/lib/controllers/settings_controller.dart index acd4551..90d8acf 100644 --- a/lib/controllers/settings_controller.dart +++ b/lib/controllers/settings_controller.dart @@ -1,8 +1,12 @@ -import 'package:get_it/get_it.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:openwardrobe/repositories/app_repository.dart'; +final settingsControllerProvider = Provider((ref) => SettingsController(ref.read)); + class SettingsController { - final AppRepository _appRepository = GetIt.instance(); + final AppRepository _appRepository; + + SettingsController(Reader read) : _appRepository = read(appRepositoryProvider); Future> fetchSettings() async { try { diff --git a/lib/controllers/wardrobe_controller.dart b/lib/controllers/wardrobe_controller.dart index b77a841..5d8b90b 100644 --- a/lib/controllers/wardrobe_controller.dart +++ b/lib/controllers/wardrobe_controller.dart @@ -1,10 +1,14 @@ -import 'package:get_it/get_it.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:openwardrobe/brick/models/outfit.model.dart'; import 'package:openwardrobe/repositories/app_repository.dart'; import 'package:openwardrobe/brick/models/wardrobe_item.model.dart'; +final wardrobeControllerProvider = Provider((ref) => WardrobeController(ref.read)); + class WardrobeController { - final AppRepository _appRepository = GetIt.instance(); + final AppRepository _appRepository; + + WardrobeController(read) : _appRepository = read(appRepositoryProvider); Future> fetchWardrobeItems() async { try { diff --git a/lib/di/service_locator.dart b/lib/di/service_locator.dart deleted file mode 100644 index 1521d1c..0000000 --- a/lib/di/service_locator.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:get_it/get_it.dart'; - -import '../repositories/app_repository.dart'; -import '../controllers/camera_controller.dart'; -import '../controllers/home_controller.dart'; -import '../controllers/wardrobe_controller.dart'; -import '../controllers/lookbook_controller.dart'; -import '../controllers/settings_account_controller.dart'; // Import the new controller - -final getIt = GetIt.instance; - -void setupLocator() { - // Register the AppRepository instance. - getIt.registerLazySingleton(() => AppRepository()); - - // Register controllers - getIt.registerLazySingleton(() => CameraController()); - getIt.registerLazySingleton(() => HomeController()); - getIt.registerLazySingleton(() => WardrobeController()); - getIt.registerLazySingleton(() => LookbookController()); - getIt.registerLazySingleton(() => SettingsAccountController()); // Register the new controller -} diff --git a/lib/main.dart b/lib/main.dart index 004d83c..a0b63ef 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,8 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:openwardrobe/repositories/app_repository.dart'; import 'router/app_router.dart'; - -import 'package:openwardrobe/di/service_locator.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; // sqflite_common_ffi_web import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; @@ -26,9 +25,7 @@ Future main() async { await AppRepository().initialize(); - setupLocator(); - - runApp(const MyApp()); + runApp(const ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { diff --git a/lib/repositories/app_repository.dart b/lib/repositories/app_repository.dart index ffde7d6..29737d0 100644 --- a/lib/repositories/app_repository.dart +++ b/lib/repositories/app_repository.dart @@ -1,15 +1,16 @@ -// Saved in my_app/lib/src/brick/repository.dart import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart'; import 'package:brick_sqlite/brick_sqlite.dart'; import 'package:brick_sqlite/memory_cache_provider.dart'; -// This hide is for Brick's @Supabase annotation; in most cases, -// supabase_flutter **will not** be imported in application code. import 'package:brick_supabase/brick_supabase.dart' hide Supabase; import 'package:openwardrobe/brick/db/schema.g.dart'; import 'package:sqflite_common/sqlite_api.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; - import 'package:openwardrobe/brick/brick.g.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final appRepositoryProvider = Provider((ref) { + return AppRepository(); +}); class AppRepository extends OfflineFirstWithSupabaseRepository { static late AppRepository? _instance; diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index dc80c55..11fac67 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -1,17 +1,14 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:openwardrobe/brick/models/lookbook.model.dart'; import 'package:openwardrobe/ui/screens/camera/page.dart'; import 'package:openwardrobe/ui/screens/lookbook/page.dart'; -import 'package:openwardrobe/ui/screens/wardrobe/settings/page.dart'; +import 'package:openwardrobe/ui/screens/settings/page.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../ui/screens/auth/page.dart'; import '../ui/screens/home/page.dart'; import '../ui/screens/wardrobe/page.dart'; -import '../ui/screens/wardrobe/add/page.dart'; import '../ui/widgets/scaffold_with_navbar.dart'; -import '../ui/screens/wardrobe/settings/account_page.dart'; // Import the new settings account page class AppRouter { static final GlobalKey _rootNavigatorKey = @@ -44,11 +41,7 @@ class AppRouter { name: 'Add Item', builder: (context, state) => const CameraScreen(), ), - GoRoute( - path: '/settings/account', - name: 'SettingsAccount', - builder: (context, state) => const SettingsAccountPage(), - ), + StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) { return ScaffoldWithNavBar(navigationShell: navigationShell); diff --git a/lib/ui/screens/home/page.dart b/lib/ui/screens/home/page.dart index f362167..54c77fb 100644 --- a/lib/ui/screens/home/page.dart +++ b/lib/ui/screens/home/page.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:openwardrobe/brick/models/user_profile.model.dart'; import 'package:openwardrobe/controllers/home_controller.dart'; import 'package:openwardrobe/ui/widgets/dashboard/link.dart'; import 'package:openwardrobe/ui/widgets/user_profile/user_profile_component.dart'; -class HomeScreen extends StatelessWidget { +class HomeScreen extends ConsumerWidget { HomeScreen({super.key}); - final HomeController homeController = GetIt.instance(); - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final homeController = ref.read(homeControllerProvider); + return Scaffold( appBar: AppBar( title: const Text('Home'), diff --git a/lib/ui/screens/lookbook/page.dart b/lib/ui/screens/lookbook/page.dart index bc3085e..c40223c 100644 --- a/lib/ui/screens/lookbook/page.dart +++ b/lib/ui/screens/lookbook/page.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:openwardrobe/brick/models/lookbook.model.dart'; -import 'package:get_it/get_it.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:openwardrobe/ui/widgets/lookbook/lookbook_component.dart'; import 'package:openwardrobe/controllers/lookbook_controller.dart'; -class LookbookScreen extends StatelessWidget { +class LookbookScreen extends ConsumerWidget { LookbookScreen({super.key}); - final LookbookController lookbookController = GetIt.instance(); - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final lookbookController = ref.read(lookbookControllerProvider); + return Scaffold( appBar: AppBar( title: const Text('Lookbook'), diff --git a/lib/ui/screens/settings/page.dart b/lib/ui/screens/settings/page.dart index 97d5ff8..f27e9c6 100644 --- a/lib/ui/screens/settings/page.dart +++ b/lib/ui/screens/settings/page.dart @@ -1,69 +1,46 @@ import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; -import 'package:openwardrobe/controllers/settings_controller.dart'; +import 'package:go_router/go_router.dart'; -class SettingsScreen extends StatelessWidget { - const SettingsScreen({Key? key}) : super(key: key); +class SettingsPage extends StatelessWidget { + const SettingsPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - final SettingsController settingsController = GetIt.instance(); - return Scaffold( appBar: AppBar( title: const Text('Settings'), ), - body: FutureBuilder>( - future: settingsController.fetchSettings(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); - } else if (!snapshot.hasData) { - return const Center(child: Text('No settings found')); - } - - final settings = snapshot.data!; - - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Settings', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 20), - ListTile( - title: const Text('Account'), - onTap: () { - // Navigate to account settings - }, - ), - ListTile( - title: const Text('Notifications'), - onTap: () { - // Navigate to notification settings - }, - ), - ListTile( - title: const Text('Privacy'), - onTap: () { - // Navigate to privacy settings - }, - ), - ListTile( - title: const Text('About'), - onTap: () { - // Navigate to about page - }, - ), - ], + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + title: const Text('Account'), + onTap: () { + context.push('/settings/account'); + }, + ), + ListTile( + title: const Text('Notifications'), + onTap: () { + Navigator.pushNamed(context, '/settings/notifications'); + }, + ), + ListTile( + title: const Text('Privacy'), + onTap: () { + Navigator.pushNamed(context, '/settings/privacy'); + }, + ), + ListTile( + title: const Text('About'), + onTap: () { + Navigator.pushNamed(context, '/settings/about'); + }, ), - ); - }, + ], + ), ), ); } diff --git a/lib/ui/screens/wardrobe/page.dart b/lib/ui/screens/wardrobe/page.dart index e1f5637..bc31aad 100644 --- a/lib/ui/screens/wardrobe/page.dart +++ b/lib/ui/screens/wardrobe/page.dart @@ -1,24 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; import 'package:openwardrobe/brick/models/outfit.model.dart'; import 'package:openwardrobe/brick/models/wardrobe_item.model.dart'; import 'package:openwardrobe/controllers/wardrobe_controller.dart'; import 'package:openwardrobe/ui/widgets/outfit/outfit_component.dart'; import 'package:openwardrobe/ui/widgets/wardrobe_item/wardrobe_item_component.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class WardrobeScreen extends StatefulWidget { - const WardrobeScreen({super.key}); - @override - _WardrobeScreenState createState() => _WardrobeScreenState(); -} - -class _WardrobeScreenState extends State { - final WardrobeController wardrobeController = - GetIt.instance(); +class WardrobeScreen extends ConsumerWidget { + const WardrobeScreen({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final wardrobeController = ref.watch(wardrobeControllerProvider); + return Scaffold( appBar: AppBar(title: const Text('Wardrobe')), body: FutureBuilder>( @@ -50,7 +45,6 @@ class _WardrobeScreenState extends State { future: wardrobeController.fetchOutfits(), builder: (context, outfitSnapshot) { if (outfitSnapshot.connectionState == ConnectionState.waiting) { - // Show wardrobe items while outfits are still loading return const Center(child: CircularProgressIndicator()); } else if (outfitSnapshot.hasError) { return Center(child: Text('Error: ${outfitSnapshot.error}')); diff --git a/lib/ui/screens/wardrobe/settings/account_page.dart b/lib/ui/screens/wardrobe/settings/account_page.dart deleted file mode 100644 index 5e5cc09..0000000 --- a/lib/ui/screens/wardrobe/settings/account_page.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; -import 'package:openwardrobe/controllers/settings_account_controller.dart'; -import 'package:openwardrobe/brick/models/user_profile.model.dart'; - -class SettingsAccountPage extends StatefulWidget { - const SettingsAccountPage({Key? key}) : super(key: key); - - @override - _SettingsAccountPageState createState() => _SettingsAccountPageState(); -} - -class _SettingsAccountPageState extends State { - final SettingsAccountController _controller = GetIt.instance(); - late Future _userProfileFuture; - final TextEditingController _usernameController = TextEditingController(); - final TextEditingController _displayNameController = TextEditingController(); - final TextEditingController _bioController = TextEditingController(); - String? _avatarUrl; - - @override - void initState() { - super.initState(); - _userProfileFuture = _controller.fetchUserProfile(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Account Settings'), - ), - body: FutureBuilder( - future: _userProfileFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); - } else if (!snapshot.hasData) { - return const Center(child: Text('No profile found')); - } - - final userProfile = snapshot.data!; - _usernameController.text = userProfile.username; - _displayNameController.text = userProfile.displayName ?? ''; - _bioController.text = userProfile.bio ?? ''; - _avatarUrl = userProfile.avatarUrl; - - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: GestureDetector( - onTap: () async { - final imageFile = await _controller.pickImage(fromGallery: true); - if (imageFile != null) { - final avatarUrl = await _controller.uploadAvatar(imageFile); - setState(() { - _avatarUrl = avatarUrl; - }); - } - }, - child: CircleAvatar( - radius: 50, - backgroundImage: _avatarUrl != null ? NetworkImage(_avatarUrl!) : null, - child: _avatarUrl == null ? const Icon(Icons.person, size: 50) : null, - ), - ), - ), - const SizedBox(height: 16), - TextField( - controller: _usernameController, - decoration: const InputDecoration(labelText: 'Username'), - ), - const SizedBox(height: 16), - TextField( - controller: _displayNameController, - decoration: const InputDecoration(labelText: 'Display Name'), - ), - const SizedBox(height: 16), - TextField( - controller: _bioController, - decoration: const InputDecoration(labelText: 'Bio'), - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () async { - final updatedProfile = UserProfile( - id: userProfile.id, - username: _usernameController.text, - displayName: _displayNameController.text, - bio: _bioController.text, - avatarUrl: _avatarUrl, - socialLinks: userProfile.socialLinks, - isPublic: userProfile.isPublic, - createdAt: userProfile.createdAt, - updatedAt: DateTime.now(), - ); - await _controller.upsertUserProfile(updatedProfile); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Profile updated successfully')), - ); - }, - child: const Text('Save'), - ), - ], - ), - ); - }, - ), - ); - } -} diff --git a/lib/ui/screens/wardrobe/settings/page.dart b/lib/ui/screens/wardrobe/settings/page.dart deleted file mode 100644 index f27e9c6..0000000 --- a/lib/ui/screens/wardrobe/settings/page.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -class SettingsPage extends StatelessWidget { - const SettingsPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Settings'), - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - title: const Text('Account'), - onTap: () { - context.push('/settings/account'); - }, - ), - ListTile( - title: const Text('Notifications'), - onTap: () { - Navigator.pushNamed(context, '/settings/notifications'); - }, - ), - ListTile( - title: const Text('Privacy'), - onTap: () { - Navigator.pushNamed(context, '/settings/privacy'); - }, - ), - ListTile( - title: const Text('About'), - onTap: () { - Navigator.pushNamed(context, '/settings/about'); - }, - ), - ], - ), - ), - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 418aa61..259d012 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,10 +35,11 @@ dependencies: sqflite_common_ffi_web: any camera: ^0.10.0+4 image_picker: any + flutter_riverpod: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 brick_offline_first_with_supabase_build: ^1.0.0 - build_runner: ^2.4.6 \ No newline at end of file + build_runner: ^2.4.6