diff --git a/lib/features/app_update/bloc/app_update_bloc.dart b/lib/features/app_update/bloc/app_update_bloc.dart new file mode 100644 index 0000000..71f9150 --- /dev/null +++ b/lib/features/app_update/bloc/app_update_bloc.dart @@ -0,0 +1,32 @@ +import 'package:bloc_example/features/app_update/bloc/app_update_event.dart'; +import 'package:bloc_example/features/app_update/bloc/app_update_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class AppUpdateBloc extends Bloc { + AppUpdateBloc() : super(AppUpdateStateLoading()) { + on(_onInit); + on(_onSkip); + } + + void _onInit(_, Emitter emit) { + if (const bool.fromEnvironment('APP_UPDATE_ERROR')) { + emit(AppUpdateStateError()); + } else { + emit( + AppUpdateStateLoaded( + needUpdate: const bool.fromEnvironment('APP_UPDATE'), + forceUpdate: const bool.fromEnvironment('APP_UPDATE_FORCE'), + ), + ); + } + } + + void _onSkip(_, Emitter emit) async { + emit( + AppUpdateStateLoaded( + needUpdate: false, + forceUpdate: false, + ), + ); + } +} diff --git a/lib/features/app_update/bloc/app_update_event.dart b/lib/features/app_update/bloc/app_update_event.dart new file mode 100644 index 0000000..6732963 --- /dev/null +++ b/lib/features/app_update/bloc/app_update_event.dart @@ -0,0 +1,5 @@ +sealed class AppUpdateEvent {} + +class AppUpdateEventInit extends AppUpdateEvent {} + +class AppUpdateEventSkip extends AppUpdateEvent {} diff --git a/lib/features/app_update/bloc/app_update_state.dart b/lib/features/app_update/bloc/app_update_state.dart new file mode 100644 index 0000000..1998d4c --- /dev/null +++ b/lib/features/app_update/bloc/app_update_state.dart @@ -0,0 +1,15 @@ +sealed class AppUpdateState {} + +class AppUpdateStateLoading extends AppUpdateState {} + +class AppUpdateStateLoaded extends AppUpdateState { + final bool needUpdate; + final bool forceUpdate; + + AppUpdateStateLoaded({ + required this.needUpdate, + required this.forceUpdate, + }); +} + +class AppUpdateStateError extends AppUpdateState {} diff --git a/lib/features/app_update/widgets/app_update_view.dart b/lib/features/app_update/widgets/app_update_view.dart new file mode 100644 index 0000000..da51525 --- /dev/null +++ b/lib/features/app_update/widgets/app_update_view.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; + +class AppUpdateView extends StatelessWidget { + final String message; + final bool isForceUpdate; + final VoidCallback onUpdate; + final VoidCallback onSkip; + + const AppUpdateView({ + required this.message, + required this.isForceUpdate, + required this.onUpdate, + required this.onSkip, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.update_rounded, + size: 128, + ), + SizedBox(height: 20), + Text( + 'Доступна новая версия', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 12), + Text( + message, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 30), + ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + minimumSize: Size( + double.infinity, + 50, + ), + ), + child: Text('Обновить'), + ), + if (!isForceUpdate) ...[ + SizedBox(height: 10), + TextButton( + onPressed: onSkip, + child: Text('Позже'), + ), + ], + ], + ), + ); + } +} diff --git a/lib/features/home/home_page.dart b/lib/features/home/home_page.dart index e10336b..57e17d0 100644 --- a/lib/features/home/home_page.dart +++ b/lib/features/home/home_page.dart @@ -1,4 +1,8 @@ import 'package:bloc_example/core/di/di.dart'; +import 'package:bloc_example/features/app_update/bloc/app_update_bloc.dart'; +import 'package:bloc_example/features/app_update/bloc/app_update_event.dart'; +import 'package:bloc_example/features/app_update/bloc/app_update_state.dart'; +import 'package:bloc_example/features/app_update/widgets/app_update_view.dart'; import 'package:bloc_example/features/profile/bloc/profile_bloc.dart'; import 'package:bloc_example/features/profile/bloc/profile_bloc_state.dart'; import 'package:bloc_example/features/users/bloc/users_bloc.dart'; @@ -7,6 +11,7 @@ import 'package:bloc_example/features/users/repository/users_repository.dart'; import 'package:bloc_example/features/users/widgets/users_list.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:url_launcher/url_launcher_string.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @@ -19,15 +24,63 @@ class HomePage extends StatelessWidget { 'BLoC Example', ), ), - body: BlocProvider( - create: (_) => UsersBloc( - usersRepository: di.get(), - ), - child: BlocListener( + body: MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => UsersBloc( + usersRepository: di.get(), + ), + ), + BlocProvider( + create: (_) => AppUpdateBloc()..add(AppUpdateEventInit()), + ), + ], + child: BlocListener( listener: (context, state) { - context.read().add(UsersBlocEventRefresh()); + if (state is AppUpdateStateLoaded) { + if (state.needUpdate) { + showModalBottomSheet( + context: context, + isDismissible: !state.forceUpdate, + enableDrag: !state.forceUpdate, + builder: (_) { + return PopScope( + canPop: !state.forceUpdate, + onPopInvokedWithResult: (didPop, __) { + if (!didPop) { + return; + } + + context.read().add(AppUpdateEventSkip()); + }, + child: AppUpdateView( + message: state.forceUpdate + ? 'Для дальнейшего использования приложения необходимо его обновить!' + : 'Новая версия уже доступна в вашем магазине приложений!', + isForceUpdate: state.forceUpdate, + onUpdate: () { + launchUrlString( + 'https://github.com/fluttermiddlepodcast/bloc_example', + ); + }, + onSkip: () { + context.read().add(AppUpdateEventSkip()); + + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + } + } }, - child: const UsersList(), + child: BlocListener( + listener: (context, state) { + context.read().add(UsersBlocEventRefresh()); + }, + child: const UsersList(), + ), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 19d00fe..72cfe78 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -291,6 +291,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -784,6 +789,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: @@ -858,4 +927,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.10.0 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index b543dee..1b8a9ea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: rxdart: 0.28.0 collection: + url_launcher: dev_dependencies: flutter_test: