diff --git a/flutter_module/lib/main.dart b/flutter_module/lib/main.dart index 0a72b44..17e9fc7 100644 --- a/flutter_module/lib/main.dart +++ b/flutter_module/lib/main.dart @@ -6,9 +6,10 @@ import 'package:flutter_module/presentation/screens//main_screen.dart'; import 'package:flutter_module/presentation/screens/details_screen.dart'; import 'package:flutter_module/presentation/screens/login_screen.dart'; import 'package:flutter_module/utils/extensions/text_extensions.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router_plus/go_router_plus.dart'; -void main() => runApp(MyApp()); +void main() => runApp(ProviderScope(child: MyApp())); class MyApp extends StatelessWidget { MyApp({super.key}); diff --git a/flutter_module/lib/presentation/providers/details_page_state_provider.dart b/flutter_module/lib/presentation/providers/details_page_state_provider.dart new file mode 100644 index 0000000..4c46edf --- /dev/null +++ b/flutter_module/lib/presentation/providers/details_page_state_provider.dart @@ -0,0 +1,40 @@ +import 'package:flutter_module/model/main_model.dart'; +import 'package:flutter_module/presentation/providers/model_bridge_provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final detailsPageStateProvider = + StateNotifierProvider<_DetailPageStateNotifier, DetailModelStateData>( + (ref) => _DetailPageStateNotifier(detailsBridge: ref.read(detailsBridge)), +); + +final detailsPageEventProvider = StateNotifierProvider<_DetailPageEventNotifier, DetailModelEventData>( + (ref) => _DetailPageEventNotifier(detailsBridge: ref.read(detailsBridge)), +); + +class _DetailPageStateNotifier extends StateNotifier { + _DetailPageStateNotifier({ + required this.detailsBridge, + }) : super(DetailModelStateData()) { + // Simulate hook's state & event refreshing + Stream.periodic( + const Duration(milliseconds: 500), + (_) => detailsBridge.getState(), + ).listen((newState) async => state = await newState); + } + + final DetailModelBridge detailsBridge; +} + +class _DetailPageEventNotifier extends StateNotifier { + _DetailPageEventNotifier({ + required this.detailsBridge, + }) : super(DetailModelEventData()) { + // Simulate hook's state & event refreshing + Stream.periodic( + const Duration(milliseconds: 500), + (_) => detailsBridge.getEvent(), + ).listen((newEvent) async => state = await newEvent); + } + + final DetailModelBridge detailsBridge; +} diff --git a/flutter_module/lib/presentation/providers/login_page_state_provider.dart b/flutter_module/lib/presentation/providers/login_page_state_provider.dart new file mode 100644 index 0000000..35aa7b2 --- /dev/null +++ b/flutter_module/lib/presentation/providers/login_page_state_provider.dart @@ -0,0 +1,40 @@ +import 'package:flutter_module/model/main_model.dart'; +import 'package:flutter_module/presentation/providers/model_bridge_provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final loginPageStateProvider = + StateNotifierProvider<_LoginPageStateNotifier, LoginModelStateData>( + (ref) => _LoginPageStateNotifier(loginBridge: ref.read(loginBridge)), +); + +final loginPageEventProvider = StateNotifierProvider<_LoginPageEventNotifier, LoginModelEventData>( + (ref) => _LoginPageEventNotifier(loginBridge: ref.read(loginBridge)), +); + +class _LoginPageStateNotifier extends StateNotifier { + _LoginPageStateNotifier({ + required this.loginBridge, + }) : super(LoginModelStateData()) { + // Simulate hook's state & event refreshing + Stream.periodic( + const Duration(milliseconds: 500), + (_) => loginBridge.getState(), + ).listen((newState) async => state = await newState); + } + + final LoginModelBridge loginBridge; +} + +class _LoginPageEventNotifier extends StateNotifier { + _LoginPageEventNotifier({ + required this.loginBridge, + }) : super(LoginModelEventData()) { + // Simulate hook's state & event refreshing + Stream.periodic( + const Duration(milliseconds: 500), + (_) => loginBridge.getEvent(), + ).listen((newEvent) async => state = await newEvent); + } + + final LoginModelBridge loginBridge; +} diff --git a/flutter_module/lib/presentation/providers/main_page_state_provider.dart b/flutter_module/lib/presentation/providers/main_page_state_provider.dart new file mode 100644 index 0000000..a7c5dcc --- /dev/null +++ b/flutter_module/lib/presentation/providers/main_page_state_provider.dart @@ -0,0 +1,40 @@ +import 'package:flutter_module/model/main_model.dart'; +import 'package:flutter_module/presentation/providers/model_bridge_provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final mainPageStateProvider = + StateNotifierProvider<_MainPageStateNotifier, MainModelStateData>( + (ref) => _MainPageStateNotifier(mainBridge: ref.read(mainBridge)), +); + +final mainPageEventProvider = StateNotifierProvider<_MainPageEventNotifier, MainModelEventData>( + (ref) => _MainPageEventNotifier(mainBridge: ref.read(mainBridge)), +); + +class _MainPageStateNotifier extends StateNotifier { + _MainPageStateNotifier({ + required this.mainBridge, + }) : super(MainModelStateData()) { + // Simulate hook's state & event refreshing + Stream.periodic( + const Duration(milliseconds: 500), + (_) => mainBridge.getState(), + ).listen((newState) async => state = await newState); + } + + final MainModelBridge mainBridge; +} + +class _MainPageEventNotifier extends StateNotifier { + _MainPageEventNotifier({ + required this.mainBridge, + }) : super(MainModelEventData()) { + // Simulate hook's state & event refreshing + Stream.periodic( + const Duration(milliseconds: 500), + (_) => mainBridge.getEvent(), + ).listen((newEvent) async => state = await newEvent); + } + + final MainModelBridge mainBridge; +} diff --git a/flutter_module/lib/presentation/providers/model_bridge_provider.dart b/flutter_module/lib/presentation/providers/model_bridge_provider.dart new file mode 100644 index 0000000..19a4fa0 --- /dev/null +++ b/flutter_module/lib/presentation/providers/model_bridge_provider.dart @@ -0,0 +1,6 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:flutter_module/model/main_model.dart'; + +final loginBridge = Provider((ref) => LoginModelBridge()); +final mainBridge = Provider((ref) => MainModelBridge()); +final detailsBridge = Provider((ref) => DetailModelBridge()); \ No newline at end of file diff --git a/flutter_module/lib/presentation/screens/details_screen.dart b/flutter_module/lib/presentation/screens/details_screen.dart index 50962c7..9ef6271 100644 --- a/flutter_module/lib/presentation/screens/details_screen.dart +++ b/flutter_module/lib/presentation/screens/details_screen.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_module/model/main_model.dart'; import 'package:flutter_module/presentation/navigation/details/details_navigator.dart'; +import 'package:flutter_module/presentation/providers/details_page_state_provider.dart'; +import 'package:flutter_module/presentation/providers/model_bridge_provider.dart'; import 'package:flutter_module/presentation/screens/named_screen.dart'; import 'package:flutter_module/presentation/styles/color_styles.dart'; import 'package:flutter_module/presentation/styles/dimens.dart'; @@ -15,6 +17,7 @@ import 'package:flutter_module/utils/extensions/state_extensions.dart'; import 'package:flutter_module/utils/hooks/use_navigator.dart'; import 'package:flutter_module/utils/hooks/use_observable_state_hook.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; class DetailsScreen extends NamedScreen { DetailsScreen({ @@ -34,10 +37,9 @@ class DetailsScreen extends NamedScreen { model: model, ); } - } -class _DetailsPage extends HookWidget { +class _DetailsPage extends HookConsumerWidget { const _DetailsPage({ required this.navigator, required this.model, @@ -47,24 +49,17 @@ class _DetailsPage extends HookWidget { final DetailModelBridge model; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { useNavigator([navigator]); - final state = useObservableState( - DetailModelStateData(), - () => model.getState(), - (current, newState) => (current as DetailModelStateData).equals(newState), - ).value; - - final event = useObservableState( - DetailModelEventData(), - () => model.getEvent(), - (current, newState) => (current as DetailModelEventData).equals(newState), - ).value; + final stateNotification = ref.watch(detailsPageStateProvider); + final state = stateNotification.state; + final eventNotification = ref.watch(detailsPageEventProvider); + final event = eventNotification.event; WidgetsBinding.instance.addPostFrameCallback((_) { - if (event.event == DetailModelEvent.saved) { - final snippetId = event.value; + if (event == DetailModelEvent.saved) { + final snippetId = eventNotification.value; if (snippetId == null) { _exit(); return; @@ -78,7 +73,7 @@ class _DetailsPage extends HookWidget { }); WidgetsBinding.instance.addPostFrameCallback((_) { - if (event.event == DetailModelEvent.deleted) { + if (event == DetailModelEvent.deleted) { _exit(); } }); @@ -91,7 +86,7 @@ class _DetailsPage extends HookWidget { return Scaffold( backgroundColor: ColorStyles.surfacePrimary(), appBar: AppBar( - title: Text(state.data?.title ?? ''), + title: Text(stateNotification.data?.title ?? ''), backgroundColor: ColorStyles.surfacePrimary(), foregroundColor: Colors.black, elevation: 0, @@ -99,15 +94,15 @@ class _DetailsPage extends HookWidget { onPressed: navigator.back, color: Colors.black, ), - actions: state.data?.isPrivate == true + actions: stateNotification.data?.isPrivate == true ? [const PaddingStyles.regular(Icon(Icons.lock_outlined))] : null, ), body: ViewStateWrapper( isLoading: - state.state == ModelState.loading || state.isLoading == true, - error: state.error, - data: state.data, + state == ModelState.loading || stateNotification.isLoading == true, + error: stateNotification.error, + data: stateNotification.data, builder: (_, snippet) => _DetailPageData( model: model, snippet: snippet, diff --git a/flutter_module/lib/presentation/screens/login_screen.dart b/flutter_module/lib/presentation/screens/login_screen.dart index d3a208c..a38f884 100644 --- a/flutter_module/lib/presentation/screens/login_screen.dart +++ b/flutter_module/lib/presentation/screens/login_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_module/generated/assets.dart'; import 'package:flutter_module/model/main_model.dart'; import 'package:flutter_module/presentation/navigation/login/login_navigator.dart'; +import 'package:flutter_module/presentation/providers/login_page_state_provider.dart'; import 'package:flutter_module/presentation/screens/named_screen.dart'; import 'package:flutter_module/presentation/styles/dimens.dart'; import 'package:flutter_module/presentation/styles/padding_styles.dart'; @@ -11,10 +12,9 @@ import 'package:flutter_module/presentation/widgets/login_input_card.dart'; import 'package:flutter_module/presentation/widgets/no_overscroll_single_child_scroll_view.dart'; import 'package:flutter_module/presentation/widgets/rounded_action_button.dart'; import 'package:flutter_module/presentation/widgets/view_state_wrapper.dart'; -import 'package:flutter_module/utils/extensions/state_extensions.dart'; import 'package:flutter_module/utils/hooks/use_navigator.dart'; -import 'package:flutter_module/utils/hooks/use_observable_state_hook.dart'; import 'package:go_router_plus/go_router_plus.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; class LoginScreen extends NamedScreen implements InitialScreen, GuestScreen { LoginScreen({ @@ -35,7 +35,7 @@ class LoginScreen extends NamedScreen implements InitialScreen, GuestScreen { } } -class _MainPage extends HookWidget { +class _MainPage extends HookConsumerWidget { const _MainPage({ required this.navigator, required this.model, @@ -45,24 +45,16 @@ class _MainPage extends HookWidget { final LoginModelBridge model; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { useNavigator([navigator]); final email = useState('mail@o2.pl'); final password = useState('12345678'); final validationCorrect = useState(true); - final state = useObservableState( - LoginModelStateData(), - () => model.getState(), - (current, newState) => (current as LoginModelStateData).equals(newState), - ).value; - - final event = useObservableState( - LoginModelEventData(), - () => model.getEvent(), - (current, newState) => (current as LoginModelEventData).equals(newState), - ).value; + final stateNotification = ref.watch(loginPageStateProvider); + final eventNotification = ref.watch(loginPageEventProvider); + final event = eventNotification.event; useEffect(() { model.checkLoginState(); @@ -70,7 +62,7 @@ class _MainPage extends HookWidget { }, []); WidgetsBinding.instance.addPostFrameCallback((_) { - if (event.event == LoginModelEvent.logged) { + if (event == LoginModelEvent.logged) { model.resetEvent(); navigator.login(); } @@ -79,8 +71,8 @@ class _MainPage extends HookWidget { return Scaffold( body: SafeArea( child: ViewStateWrapper( - isLoading: state.state == ModelState.loading, - data: state.state, + isLoading: stateNotification.state == ModelState.loading, + data: stateNotification, builder: (BuildContext context, _) { return NoOverscrollSingleChildScrollView( child: Column( diff --git a/flutter_module/lib/presentation/screens/main_screen.dart b/flutter_module/lib/presentation/screens/main_screen.dart index 2c59a13..8e39a04 100644 --- a/flutter_module/lib/presentation/screens/main_screen.dart +++ b/flutter_module/lib/presentation/screens/main_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_module/generated/assets.dart'; import 'package:flutter_module/model/main_model.dart'; import 'package:flutter_module/presentation/navigation/details/details_navigator.dart'; import 'package:flutter_module/presentation/navigation/login/login_navigator.dart'; +import 'package:flutter_module/presentation/providers/main_page_state_provider.dart'; import 'package:flutter_module/presentation/screens/named_screen.dart'; import 'package:flutter_module/presentation/styles/color_styles.dart'; import 'package:flutter_module/presentation/styles/dimens.dart'; @@ -17,6 +18,7 @@ import 'package:flutter_module/utils/extensions/state_extensions.dart'; import 'package:flutter_module/utils/hooks/use_navigator.dart'; import 'package:flutter_module/utils/hooks/use_observable_state_hook.dart'; import 'package:go_router_plus/go_router_plus.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; class MainScreen extends NamedScreen implements UserScreen { MainScreen({ @@ -40,7 +42,7 @@ class MainScreen extends NamedScreen implements UserScreen { } } -class _MainPage extends HookWidget { +class _MainPage extends HookConsumerWidget { const _MainPage({ required this.loginNavigator, required this.detailsNavigator, @@ -52,32 +54,23 @@ class _MainPage extends HookWidget { final MainModelBridge model; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { useNavigator([loginNavigator, detailsNavigator]); - - final state = useObservableState( - MainModelStateData(), - () => model.getState(), - (current, newState) => (current as MainModelStateData).equals(newState), - ).value; - - // Event - final event = useObservableState( - MainModelEventData(), - () => model.getEvent(), - (current, newState) => (current as MainModelEventData).equals(newState), - ).value; - final expandedState = useState(true); final controller = useScrollController(); + final stateNotification = ref.watch(mainPageStateProvider); + final state = stateNotification.state; + final eventNotification = ref.watch(mainPageEventProvider); + final event = eventNotification.event; + useEffect(() { model.initState(); return null; }, []); WidgetsBinding.instance.addPostFrameCallback((_) { - if (event.event == MainModelEvent.logout) { + if (event == MainModelEvent.logout) { model.resetEvent(); loginNavigator.logout(); } @@ -86,15 +79,15 @@ class _MainPage extends HookWidget { return Scaffold( backgroundColor: ColorStyles.pageBackground(), body: ViewStateWrapper>( - isLoading: state.state == ModelState.loading || state.isLoading == true, - error: state.error, - data: state.data?.cast(), + isLoading: state == ModelState.loading || stateNotification.isLoading == true, + error: stateNotification.error, + data: stateNotification.data?.cast(), builder: (_, snippets) { return _MainPageData( navigator: detailsNavigator, model: model, snippets: snippets ?? List.empty(), - filter: state.filter ?? SnippetFilter(), + filter: stateNotification.filter ?? SnippetFilter(), controller: controller, expanded: expandedState.value, onExpandChange: (expanded) => expandedState.value = expanded, diff --git a/flutter_module/pubspec.lock b/flutter_module/pubspec.lock index b75a528..e4bc463 100644 --- a/flutter_module/pubspec.lock +++ b/flutter_module/pubspec.lock @@ -230,6 +230,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" flutter_test: dependency: "direct dev" description: flutter @@ -280,6 +288,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + hooks_riverpod: + dependency: "direct main" + description: + name: hooks_riverpod + sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966" + url: "https://pub.dev" + source: hosted + version: "2.6.1" http_multi_server: dependency: transitive description: @@ -456,6 +472,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + url: "https://pub.dev" + source: hosted + version: "2.6.1" shelf: dependency: transitive description: @@ -525,6 +557,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: diff --git a/flutter_module/pubspec.yaml b/flutter_module/pubspec.yaml index 231e2b9..7bd8e58 100644 --- a/flutter_module/pubspec.yaml +++ b/flutter_module/pubspec.yaml @@ -20,6 +20,13 @@ version: 1.0.0+1 environment: sdk: ">=3.0.0 <4.0.0" +scripts: + # run is a default script. To use it, simply type " in the command line: "rps" - that's all! + add: "fvm flutter pub add" + get: "fvm flutter pub get" + gen: "fvm flutter pub run build_runner build --delete-conflicting-outputs" + run: "fvm flutter run" + dependencies: flutter: sdk: flutter @@ -32,6 +39,8 @@ dependencies: collection: any logger: ^2.4.0 + flutter_riverpod: ^2.6.1 + hooks_riverpod: ^2.6.1 dev_dependencies: flutter_test: sdk: flutter