From 23a217d5661da1890ed5a02b35717283fc2d141b Mon Sep 17 00:00:00 2001 From: Miller Adulu Date: Fri, 30 Aug 2024 15:40:15 +0300 Subject: [PATCH] Ghost sign in (#122) * Update firebase_remote_config * Initialize firebase configuration * Perform a ghost sign in if the app is in review * Update base URL * Fix formatting --- .gitignore | 1 + build.yaml | 6 +++ lib/bootstrap.dart | 18 ++++++++- lib/common/data/models/remote_config.dart | 15 ++++++++ lib/common/repository/auth_repository.dart | 17 +++++++++ .../repository/firebase_repository.dart | 24 ++++++++++++ lib/common/utils/misc.dart | 5 +++ .../auth/cubit/ghost_sign_in_cubit.dart | 34 +++++++++++++++++ .../auth/cubit/ghost_sign_in_state.dart | 9 +++++ lib/features/auth/ui/sign_in.dart | 37 ++++++++++++++++++- pubspec.lock | 32 ++++++++++++++++ pubspec.yaml | 4 +- 12 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 build.yaml create mode 100644 lib/common/data/models/remote_config.dart create mode 100644 lib/common/repository/firebase_repository.dart 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/.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..7fe3ac2 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -7,10 +7,12 @@ 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/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'; @@ -48,10 +50,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 @@ -128,7 +136,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/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/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/common/repository/firebase_repository.dart b/lib/common/repository/firebase_repository.dart new file mode 100644 index 0000000..08f585d --- /dev/null +++ b/lib/common/repository/firebase_repository.dart @@ -0,0 +1,24 @@ +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'; + +@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/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..56deb75 --- /dev/null +++ b/lib/features/auth/cubit/ghost_sign_in_cubit.dart @@ -0,0 +1,34 @@ +import 'package:bloc/bloc.dart'; +import 'package:fluttercon/common/repository/auth_repository.dart'; +import 'package:fluttercon/common/repository/hive_repository.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..e54e895 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,39 @@ 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: () => const Center( + child: CircularProgressIndicator(), + ), + orElse: () => const Image( + image: AssetImage(AppAssets.flutterConKeLogo), + ), + ); + }, + ), + ), const SizedBox(height: 64), BlocBuilder( builder: (context, state) { diff --git a/pubspec.lock b/pubspec.lock index de41f19..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: @@ -425,6 +433,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..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" @@ -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 @@ -20,6 +21,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