diff --git a/lib/app_config.dart b/lib/app_config.dart index b66a9877..4c457c7d 100644 --- a/lib/app_config.dart +++ b/lib/app_config.dart @@ -1,22 +1,86 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'routes/middleware.dart'; import 'vaahextendflutter/app_theme.dart'; -import 'vaahextendflutter/env/env.dart'; +import 'vaahextendflutter/base/base_bloc/base_bloc.dart'; +import 'vaahextendflutter/env/env_bloc/env_bloc.dart'; +import 'vaahextendflutter/helpers/constants.dart'; import 'vaahextendflutter/widgets/debug.dart'; -final _navigatorKey = GlobalKey(); +final navigatorKey = GlobalKey(); + +class AppLauncher extends StatelessWidget { + const AppLauncher({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: BaseBloc.instance, + buildWhen: (previous, current) { + if (previous is BaseBlocLoaded && current is BaseBlocLoaded) { + return previous.app != current.app; + } + return current is BaseBlocLoaded || current is BaseBlocError; + }, + builder: (context, state) { + if (state is BaseBlocInitial) { + return _initialStateView(); + } else if (state is BaseBlocLoading) { + return _loadingStateView(); + } else if (state is BaseBlocLoaded) { + return state.app; + } else if (state is BaseBlocError) { + return state.errorApp; + } + return ErrorAppConfig(); + }, + ); + } + + Widget _initialStateView() { + return Center( + child: Column( + children: [ + Text( + 'Initial state of Base Bloc', + ), + verticalMargin4, + ElevatedButton( + onPressed: () => BaseBloc.instance.add( + InitializeApp( + app: AppConfig(), + errorApp: ErrorAppConfig(), + ), + ), + child: Text('Tap to load'), + ) + ], + ), + ); + } + + Widget _loadingStateView() { + return const MaterialApp( + home: Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ), + ); + } +} class AppConfig extends StatelessWidget { const AppConfig({super.key}); @override Widget build(BuildContext context) { - EnvironmentConfig env = EnvironmentConfig.getConfig; - return GetMaterialApp( - title: env.appTitle, + return MaterialApp( + navigatorKey: navigatorKey, + title: EnvBloc.instance.config.appTitle, theme: ThemeData( primarySwatch: AppTheme.colors['primary'], ), @@ -26,8 +90,8 @@ class AppConfig extends StatelessWidget { onGenerateRoute: routeMiddleware, builder: (BuildContext context, Widget? child) { return DebugWidget( - navigatorKey: _navigatorKey, - child: child!, + navigatorKey: navigatorKey, + child: child ?? const SizedBox.shrink(), ); }, ); @@ -39,7 +103,7 @@ class ErrorAppConfig extends StatelessWidget { @override Widget build(BuildContext context) { - return GetMaterialApp( + return MaterialApp( theme: ThemeData( primarySwatch: AppTheme.colors['primary'], ), diff --git a/lib/main.dart b/lib/main.dart index 53f49d91..09abaa5a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'app_config.dart'; -import 'vaahextendflutter/base/base_controller.dart'; +import 'vaahextendflutter/base/base_bloc/base_bloc.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - BaseController baseController = Get.put(BaseController()); - await baseController.init( - app: const AppConfig(), - errorApp: const ErrorAppConfig(), - ); // Pass main app as argument in init method + + /// Initialize the app's with core features + BaseBloc.instance.add( + InitializeApp( + app: AppConfig(), + errorApp: const ErrorAppConfig(), + ), + ); + + runApp(AppLauncher()); } diff --git a/lib/vaahextendflutter/base/base_bloc/base_bloc.dart b/lib/vaahextendflutter/base/base_bloc/base_bloc.dart new file mode 100644 index 00000000..5ab6d71d --- /dev/null +++ b/lib/vaahextendflutter/base/base_bloc/base_bloc.dart @@ -0,0 +1,131 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/widgets.dart'; +import 'package:get_storage/get_storage.dart'; + +import '../../app_theme.dart'; +import '../../env/env_bloc/env_bloc.dart'; +import '../../services/api.dart'; +import '../../services/logging_library/logging_library.dart'; +import '../../services/notification/internal/notification.dart'; +import '../../services/notification/push/notification.dart'; +import '../root_asset_bloc/root_asset_bloc.dart'; + +part 'base_bloc_event.dart'; +part 'base_bloc_state.dart'; + +class BaseBloc extends Bloc { + BaseBloc._() : super(BaseBlocInitial()) { + on(_handleAppInitialization); + } + + static final BaseBloc _instance = BaseBloc._(); + + static BaseBloc get instance => _instance; + + Future _initializeStorage() async { + await GetStorage.init(); + } + + void _initializeEnvironment() { + EnvBloc.instance.add(LoadEnvironment()); + } + + void _initializeServices() { + AppTheme.init(); + Api.init(); + } + + void _loadUserFromStorage() { + RootAssetBloc.instance.add(LoadUser()); + } + + Future _initializeNotifications() async { + await PushNotifications.init(); + await InternalNotifications.init(); + PushNotifications.askPermission(); + } + + Future _handleAppInitialization( + InitializeApp event, + Emitter emit, + ) async { + emit(BaseBlocLoading()); + + try { + await _initializeStorage(); + + _initializeEnvironment(); + + // Todo : Depends on datadog implementation PR + // Initialization of Firebase and Services + // if (firebaseOptions != null) { + // await Firebase.initializeApp( + // options: firebaseOptions, + // ); + // } + + _initializeServices(); + + _loadUserFromStorage(); + + _initializeNotifications(); + + // Todo : Initialization of Logging Services will be added once Datadog PR proceed + // Todo : it will return a Widget which will be passed in BaseBlocLoaded State + // Todo : Do not remove the below code + // Sentry Initialization (And/ Or) Running main app + // if (null != config.sentryConfig && config.sentryConfig!.dsn.isNotEmpty) { + // await SentryFlutter.init( + // (options) => options + // ..dsn = config.sentryConfig!.dsn + // ..autoAppStart = config.sentryConfig!.autoAppStart + // ..tracesSampleRate = config.sentryConfig!.tracesSampleRate + // ..enableAutoPerformanceTracing = config.sentryConfig!.enableAutoPerformanceTracing + // ..enableUserInteractionTracing = config.sentryConfig!.enableUserInteractionTracing + // ..environment = config.envType, + // ); + // Widget child = event.app; + // if (config.sentryConfig!.enableUserInteractionTracing) { + // child = SentryUserInteractionWidget( + // child: child, + // ); + // } + // if (config.sentryConfig!.enableAssetsInstrumentation) { + // child = DefaultAssetBundle( + // bundle: SentryAssetBundle( + // enableStructuredDataTracing: true, + // ), + // child: child, + // ); + // } + // // Running main app + // runApp(child); + // } else { + // // Running main app when sentry config is not there + // runApp(app); + // } + + emit( + BaseBlocLoaded(app: event.app), + ); + } catch (e, stackTrace) { + emit( + BaseBlocError( + errorApp: event.errorApp, + errorMessage: e.toString(), + ), + ); + + // Logs an error to LoggingService[Sentry || datadog || firebase] opted by user + // This will log to the remote as well as local based on your ENV config + Log.exception( + e.toString(), + stackTrace: stackTrace, + ); + } + } +} diff --git a/lib/vaahextendflutter/base/base_bloc/base_bloc_event.dart b/lib/vaahextendflutter/base/base_bloc/base_bloc_event.dart new file mode 100644 index 00000000..2445fc56 --- /dev/null +++ b/lib/vaahextendflutter/base/base_bloc/base_bloc_event.dart @@ -0,0 +1,15 @@ +part of 'base_bloc.dart'; + +abstract class BaseBlocEvent {} + +class InitializeApp extends BaseBlocEvent { + final Widget app; + final Widget errorApp; + final FirebaseOptions? firebaseOptions; + + InitializeApp({ + required this.app, + required this.errorApp, + this.firebaseOptions, + }); +} diff --git a/lib/vaahextendflutter/base/base_bloc/base_bloc_state.dart b/lib/vaahextendflutter/base/base_bloc/base_bloc_state.dart new file mode 100644 index 00000000..7b9eb86b --- /dev/null +++ b/lib/vaahextendflutter/base/base_bloc/base_bloc_state.dart @@ -0,0 +1,35 @@ +part of 'base_bloc.dart'; + +abstract class BaseBlocState extends Equatable { + const BaseBlocState(); + @override + List get props => []; +} + +class BaseBlocInitial extends BaseBlocState {} + +class BaseBlocLoading extends BaseBlocState {} + +class BaseBlocLoaded extends BaseBlocState { + final Widget app; + + const BaseBlocLoaded({ + required this.app, + }); + + @override + List get props => [app]; +} + +class BaseBlocError extends BaseBlocState { + final Widget errorApp; + final String errorMessage; + + const BaseBlocError({ + required this.errorApp, + required this.errorMessage, + }); + + @override + List get props => [errorApp, errorMessage]; +} diff --git a/lib/vaahextendflutter/base/base_controller.dart b/lib/vaahextendflutter/base/base_controller.dart deleted file mode 100644 index f0df6c43..00000000 --- a/lib/vaahextendflutter/base/base_controller.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; - -import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; - -import '../app_theme.dart'; -import '../env/env.dart'; -import '../services/api.dart'; -import '../services/notification/internal/notification.dart'; -import '../services/notification/push/notification.dart'; -import 'root_assets_controller.dart'; - -class BaseController extends GetxController { - Future init({ - required Widget app, - required Widget errorApp, - FirebaseOptions? firebaseOptions, - }) async { - try { - // Storage initialization to store some properties locally - await GetStorage.init(); - - // Environment initialization - final envController = Get.put(EnvController()); - await envController.initialize(); - final EnvironmentConfig config = EnvironmentConfig.getConfig; - - // Initialization of Firebase and Services - if (firebaseOptions != null) { - await Firebase.initializeApp( - options: firebaseOptions, - ); - } - - // Other Local Initializations (Depends on your app) - AppTheme.init(); - Api.init(); - - // RootAssets - Get.put(RootAssetsController()); - - // Other Core Services - await PushNotifications.init(); - await InternalNotifications.init(); - PushNotifications.askPermission(); - - // Sentry Initialization (And/ Or) Running main app - if (null != config.sentryConfig && config.sentryConfig!.dsn.isNotEmpty) { - await SentryFlutter.init( - (options) => options - ..dsn = config.sentryConfig!.dsn - ..autoAppStart = config.sentryConfig!.autoAppStart - ..tracesSampleRate = config.sentryConfig!.tracesSampleRate - ..enableAutoPerformanceTracing = config.sentryConfig!.enableAutoPerformanceTracing - ..enableUserInteractionTracing = config.sentryConfig!.enableUserInteractionTracing - ..environment = config.envType, - ); - Widget child = app; - if (config.sentryConfig!.enableUserInteractionTracing) { - child = SentryUserInteractionWidget( - child: child, - ); - } - if (config.sentryConfig!.enableAssetsInstrumentation) { - child = DefaultAssetBundle( - bundle: SentryAssetBundle( - enableStructuredDataTracing: true, - ), - child: child, - ); - } - // Running main app - runApp(child); - } else { - // Running main app when sentry config is not there - runApp(app); - } - } catch (error, stackTrace) { - debugPrint(error.toString()); - debugPrintStack(stackTrace: stackTrace); - runApp(errorApp); - } - } -} diff --git a/lib/vaahextendflutter/base/root_asset_bloc/root_asset_bloc.dart b/lib/vaahextendflutter/base/root_asset_bloc/root_asset_bloc.dart new file mode 100644 index 00000000..5831e9d4 --- /dev/null +++ b/lib/vaahextendflutter/base/root_asset_bloc/root_asset_bloc.dart @@ -0,0 +1,64 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:get_storage/get_storage.dart'; + +import '../../../models/user.dart'; + +part 'root_asset_event.dart'; +part 'root_asset_state.dart'; + +const String userKey = 'user'; + +class RootAssetBloc extends Bloc { + RootAssetBloc._() : super(RootAssetInitial()) { + on(_onLoadUser); + on(_onUpdateUser); + } + + static final RootAssetBloc _instance = RootAssetBloc._(); + + static RootAssetBloc get instance => _instance; + + final GetStorage _storage = GetStorage(); + + Future _onLoadUser( + LoadUser event, + Emitter emit, + ) async { + try { + final rawUser = _storage.read(userKey); + + if (rawUser == null || rawUser.isEmpty) { + emit(RootAssetLoaded(null)); + return; + } + + final user = User.fromJson(jsonDecode(rawUser)); + emit(RootAssetLoaded(user)); + } catch (e) { + emit(RootAssetError("Failed to load user: ${e.toString()}")); + } + } + + Future _onUpdateUser( + UpdateUser event, + Emitter emit, + ) async { + try { + if (event.user != null) { + await _storage.write( + userKey, + jsonEncode( + event.user!.toJson(), + ), + ); + } + emit(RootAssetLoaded(event.user)); + } catch (e) { + emit(RootAssetError("Failed to update user: ${e.toString()}")); + } + } +} diff --git a/lib/vaahextendflutter/base/root_asset_bloc/root_asset_event.dart b/lib/vaahextendflutter/base/root_asset_bloc/root_asset_event.dart new file mode 100644 index 00000000..ba6d5aaf --- /dev/null +++ b/lib/vaahextendflutter/base/root_asset_bloc/root_asset_event.dart @@ -0,0 +1,19 @@ +part of 'root_asset_bloc.dart'; + +abstract class RootAssetEvent extends Equatable { + const RootAssetEvent(); + + @override + List get props => []; +} + +class LoadUser extends RootAssetEvent {} + +class UpdateUser extends RootAssetEvent { + final User? user; + + const UpdateUser(this.user); + + @override + List get props => [user]; +} diff --git a/lib/vaahextendflutter/base/root_asset_bloc/root_asset_state.dart b/lib/vaahextendflutter/base/root_asset_bloc/root_asset_state.dart new file mode 100644 index 00000000..be6d945a --- /dev/null +++ b/lib/vaahextendflutter/base/root_asset_bloc/root_asset_state.dart @@ -0,0 +1,28 @@ +part of 'root_asset_bloc.dart'; + +abstract class RootAssetBlocState extends Equatable { + const RootAssetBlocState(); + + @override + List get props => []; +} + +class RootAssetInitial extends RootAssetBlocState {} + +class RootAssetLoaded extends RootAssetBlocState { + final User? user; + + const RootAssetLoaded(this.user); + + @override + List get props => [user]; +} + +class RootAssetError extends RootAssetBlocState { + final String message; + + const RootAssetError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/vaahextendflutter/base/root_assets_controller.dart b/lib/vaahextendflutter/base/root_assets_controller.dart deleted file mode 100644 index ddbb9fa8..00000000 --- a/lib/vaahextendflutter/base/root_assets_controller.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; - -import '../../models/user.dart'; - -const String userKey = 'user'; - -class RootAssetsController extends GetxController { - RootAssetsController() { - if (_storage.hasData(userKey)) { - final String rawUser = _storage.read(userKey); - _user = User.fromJson(jsonDecode(rawUser)); - } - } - - final _storage = GetStorage(); - - User? _user; - User? get user => _user; - final StreamController _userStreamController = StreamController.broadcast(); - Stream get userStream => _userStreamController.stream; - - void setUser(User? updatedUser) async { - await _storage.write(userKey, jsonEncode(user?.toJson())); - _user = updatedUser; - _userStreamController.add(user); - update(); - } - - // TODO: Need to use api token in Api.ajax -} diff --git a/lib/vaahextendflutter/env/env.dart b/lib/vaahextendflutter/env/env.dart index eb09b9c0..3b5592c8 100644 --- a/lib/vaahextendflutter/env/env.dart +++ b/lib/vaahextendflutter/env/env.dart @@ -1,49 +1,46 @@ -import 'dart:convert'; -import 'dart:io'; - +import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; import 'package:json_annotation/json_annotation.dart'; -import '../services/logging_library/logging_library.dart'; import 'logging.dart'; import 'notification.dart'; part 'env.g.dart'; -class EnvController extends GetxController { - EnvironmentConfig _config = EnvironmentConfig.defaultConfig(); - EnvironmentConfig get config => _config; +// Todo : Remove the below only GetxController code +// Todo : Shifted to EnvBloc +// class EnvController extends GetxController { +// EnvironmentConfig _config = EnvironmentConfig.defaultConfig(); +// EnvironmentConfig get config => _config; - Future initialize() async { - try { - const String envPath = String.fromEnvironment("ENV_PATH"); - if (envPath.isEmpty) { - Log.warning("INVALID ENVIRONMENT PATH"); - return; - } - Log.success("ENVIRONMENT PATH: $envPath"); - final String jsonConfig = await rootBundle.loadString(envPath); - if (jsonConfig.isNotEmpty) { - final Map json = jsonDecode(jsonConfig); - _config = EnvironmentConfig.fromJson(json); - } else { - throw Exception('Environment configuration not found for key: $envPath'); - } - } catch (error, stackTrace) { - Log.exception( - "error-occured-while-initializing-env-controller", - throwable: error, - stackTrace: stackTrace, - ); - exit(0); - } - } -} +// Future initialize() async { +// try { +// const String envPath = String.fromEnvironment("ENV_PATH"); +// if (envPath.isEmpty) { +// Log.warning("INVALID ENVIRONMENT PATH"); +// return; +// } +// Log.success("ENVIRONMENT PATH: $envPath"); +// final String jsonConfig = await rootBundle.loadString(envPath); +// if (jsonConfig.isNotEmpty) { +// final Map json = jsonDecode(jsonConfig); +// _config = EnvironmentConfig.fromJson(json); +// } else { +// throw Exception('Environment configuration not found for key: $envPath'); +// } +// } catch (error, stackTrace) { +// Log.exception( +// "error-occured-while-initializing-env-controller", +// throwable: error, +// stackTrace: stackTrace, +// ); +// exit(0); +// } +// } +// } @JsonSerializable(fieldRename: FieldRename.snake) -class EnvironmentConfig { +class EnvironmentConfig extends Equatable { const EnvironmentConfig({ required this.appTitle, required this.appTitleShort, @@ -98,16 +95,6 @@ class EnvironmentConfig { Map toJson() => _$EnvironmentConfigToJson(this); - static EnvironmentConfig get getConfig { - final bool isRegistered = Get.isRegistered(); - if (isRegistered) { - EnvController envController = Get.find(); - return envController.config; - } else { - return EnvironmentConfig.defaultConfig(); - } - } - factory EnvironmentConfig.defaultConfig() { return EnvironmentConfig( appTitle: 'VaahFlutter', @@ -126,4 +113,8 @@ class EnvironmentConfig { debugPanelColor: Colors.black.withOpacity(0.8), ); } + + @override + // TODO: implement props + List get props => [envType]; } diff --git a/lib/vaahextendflutter/env/env_bloc/env_bloc.dart b/lib/vaahextendflutter/env/env_bloc/env_bloc.dart new file mode 100644 index 00000000..8a2ed474 --- /dev/null +++ b/lib/vaahextendflutter/env/env_bloc/env_bloc.dart @@ -0,0 +1,68 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/services.dart'; + +import '../../services/logging_library/logging_library.dart'; +import '../env.dart'; + +part 'env_event.dart'; +part 'env_state.dart'; + +class EnvBloc extends Bloc { + EnvironmentConfig _config = EnvironmentConfig.defaultConfig(); + + EnvBloc._() : super(EnvInitial()) { + on(_onLoadEnvironment); + } + + static final EnvBloc _instance = EnvBloc._(); + + static EnvBloc get instance => _instance; + + /// Get current configuration + EnvironmentConfig get config => _config; + + /// Load Environment from ENV_PATH + Future _onLoadEnvironment( + LoadEnvironment event, + Emitter emit, + ) async { + emit(EnvLoading()); + + try { + const String envPath = String.fromEnvironment("ENV_PATH"); + if (envPath.isEmpty) { + Log.warning("INVALID ENVIRONMENT PATH"); + emit(EnvError("Invalid environment path.")); + return; + } + + final String jsonConfig = await rootBundle.loadString(envPath); + + if (jsonConfig.isNotEmpty) { + final Map json = jsonDecode(jsonConfig); + _config = EnvironmentConfig.fromJson(json); + emit(EnvLoaded(_config)); + Log.success( + "ENVIRONMENT PATH: $envPath", + disableCloudLogging: true, + ); + } else { + throw Exception('Environment configuration not found for key: $envPath'); + } + } catch (error, stackTrace) { + Log.exception( + "error-occured-while-initializing-env-bloc", + throwable: error, + stackTrace: stackTrace, + hint: "Ensure ENV_PATH is set and contains valid JSON.", + ); + emit(EnvError("Failed to load environment: ${error.toString()}")); + exit(0); // Exit the app as in the original code + } + } +} diff --git a/lib/vaahextendflutter/env/env_bloc/env_event.dart b/lib/vaahextendflutter/env/env_bloc/env_event.dart new file mode 100644 index 00000000..9aee924c --- /dev/null +++ b/lib/vaahextendflutter/env/env_bloc/env_event.dart @@ -0,0 +1,11 @@ +part of 'env_bloc.dart'; + +abstract class EnvBlocEvent extends Equatable { + const EnvBlocEvent(); + + @override + List get props => []; +} + +/// Event to Load Environment on Startup +class LoadEnvironment extends EnvBlocEvent {} diff --git a/lib/vaahextendflutter/env/env_bloc/env_state.dart b/lib/vaahextendflutter/env/env_bloc/env_state.dart new file mode 100644 index 00000000..4b831512 --- /dev/null +++ b/lib/vaahextendflutter/env/env_bloc/env_state.dart @@ -0,0 +1,30 @@ +part of 'env_bloc.dart'; + +abstract class EnvBlocState extends Equatable { + const EnvBlocState(); + + @override + List get props => []; +} + +class EnvInitial extends EnvBlocState {} + +class EnvLoading extends EnvBlocState {} + +class EnvLoaded extends EnvBlocState { + final EnvironmentConfig config; + + const EnvLoaded(this.config); + + @override + List get props => [config]; +} + +class EnvError extends EnvBlocState { + final String message; + + const EnvError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/vaahextendflutter/helpers/alerts.dart b/lib/vaahextendflutter/helpers/alerts.dart index 3cc9d434..0403f5c0 100644 --- a/lib/vaahextendflutter/helpers/alerts.dart +++ b/lib/vaahextendflutter/helpers/alerts.dart @@ -1,7 +1,8 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:get/get.dart'; +import '../../app_config.dart'; import '../app_theme.dart'; import 'constants.dart'; @@ -25,71 +26,78 @@ class Alerts { List? actions, Color color = Colors.white, }) { - return Get.dialog( - AlertDialog( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(defaultPadding), + BuildContext? context = navigatorKey.currentContext; + if (context == null) { + return; + } + return showCupertinoModalPopup( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(defaultPadding), + ), ), - ), - contentPadding: allPadding8, - title: Center(child: Text(title)), - content: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (messages != null && messages.isNotEmpty) ...[ - verticalMargin12, - Padding( - padding: horizontalPadding8, - child: Text( - messages.join('\n'), - textAlign: TextAlign.center, + contentPadding: allPadding8, + title: Center(child: Text(title)), + content: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (messages != null && messages.isNotEmpty) ...[ + verticalMargin12, + Padding( + padding: horizontalPadding8, + child: Text( + messages.join('\n'), + textAlign: TextAlign.center, + ), ), - ), - ], - if ((messages != null && messages.isNotEmpty) || - (hint != null && hint.trim().isNotEmpty)) - verticalMargin8, - if (hint != null && hint.trim().isNotEmpty) ...[ - Padding( - padding: horizontalPadding8, - child: Text( - hint, - textAlign: TextAlign.center, - style: TextStyle(color: hintColor), + ], + if ((messages != null && messages.isNotEmpty) || + (hint != null && hint.trim().isNotEmpty)) + verticalMargin8, + if (hint != null && hint.trim().isNotEmpty) ...[ + Padding( + padding: horizontalPadding8, + child: Text( + hint, + textAlign: TextAlign.center, + style: TextStyle(color: hintColor), + ), ), - ), - verticalMargin8, + verticalMargin8, + ], ], - ], + ), ), - ), - actions: [ - if (actions == null || actions.isNotEmpty) - Center( - child: ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: color), - child: Text( - 'Ok', - style: TextStyle( - color: color == AppTheme.colors['white'] - ? AppTheme.colors['black'] - : AppTheme.colors['white'], + actions: [ + if (actions == null || actions.isNotEmpty) + Center( + child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: color), + child: Text( + 'Ok', + style: TextStyle( + color: color == AppTheme.colors['white'] + ? AppTheme.colors['black'] + : AppTheme.colors['white'], + ), ), + onPressed: () { + Navigator.of(context).pop(); + }, ), - onPressed: () { - Get.back(); - }, - ), - ) - else - ...actions, - ], - ), - barrierDismissible: false, + ) + else + ...actions, + ], + ); + }, ); } diff --git a/lib/vaahextendflutter/helpers/extensions/string_extensions.dart b/lib/vaahextendflutter/helpers/extensions/string_extensions.dart new file mode 100644 index 00000000..ecb80360 --- /dev/null +++ b/lib/vaahextendflutter/helpers/extensions/string_extensions.dart @@ -0,0 +1,3 @@ +extension StringExtensions on String { + bool get isAlphabetOnly => RegExp(r'^[a-zA-Z]+$').hasMatch(this); +} diff --git a/lib/vaahextendflutter/services/api.dart b/lib/vaahextendflutter/services/api.dart index 9224850f..72951846 100644 --- a/lib/vaahextendflutter/services/api.dart +++ b/lib/vaahextendflutter/services/api.dart @@ -5,12 +5,14 @@ import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:get/get.dart' as getx; +import '../../app_config.dart'; import '../app_theme.dart'; import '../env/env.dart'; +import '../env/env_bloc/env_bloc.dart'; import '../helpers/alerts.dart'; import '../helpers/constants.dart'; +import '../helpers/extensions/string_extensions.dart'; import 'logging_library/logging_library.dart'; // alertType : 'dialog', 'toast', @@ -153,7 +155,10 @@ abstract class Api { static void init() { // get env controller to get variable apiUrl - _config = EnvironmentConfig.getConfig; + // Todo : Check is configs getting loaded or not + final config = EnvBloc.instance.config; + _config = EnvBloc.instance.config; + _apiBaseUrl = _config.apiUrl; if (_config.enableApiLogInterceptor) { _dio.interceptors.add( @@ -547,33 +552,39 @@ Future _showDialog({ List? content, String? hint, List? actions, -}) { - return getx.Get.dialog( - CupertinoAlertDialog( - title: Text(title), - content: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (content != null && content.isNotEmpty) Text(content.join('\n')), - if (content != null && content.isNotEmpty) verticalMargin12, - if (hint != null && hint.trim().isNotEmpty) Text(hint), - ], +}) async { + BuildContext? context = navigatorKey.currentContext; + if (context == null) { + return; + } + return showCupertinoModalPopup( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: Text(title), + content: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (content != null && content.isNotEmpty) Text(content.join('\n')), + if (content != null && content.isNotEmpty) verticalMargin12, + if (hint != null && hint.trim().isNotEmpty) Text(hint), + ], + ), ), - ), - actions: [ - if (actions == null || actions.isNotEmpty) - CupertinoButton( - child: const Text('Ok'), - onPressed: () { - getx.Get.back(); - }, - ) - else - ...actions, - ], - ), - barrierDismissible: false, + actions: [ + if (actions == null || actions.isNotEmpty) + CupertinoButton( + child: const Text('Ok'), + onPressed: () { + Navigator.of(context).pop(); + }, + ) + else + ...actions, + ], + ); + }, ); } diff --git a/lib/vaahextendflutter/services/logging_library/logging_library.dart b/lib/vaahextendflutter/services/logging_library/logging_library.dart index a58d1d89..f1f8ba10 100644 --- a/lib/vaahextendflutter/services/logging_library/logging_library.dart +++ b/lib/vaahextendflutter/services/logging_library/logging_library.dart @@ -1,11 +1,13 @@ import '../../env/env.dart'; +import '../../env/env_bloc/env_bloc.dart'; import '_cloud/firebase_logging_service.dart'; import '_cloud/sentry_logging_service.dart'; import '_local/console_service.dart'; import 'models/log.dart'; class Log { - static final EnvironmentConfig _config = EnvironmentConfig.getConfig; + // Every time _config is accessed, it will fetch the latest configuration from EnvBloc. + static EnvironmentConfig get _config => EnvBloc.instance.config; static final List _services = [ SentryLoggingService, diff --git a/lib/vaahextendflutter/services/notification/internal/notification.dart b/lib/vaahextendflutter/services/notification/internal/notification.dart index cf2e66b9..1df8c630 100644 --- a/lib/vaahextendflutter/services/notification/internal/notification.dart +++ b/lib/vaahextendflutter/services/notification/internal/notification.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import '../../../env/env.dart'; +import '../../../env/env_bloc/env_bloc.dart'; import '../../../env/notification.dart'; import '../models/notification.dart'; import 'services/base_service.dart'; @@ -11,7 +11,7 @@ import 'services/pusher.dart'; InternalNotificationsService get getService { final InternalNotificationsServiceType serviceType = - EnvironmentConfig.getConfig.internalNotificationsServiceType; + EnvBloc.instance.config.internalNotificationsServiceType; switch (serviceType) { case InternalNotificationsServiceType.firebase: return InternalNotificationsWithFirebase(); diff --git a/lib/vaahextendflutter/services/notification/internal/notification_view.dart b/lib/vaahextendflutter/services/notification/internal/notification_view.dart index a3ce12ad..078a8af4 100644 --- a/lib/vaahextendflutter/services/notification/internal/notification_view.dart +++ b/lib/vaahextendflutter/services/notification/internal/notification_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../../../env/env.dart'; +import '../../../env/env_bloc/env_bloc.dart'; import '../../../env/notification.dart'; import '../../../helpers/constants.dart'; import 'notification.dart'; @@ -15,7 +16,7 @@ class InternalNotificationsBadge extends StatefulWidget { } class _InternalNotificationsBadgeState extends State { - final EnvironmentConfig _environmentConfig = EnvironmentConfig.getConfig; + final EnvironmentConfig _environmentConfig = EnvBloc.instance.config; @override Widget build(BuildContext context) { diff --git a/lib/vaahextendflutter/services/notification/internal/services/pusher.dart b/lib/vaahextendflutter/services/notification/internal/services/pusher.dart index 77e8d67e..1e8c2d3e 100644 --- a/lib/vaahextendflutter/services/notification/internal/services/pusher.dart +++ b/lib/vaahextendflutter/services/notification/internal/services/pusher.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:pusher_channels_flutter/pusher_channels_flutter.dart'; import '../../../../env/env.dart'; +import '../../../../env/env_bloc/env_bloc.dart'; import '../../../logging_library/logging_library.dart'; import '../../models/notification.dart'; import 'base_service.dart'; @@ -40,7 +41,7 @@ class InternalNotificationsWithPusher implements InternalNotificationsService { // Write your logic here to get user id userId = 'userId'; - final EnvironmentConfig environmentConfig = EnvironmentConfig.getConfig; + final EnvironmentConfig environmentConfig = EnvBloc.instance.config; if (environmentConfig.pusherConfig == null) return; _pusher = PusherChannelsFlutter.getInstance(); diff --git a/lib/vaahextendflutter/services/notification/push/notification.dart b/lib/vaahextendflutter/services/notification/push/notification.dart index 86ae2aa3..4810a5ed 100644 --- a/lib/vaahextendflutter/services/notification/push/notification.dart +++ b/lib/vaahextendflutter/services/notification/push/notification.dart @@ -1,10 +1,7 @@ import 'dart:async'; -import 'package:get/get.dart'; - -import '../../../../models/user.dart'; -import '../../../base/root_assets_controller.dart'; -import '../../../env/env.dart'; +import '../../../base/root_asset_bloc/root_asset_bloc.dart'; +import '../../../env/env_bloc/env_bloc.dart'; import '../../../env/notification.dart'; import '../models/notification.dart'; import 'services/local.dart'; @@ -12,7 +9,7 @@ import 'services/remote.dart'; abstract class PushNotifications { static final PushNotificationsServiceType _pushNotificationsServiceType = - EnvironmentConfig.getConfig.pushNotificationsServiceType; + EnvBloc.instance.config.pushNotificationsServiceType; static Future init() async { switch (_pushNotificationsServiceType) { @@ -34,16 +31,17 @@ abstract class PushNotifications { } static void _listen() { - final RootAssetsController assetController = Get.find(); - assetController.userStream.listen( - (User? user) { - if (user == null) { + final rootAssetBloc = RootAssetBloc.instance; + + rootAssetBloc.stream.listen((state) { + if (state is RootAssetLoaded) { + if (state.user == null) { unsubscribe(); } else { - subscribe(userid: user.id); + subscribe(userid: state.user!.id); } - }, - ); + } + }); } static void dispose() { diff --git a/lib/vaahextendflutter/services/notification/push/services/remote.dart b/lib/vaahextendflutter/services/notification/push/services/remote.dart index 628d10df..c51b9105 100644 --- a/lib/vaahextendflutter/services/notification/push/services/remote.dart +++ b/lib/vaahextendflutter/services/notification/push/services/remote.dart @@ -1,9 +1,11 @@ import 'dart:async'; -import 'package:get/get.dart'; +import 'package:flutter/material.dart'; import 'package:onesignal_flutter/onesignal_flutter.dart'; +import '../../../../../app_config.dart'; import '../../../../env/env.dart'; +import '../../../../env/env_bloc/env_bloc.dart'; import '../../../api.dart'; import '../../../logging_library/logging_library.dart'; import '../../models/notification.dart'; @@ -14,7 +16,7 @@ const Map channels = { }; abstract class RemoteNotifications { - static final EnvironmentConfig _env = EnvironmentConfig.getConfig; + static final EnvironmentConfig _env = EnvBloc.instance.config; static Future init() async { if (_env.oneSignalConfig == null) return; @@ -108,7 +110,12 @@ abstract class RemoteNotifications { ); final dynamic payload = openedResult.notification.additionalData?['payload']; if (payload != null && payload['path'] != null) { - Get.to( + BuildContext? context = navigatorKey.currentContext; + + if (context == null) return; + + Navigator.pushNamed( + context, payload['path'], arguments: { 'data': payload['data'], diff --git a/lib/vaahextendflutter/widgets/debug.dart b/lib/vaahextendflutter/widgets/debug.dart index e25c8db0..ad3fbc10 100644 --- a/lib/vaahextendflutter/widgets/debug.dart +++ b/lib/vaahextendflutter/widgets/debug.dart @@ -8,6 +8,7 @@ // ***************************************** import 'package:flutter/material.dart'; +import 'package:vaahflutter/vaahextendflutter/env/env_bloc/env_bloc.dart'; import '../app_theme.dart'; import '../env/env.dart'; @@ -49,7 +50,7 @@ class DebugWidgetState extends State with SingleTickerProviderState void initState() { super.initState(); // get env controller and set variable showDebugPanel - _environmentConfig = EnvironmentConfig.getConfig; + _environmentConfig = EnvBloc.instance.config; showDebugPanel = _environmentConfig.showDebugPanel; // initialise AnimationController _controller = AnimationController( diff --git a/lib/views/pages/ui/components/buttons/icon_and_label.dart b/lib/views/pages/ui/components/buttons/icon_and_label.dart index 21c08320..f9f3cf6c 100644 --- a/lib/views/pages/ui/components/buttons/icon_and_label.dart +++ b/lib/views/pages/ui/components/buttons/icon_and_label.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + import '../../../../../vaahextendflutter/app_theme.dart'; import '../../../../../vaahextendflutter/helpers/constants.dart'; import '../../../../../vaahextendflutter/helpers/styles.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 585b07c8..86c7ef35 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,8 @@ dependencies: # Log and Performance sentry_flutter: ^8.9.0 # State Management - get: ^4.6.6 + bloc: ^9.0.0 + flutter_bloc: ^9.0.0 # Api dio: ^5.7.0 # Utilities @@ -44,6 +45,7 @@ dependencies: # Icons cupertino_icons: ^1.0.8 font_awesome_flutter: ^10.7.0 + equatable: ^2.0.7 dev_dependencies: flutter_test: