From 9fd933e29d130620c8bb5db46e44c0876a194e5d Mon Sep 17 00:00:00 2001 From: Miller Adulu Date: Fri, 30 Aug 2024 11:55:10 +0300 Subject: [PATCH 1/5] Update firebase_remote_config --- pubspec.lock | 24 ++++++++++++++++++++++++ pubspec.yaml | 1 + 2 files changed, 25 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index de41f19..01618ef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -425,6 +425,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.6.41" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: b5c23fb7f5b8fd2338f512587a8d2714b5b81dc02508a1c16163c51c1aa41991 + url: "https://pub.dev" + source: hosted + version: "5.1.0" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: "127ebc8b7c905d211396cab3b0984e4436c6350400805d196607043b8984d09c" + url: "https://pub.dev" + source: hosted + version: "1.4.41" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: "29dbff195c6225f957af541d325426f1697710ac36d169431c95bc92d985f4d2" + url: "https://pub.dev" + source: hosted + version: "1.6.13" fixnum: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c73c762..0b03c6a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: firebase_auth: ^5.2.0 firebase_core: ^3.4.0 firebase_crashlytics: ^4.1.0 + firebase_remote_config: ^5.1.0 flutter: sdk: flutter flutter_bloc: ^8.1.5 From dcf69d67b405888a1bc80b81cf73ba3d9a77bccc Mon Sep 17 00:00:00 2001 From: Miller Adulu Date: Fri, 30 Aug 2024 12:48:06 +0300 Subject: [PATCH 2/5] Initialize firebase configuration --- .gitignore | 1 + build.yaml | 6 +++++ lib/bootstrap.dart | 9 +++++++ lib/common/data/models/remote_config.dart | 15 +++++++++++ .../repository/firebase_repository.dart | 25 +++++++++++++++++++ lib/common/utils/misc.dart | 5 ++++ pubspec.lock | 8 ++++++ pubspec.yaml | 1 + 8 files changed, 70 insertions(+) create mode 100644 build.yaml create mode 100644 lib/common/data/models/remote_config.dart create mode 100644 lib/common/repository/firebase_repository.dart diff --git a/.gitignore b/.gitignore index 19e516f..ebab6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ app.*.map.json .fvmrc .vscode/settings.json devtools_options.yaml +lib/versioning/build_version.dart diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..f55603f --- /dev/null +++ b/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + build_version: + options: + output: lib/versioning/build_version.dart \ No newline at end of file diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index d87a818..ae83fc6 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -7,7 +7,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fluttercon/common/repository/db_repository.dart'; +import 'package:fluttercon/common/repository/firebase_repository.dart'; import 'package:fluttercon/common/repository/hive_repository.dart'; +import 'package:fluttercon/common/utils/misc.dart'; import 'package:fluttercon/common/utils/notification_service.dart'; import 'package:fluttercon/core/di/injectable.dart'; import 'package:fluttercon/features/about/cubit/fetch_individual_organisers_cubit.dart'; @@ -22,6 +24,7 @@ import 'package:fluttercon/features/home/cubit/home_cubits.dart'; import 'package:fluttercon/features/sessions/cubit/bookmark_session_cubit.dart'; import 'package:fluttercon/features/sessions/cubit/fetch_grouped_sessions_cubit.dart'; import 'package:fluttercon/firebase_options.dart'; +import 'package:logger/logger.dart'; class AppBlocObserver extends BlocObserver { const AppBlocObserver(); @@ -48,10 +51,16 @@ Future bootstrap(FutureOr Function() builder) async { ); await configureDependencies(); + await getIt().initBoxes(); + localDB = await getIt().init(); + await getIt().requestPermission(); await getIt().initNotifications(); + + await getIt().init(); + runApp( MultiBlocProvider( // Register all the BLoCs here diff --git a/lib/common/data/models/remote_config.dart b/lib/common/data/models/remote_config.dart new file mode 100644 index 0000000..5053102 --- /dev/null +++ b/lib/common/data/models/remote_config.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'remote_config.freezed.dart'; +part 'remote_config.g.dart'; + +@freezed +class RemoteConfig with _$RemoteConfig { + factory RemoteConfig({ + @JsonKey(name: 'app_version') required String appVersion, + @JsonKey(name: 'is_in_review') required bool isInReview, + }) = _RemoteConfig; + + factory RemoteConfig.fromJson(Map json) => + _$RemoteConfigFromJson(json); +} diff --git a/lib/common/repository/firebase_repository.dart b/lib/common/repository/firebase_repository.dart new file mode 100644 index 0000000..b093a46 --- /dev/null +++ b/lib/common/repository/firebase_repository.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:fluttercon/common/data/models/remote_config.dart'; +import 'package:injectable/injectable.dart'; +import 'package:logger/logger.dart'; + +@singleton +class FirebaseRepository { + final remoteConfig = FirebaseRemoteConfig.instance; + + Future init() async { + await remoteConfig.fetchAndActivate(); + } + + RemoteConfig getConfig() { + final config = remoteConfig.getValue('dev_flutterconke_fluttercon'); + + return RemoteConfig.fromJson( + json.decode( + config.asString(), + ) as Map, + ); + } +} diff --git a/lib/common/utils/misc.dart b/lib/common/utils/misc.dart index 5def7bb..a4f2136 100644 --- a/lib/common/utils/misc.dart +++ b/lib/common/utils/misc.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; +import 'package:fluttercon/versioning/build_version.dart' as package_version; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -46,4 +47,8 @@ class Misc { return (isLightMode, colorScheme); } + + static String getAppVersion() { + return package_version.packageVersion; + } } diff --git a/pubspec.lock b/pubspec.lock index 01618ef..a34f16f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -153,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.3.2" + build_version: + dependency: "direct main" + description: + name: build_version + sha256: "4e8eafbf722eac3bd60c8d38f108c04bd69b80100f8792b32be3407725c7fa6a" + url: "https://pub.dev" + source: hosted + version: "2.1.1" built_collection: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0b03c6a..bf633b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: auto_size_text: ^3.0.0 awesome_notifications: ^0.9.3+1 bloc: ^8.1.4 + build_version: ^2.1.1 cached_network_image: ^3.4.0 collection: ^1.18.0 cupertino_icons: ^1.0.8 From 3531d8c858ac4bd5e60f9ee0eaaf71de76de2d36 Mon Sep 17 00:00:00 2001 From: Miller Adulu Date: Fri, 30 Aug 2024 13:25:10 +0300 Subject: [PATCH 3/5] Perform a ghost sign in if the app is in review --- lib/bootstrap.dart | 11 +++++- lib/common/repository/auth_repository.dart | 17 +++++++++ .../auth/cubit/ghost_sign_in_cubit.dart | 36 +++++++++++++++++++ .../auth/cubit/ghost_sign_in_state.dart | 9 +++++ lib/features/auth/ui/sign_in.dart | 36 ++++++++++++++++++- lib/main_production.dart | 4 +-- pubspec.yaml | 2 +- 7 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 lib/features/auth/cubit/ghost_sign_in_cubit.dart create mode 100644 lib/features/auth/cubit/ghost_sign_in_state.dart diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index ae83fc6..3806039 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -13,6 +13,7 @@ import 'package:fluttercon/common/utils/misc.dart'; import 'package:fluttercon/common/utils/notification_service.dart'; import 'package:fluttercon/core/di/injectable.dart'; import 'package:fluttercon/features/about/cubit/fetch_individual_organisers_cubit.dart'; +import 'package:fluttercon/features/auth/cubit/ghost_sign_in_cubit.dart'; import 'package:fluttercon/features/auth/cubit/google_sign_in_cubit.dart'; import 'package:fluttercon/features/auth/cubit/log_out_cubit.dart'; import 'package:fluttercon/features/auth/cubit/social_auth_sign_in_cubit.dart'; @@ -137,7 +138,15 @@ Future bootstrap(FutureOr Function() builder) async { ), ), BlocProvider( - create: (context) => SendFeedbackCubit(apiRepository: getIt()), + create: (context) => SendFeedbackCubit( + apiRepository: getIt(), + ), + ), + BlocProvider( + create: (context) => GhostSignInCubit( + authRepository: getIt(), + hiveRepository: getIt(), + ), ), ], child: await builder(), diff --git a/lib/common/repository/auth_repository.dart b/lib/common/repository/auth_repository.dart index 9c90b58..24f050c 100644 --- a/lib/common/repository/auth_repository.dart +++ b/lib/common/repository/auth_repository.dart @@ -1,5 +1,6 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:fluttercon/common/data/models/models.dart'; +import 'package:fluttercon/common/utils/env/flavor_config.dart'; import 'package:fluttercon/common/utils/network.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:injectable/injectable.dart'; @@ -18,6 +19,22 @@ class AuthRepository { ], ); + Future ghostSignIn() async { + try { + final response = await _networkUtil.postReq( + '${FlutterConConfig.instance!.values.baseUrl}/api/v1/login', + body: { + 'email': 'google@play.com', + 'password': 'password', + }, + ); + + return AuthResult.fromJson(response); + } catch (e) { + rethrow; + } + } + Future signInWithGoogle() async { try { final googleSignInAccount = await _googleSignIn.signIn(); diff --git a/lib/features/auth/cubit/ghost_sign_in_cubit.dart b/lib/features/auth/cubit/ghost_sign_in_cubit.dart new file mode 100644 index 0000000..ac54b72 --- /dev/null +++ b/lib/features/auth/cubit/ghost_sign_in_cubit.dart @@ -0,0 +1,36 @@ +import 'package:bloc/bloc.dart'; +import 'package:fluttercon/common/data/models/models.dart'; +import 'package:fluttercon/common/repository/auth_repository.dart'; +import 'package:fluttercon/common/repository/hive_repository.dart'; +import 'package:fluttercon/features/auth/cubit/social_auth_sign_in_cubit.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'ghost_sign_in_state.dart'; +part 'ghost_sign_in_cubit.freezed.dart'; + +class GhostSignInCubit extends Cubit { + GhostSignInCubit({ + required AuthRepository authRepository, + required HiveRepository hiveRepository, + }) : super(const GhostSignInState.initial()) { + _authRepository = authRepository; + _hiveRepository = hiveRepository; + } + + late AuthRepository _authRepository; + late HiveRepository _hiveRepository; + + Future signIn() async { + emit(const GhostSignInState.loading()); + try { + final authResult = await _authRepository.ghostSignIn(); + _hiveRepository + ..persistToken(authResult.token) + ..persistUser(authResult.user); + + emit(const GhostSignInState.loaded()); + } catch (e) { + emit(GhostSignInState.error(e.toString())); + } + } +} diff --git a/lib/features/auth/cubit/ghost_sign_in_state.dart b/lib/features/auth/cubit/ghost_sign_in_state.dart new file mode 100644 index 0000000..e29649f --- /dev/null +++ b/lib/features/auth/cubit/ghost_sign_in_state.dart @@ -0,0 +1,9 @@ +part of 'ghost_sign_in_cubit.dart'; + +@freezed +class GhostSignInState with _$GhostSignInState { + const factory GhostSignInState.initial() = _Initial; + const factory GhostSignInState.loading() = _Loading; + const factory GhostSignInState.loaded() = _Loaded; + const factory GhostSignInState.error(String message) = _Error; +} diff --git a/lib/features/auth/ui/sign_in.dart b/lib/features/auth/ui/sign_in.dart index 01852ff..8834226 100644 --- a/lib/features/auth/ui/sign_in.dart +++ b/lib/features/auth/ui/sign_in.dart @@ -2,9 +2,12 @@ import 'package:auth_buttons/auth_buttons.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fluttercon/common/repository/firebase_repository.dart'; import 'package:fluttercon/common/utils/constants/app_assets.dart'; import 'package:fluttercon/common/utils/misc.dart'; import 'package:fluttercon/common/utils/router.dart'; +import 'package:fluttercon/core/di/injectable.dart'; +import 'package:fluttercon/features/auth/cubit/ghost_sign_in_cubit.dart'; import 'package:fluttercon/features/auth/cubit/google_sign_in_cubit.dart'; import 'package:fluttercon/features/auth/cubit/social_auth_sign_in_cubit.dart'; import 'package:go_router/go_router.dart'; @@ -53,7 +56,38 @@ class SignInScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ const Spacer(), - const Image(image: AssetImage(AppAssets.flutterConKeLogo)), + GestureDetector( + onLongPress: () { + final config = getIt().getConfig(); + if (config.isInReview && + config.appVersion == Misc.getAppVersion()) { + context.read().signIn(); + } + }, + child: BlocConsumer( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + loaded: () => GoRouter.of(context) + .goNamed(FlutterConRouter.decisionRoute), + error: (message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: AutoSizeText(message)), + ); + }, + ); + }, + builder: (context, state) { + return state.maybeWhen( + loading: () => Center( + child: const CircularProgressIndicator()), + orElse: () => const Image( + image: AssetImage(AppAssets.flutterConKeLogo), + ), + ); + }, + ), + ), const SizedBox(height: 64), BlocBuilder( builder: (context, state) { diff --git a/lib/main_production.dart b/lib/main_production.dart index 4f588e4..f306a8f 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -14,8 +14,8 @@ Future main() async { }); FlutterConConfig( values: FlutterConValues( - baseDomain: 'api.droidcon.co.ke', - urlScheme: 'https', + baseDomain: '127.0.0.1:8000', + urlScheme: 'http', hiveBox: 'fluttercon-prod', eventSlug: 'droidconke-2022-281', ), diff --git a/pubspec.yaml b/pubspec.yaml index bf633b6..0651875 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fluttercon description: "A new Flutter project." publish_to: 'none' -version: 1.15.02+11502 +version: 1.16.00+11600 environment: sdk: ">=3.5.0 <4.0.0" From 9ac4117b58dcfdf3ed870277e5230c4331935371 Mon Sep 17 00:00:00 2001 From: Miller Adulu Date: Fri, 30 Aug 2024 13:34:14 +0300 Subject: [PATCH 4/5] Update base URL --- lib/main_production.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main_production.dart b/lib/main_production.dart index f306a8f..e144f52 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -14,8 +14,8 @@ Future main() async { }); FlutterConConfig( values: FlutterConValues( - baseDomain: '127.0.0.1:8000', - urlScheme: 'http', + baseDomain: 'api.droidcon.co.ke', + urlScheme: 'https', hiveBox: 'fluttercon-prod', eventSlug: 'droidconke-2022-281', ), From a67a98ab2fb506702d9a551b4dd5f09594c9dcc1 Mon Sep 17 00:00:00 2001 From: Miller Adulu Date: Fri, 30 Aug 2024 13:34:46 +0300 Subject: [PATCH 5/5] Fix formatting --- lib/bootstrap.dart | 2 -- lib/common/repository/firebase_repository.dart | 1 - lib/features/auth/cubit/ghost_sign_in_cubit.dart | 2 -- lib/features/auth/ui/sign_in.dart | 5 +++-- lib/main_production.dart | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 3806039..7fe3ac2 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -9,7 +9,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fluttercon/common/repository/db_repository.dart'; import 'package:fluttercon/common/repository/firebase_repository.dart'; import 'package:fluttercon/common/repository/hive_repository.dart'; -import 'package:fluttercon/common/utils/misc.dart'; import 'package:fluttercon/common/utils/notification_service.dart'; import 'package:fluttercon/core/di/injectable.dart'; import 'package:fluttercon/features/about/cubit/fetch_individual_organisers_cubit.dart'; @@ -25,7 +24,6 @@ import 'package:fluttercon/features/home/cubit/home_cubits.dart'; import 'package:fluttercon/features/sessions/cubit/bookmark_session_cubit.dart'; import 'package:fluttercon/features/sessions/cubit/fetch_grouped_sessions_cubit.dart'; import 'package:fluttercon/firebase_options.dart'; -import 'package:logger/logger.dart'; class AppBlocObserver extends BlocObserver { const AppBlocObserver(); diff --git a/lib/common/repository/firebase_repository.dart b/lib/common/repository/firebase_repository.dart index b093a46..08f585d 100644 --- a/lib/common/repository/firebase_repository.dart +++ b/lib/common/repository/firebase_repository.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:fluttercon/common/data/models/remote_config.dart'; import 'package:injectable/injectable.dart'; -import 'package:logger/logger.dart'; @singleton class FirebaseRepository { diff --git a/lib/features/auth/cubit/ghost_sign_in_cubit.dart b/lib/features/auth/cubit/ghost_sign_in_cubit.dart index ac54b72..56deb75 100644 --- a/lib/features/auth/cubit/ghost_sign_in_cubit.dart +++ b/lib/features/auth/cubit/ghost_sign_in_cubit.dart @@ -1,8 +1,6 @@ import 'package:bloc/bloc.dart'; -import 'package:fluttercon/common/data/models/models.dart'; import 'package:fluttercon/common/repository/auth_repository.dart'; import 'package:fluttercon/common/repository/hive_repository.dart'; -import 'package:fluttercon/features/auth/cubit/social_auth_sign_in_cubit.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'ghost_sign_in_state.dart'; diff --git a/lib/features/auth/ui/sign_in.dart b/lib/features/auth/ui/sign_in.dart index 8834226..e54e895 100644 --- a/lib/features/auth/ui/sign_in.dart +++ b/lib/features/auth/ui/sign_in.dart @@ -79,8 +79,9 @@ class SignInScreen extends StatelessWidget { }, builder: (context, state) { return state.maybeWhen( - loading: () => Center( - child: const CircularProgressIndicator()), + loading: () => const Center( + child: CircularProgressIndicator(), + ), orElse: () => const Image( image: AssetImage(AppAssets.flutterConKeLogo), ), diff --git a/lib/main_production.dart b/lib/main_production.dart index e144f52..4f588e4 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -14,7 +14,7 @@ Future main() async { }); FlutterConConfig( values: FlutterConValues( - baseDomain: 'api.droidcon.co.ke', + baseDomain: 'api.droidcon.co.ke', urlScheme: 'https', hiveBox: 'fluttercon-prod', eventSlug: 'droidconke-2022-281',