diff --git a/mobile-v3/android/app/src/main/AndroidManifest.xml b/mobile-v3/android/app/src/main/AndroidManifest.xml index 2964525e49..2ade3f61e9 100644 --- a/mobile-v3/android/app/src/main/AndroidManifest.xml +++ b/mobile-v3/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + ) +#import +#else +@import connectivity_plus; +#endif + #if __has_include() #import #else @@ -21,6 +27,7 @@ @implementation GeneratedPluginRegistrant + (void)registerWithRegistry:(NSObject*)registry { + [ConnectivityPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"ConnectivityPlusPlugin"]]; [FLTGoogleMapsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTGoogleMapsPlugin"]]; [PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]]; } diff --git a/mobile-v3/lib/main.dart b/mobile-v3/lib/main.dart index e85fe6fc86..71d3f5ceab 100644 --- a/mobile-v3/lib/main.dart +++ b/mobile-v3/lib/main.dart @@ -17,14 +17,17 @@ import 'package:airqo/src/app/other/theme/bloc/theme_bloc.dart'; import 'package:airqo/src/app/other/theme/repository/theme_repository.dart'; import 'package:airqo/src/app/profile/bloc/user_bloc.dart'; import 'package:airqo/src/app/profile/repository/user_repository.dart'; +import 'package:airqo/src/app/shared/bloc/connectivity_bloc.dart'; import 'package:airqo/src/app/shared/pages/nav_page.dart'; import 'package:airqo/src/meta/utils/colors.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:airqo/src/app/shared/pages/no_internet_banner.dart'; import 'src/app/shared/repository/hive_repository.dart'; @@ -67,6 +70,7 @@ class AirqoMobile extends StatelessWidget { @override Widget build(BuildContext context) { + final connectivity = Connectivity(); return MultiBlocProvider( providers: [ BlocProvider( @@ -93,7 +97,10 @@ class AirqoMobile extends StatelessWidget { ), BlocProvider( create: (context) => MapBloc(mapRepository)..add(LoadMap()), - ) + ), + BlocProvider( + create: (context) => ConnectivityBloc(connectivity), + ), ], child: BlocBuilder( builder: (context, state) { @@ -131,18 +138,42 @@ class Decider extends StatefulWidget { class _DeciderState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( - future: HiveRepository.getData('token', HiveBoxNames.authBox), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (!snapshot.hasData) { - return WelcomeScreen(); - } else { - return NavPage(); - } - } else { - return Scaffold(body: Center(child: Text('An Error occured.'))); - } - }); + return BlocBuilder( + builder: (context, connectivityState) { + debugPrint('Current connectivity state: $connectivityState'); + return Stack( + children: [ + FutureBuilder( + future: HiveRepository.getData('token', HiveBoxNames.authBox), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (!snapshot.hasData) { + return WelcomeScreen(); + } else { + return NavPage(); + } + } else { + return Scaffold( + body: Center(child: Text('An Error occurred.'))); + } + }, + ), + if (connectivityState is ConnectivityOffline) + Positioned( + top: 0, + left: 0, + right: 0, + child: NoInternetBanner( + onClose: () { + context + .read() + .add(ConnectivityBannerDismissed()); + }, + ), + ), + ], + ); + }, + ); } } diff --git a/mobile-v3/lib/src/app/shared/bloc/connectivity_bloc.dart b/mobile-v3/lib/src/app/shared/bloc/connectivity_bloc.dart new file mode 100644 index 0000000000..fdd93a3225 --- /dev/null +++ b/mobile-v3/lib/src/app/shared/bloc/connectivity_bloc.dart @@ -0,0 +1,79 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/foundation.dart'; +import 'dart:io' show InternetAddress; + +part 'connectivity_event.dart'; +part 'connectivity_state.dart'; + +class ConnectivityBloc extends Bloc { + final Connectivity _connectivity; + StreamSubscription? _connectivitySubscription; + bool _bannerDismissed = false; + bool get isBannerDismissed => _bannerDismissed; + + ConnectivityBloc(this._connectivity) : super(ConnectivityInitial()) { + _checkInitialConnectivity(); + + _connectivitySubscription = + _connectivity.onConnectivityChanged.listen((result) { + debugPrint('Connectivity changed: $result'); + add(ConnectivityChanged(result != ConnectivityResult.none)); + }); + + on((event, emit) { + if (event.isConnected) { + emit(ConnectivityOnline()); + } else { + emit(ConnectivityOffline()); + } + }); + } + + Future _hasInternetConnection() async { + try { + final result = await InternetAddress.lookup('google.com'); + return result.isNotEmpty && result[0].rawAddress.isNotEmpty; + } catch (e) { + return false; + } + } + + Future _checkInitialConnectivity() async { + try { + var connectivityResult = await _connectivity.checkConnectivity(); + debugPrint('Initial connectivity check result: $connectivityResult'); + + bool isConnected = connectivityResult != ConnectivityResult.none && + await _hasInternetConnection(); + add(ConnectivityChanged(isConnected)); + } catch (e) { + add(ConnectivityChanged(false)); + } + } + + @override + Future close() { + _connectivitySubscription?.cancel(); + return super.close(); + } + + @override + void onEvent(ConnectivityEvent event) { + if (event is ConnectivityBannerDismissed) { + _bannerDismissed = true; + } + super.onEvent(event); + } + + Stream mapEventToState(ConnectivityEvent event) async* { + if (event is ConnectivityChanged) { + yield event.isConnected ? ConnectivityOnline() : ConnectivityOffline(); + } else if (event is ConnectivityBannerDismissed) { + _bannerDismissed = true; + yield state; + } + } +} diff --git a/mobile-v3/lib/src/app/shared/bloc/connectivity_event.dart b/mobile-v3/lib/src/app/shared/bloc/connectivity_event.dart new file mode 100644 index 0000000000..b51d89e444 --- /dev/null +++ b/mobile-v3/lib/src/app/shared/bloc/connectivity_event.dart @@ -0,0 +1,19 @@ +part of 'connectivity_bloc.dart'; + +abstract class ConnectivityEvent extends Equatable { + const ConnectivityEvent(); + + @override + List get props => []; +} + +class ConnectivityChanged extends ConnectivityEvent { + final bool isConnected; + + const ConnectivityChanged(this.isConnected); + + @override + List get props => [isConnected]; +} + +class ConnectivityBannerDismissed extends ConnectivityEvent {} diff --git a/mobile-v3/lib/src/app/shared/bloc/connectivity_state.dart b/mobile-v3/lib/src/app/shared/bloc/connectivity_state.dart new file mode 100644 index 0000000000..7c5c9f5e28 --- /dev/null +++ b/mobile-v3/lib/src/app/shared/bloc/connectivity_state.dart @@ -0,0 +1,18 @@ +part of 'connectivity_bloc.dart'; + +abstract class ConnectivityState extends Equatable { + const ConnectivityState(); + + @override + List get props => []; +} + +class ConnectivityInitial extends ConnectivityState {} + +class ConnectivityOnline extends ConnectivityState {} + +class ConnectivityOffline extends ConnectivityState { + final bool isDismissed; + + ConnectivityOffline([this.isDismissed = false]); +} diff --git a/mobile-v3/lib/src/app/shared/pages/no_internet_banner.dart b/mobile-v3/lib/src/app/shared/pages/no_internet_banner.dart index e106f58eb4..248c66580f 100644 --- a/mobile-v3/lib/src/app/shared/pages/no_internet_banner.dart +++ b/mobile-v3/lib/src/app/shared/pages/no_internet_banner.dart @@ -1,32 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:airqo/src/app/shared/bloc/connectivity_bloc.dart'; class NoInternetBanner extends StatelessWidget { - final VoidCallback onClose; + final VoidCallback? onClose; - const NoInternetBanner({Key? key, required this.onClose}) : super(key: key); + const NoInternetBanner({Key? key, this.onClose}) : super(key: key); @override Widget build(BuildContext context) { - return Container( - color: Colors.red, - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Internet Connection Lost', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 16.0, + return BlocBuilder( + builder: (context, state) { + if (state is! ConnectivityOffline || + (state).isDismissed) { + return SizedBox.shrink(); + } + + return Container( + color: Colors.red, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: SafeArea( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + 'Internet Connection Lost', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16.0, + ), + overflow: TextOverflow.ellipsis, + ), + ), + IconButton( + icon: Icon(Icons.close, color: Colors.white), + onPressed: () { + context.read().add(ConnectivityBannerDismissed()); + onClose?.call(); + }, + ), + ], ), ), - IconButton( - icon: Icon(Icons.close, color: Colors.white), - onPressed: onClose, - ), - ], - ), + ); + }, ); } -} +} \ No newline at end of file diff --git a/mobile-v3/pubspec.lock b/mobile-v3/pubspec.lock index 622b024007..ea03c35d01 100644 --- a/mobile-v3/pubspec.lock +++ b/mobile-v3/pubspec.lock @@ -158,6 +158,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: "876849631b0c7dc20f8b471a2a03142841b482438e3b707955464f5ffca3e4c3" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" convert: dependency: transitive description: @@ -198,6 +214,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.7" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" equatable: dependency: "direct main" description: @@ -581,6 +605,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" package_config: dependency: transitive description: diff --git a/mobile-v3/pubspec.yaml b/mobile-v3/pubspec.yaml index 563ef8b110..06f08d0ac1 100644 --- a/mobile-v3/pubspec.yaml +++ b/mobile-v3/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: google_maps_flutter: ^2.7.1 flutter_card_swiper: ^7.0.1 logger: ^2.5.0 + connectivity_plus: ^6.1.0 dev_dependencies: flutter_test: