diff --git a/docs/README.md b/docs/README.md index 17e64f29..70ff8881 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,15 +23,17 @@ command to run when you need to build or re-build these files: _The watch command keeps re-building generated files. It is generally not recommended to use._ ```bash - flutter pub run build_runner watch --delete-conflicting-outputs + flutter pub run build_runner watch -d ``` _If you want to build generated files once run this command, it's preferred._ ```bash - flutter pub run build_runner build --delete-conflicting-outputs + flutter pub run build_runner build -d ``` +Note: The argument `-d` is shorthand for `--delete-conflicting-outputs` introduced in build_runner 2.3.0 + ## Imutable app settings We use an environment file called `.env` to define secrets used in the application. ` @@ -70,14 +72,15 @@ To update the icons font with a new svg, go to [FlutterIcons](https://www.flutte To generate the OpenSource licenses dart file via [flutter_oss_licenses](https://pub.dev/packages/flutter_oss_licenses), run the command `flutter pub run flutter_oss_licenses:generate.dart lib/presentation/licenses/oss_licenses.dart` from the project root. ->Note: Run `flutter format .` once [oss_licenses.dart](../lib/presentation/licenses/oss_licenses.dart) is created. +> Note: Run `flutter format .` once [oss_licenses.dart](../lib/presentation/licenses/oss_licenses.dart) is created. ## Running tests and generating Test Report -- Step 1: Just for the 1st time, to make sure that all files are included, run ```./.github/scripts/coverage_gen.sh``` to generate a report for the whole project. -- Step 2: Run ```flutter test --coverage```. This will generate the report locally. -- Step 3: Run ```genhtml coverage/lcov.info -o coverage```. -- Step 4: Run ``` firefox coverage/index.html``` (or run ```coverage/index.html``` to any of your web browser). +- Step 1: Just for the 1st time, to make sure that all files are included, run `./.github/scripts/coverage_gen.sh` to generate a report for the whole project. +- Step 2: Run `flutter test --coverage`. This will generate the report locally. +- Step 3: Run `genhtml coverage/lcov.info -o coverage`. +- Step 4: Run ` firefox coverage/index.html` (or run `coverage/index.html` to any of your web browser). + #### You should see the coverage of your tests line-wise in Codecov. ## Getting Started with Flutter @@ -94,6 +97,7 @@ mobile development, and a full API reference. ## Pre-push checks We have set up Git hooks to prevent extra whitespace and other possible mistakes before pushing the code to your branch. Run the below command in the project root directory to copy the pre-push hooks to your local `.git` directory. + ```bash cp ./hooks/* ./.git/hooks/ ``` diff --git a/ios/Podfile.lock b/ios/Podfile.lock index aa350ee4..bfb22058 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,26 +1,14 @@ PODS: - country_codes (0.0.1): - Flutter - - Firebase/Analytics (9.6.0): - - Firebase/Core - Firebase/Auth (9.6.0): - Firebase/CoreOnly - FirebaseAuth (~> 9.6.0) - - Firebase/Core (9.6.0): - - Firebase/CoreOnly - - FirebaseAnalytics (~> 9.6.0) - Firebase/CoreOnly (9.6.0): - FirebaseCore (= 9.6.0) - Firebase/Crashlytics (9.6.0): - Firebase/CoreOnly - FirebaseCrashlytics (~> 9.6.0) - - Firebase/RemoteConfig (9.6.0): - - Firebase/CoreOnly - - FirebaseRemoteConfig (~> 9.6.0) - - firebase_analytics (9.3.8): - - Firebase/Analytics (= 9.6.0) - - firebase_core - - Flutter - firebase_auth (3.11.2): - Firebase/Auth (= 9.6.0) - firebase_core @@ -32,30 +20,6 @@ PODS: - Firebase/Crashlytics (= 9.6.0) - firebase_core - Flutter - - firebase_remote_config (2.0.20): - - Firebase/RemoteConfig (= 9.6.0) - - firebase_core - - Flutter - - FirebaseABTesting (9.6.0): - - FirebaseCore (~> 9.0) - - FirebaseAnalytics (9.6.0): - - FirebaseAnalytics/AdIdSupport (= 9.6.0) - - FirebaseCore (~> 9.0) - - FirebaseInstallations (~> 9.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (9.6.0): - - FirebaseCore (~> 9.0) - - FirebaseInstallations (~> 9.0) - - GoogleAppMeasurement (= 9.6.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (< 2.30910.0, >= 2.30908.0) - FirebaseAuth (9.6.0): - FirebaseCore (~> 9.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) @@ -85,36 +49,10 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/UserDefaults (~> 7.7) - PromisesObjC (~> 2.1) - - FirebaseRemoteConfig (9.6.0): - - FirebaseABTesting (~> 9.0) - - FirebaseCore (~> 9.0) - - FirebaseInstallations (~> 9.0) - - GoogleUtilities/Environment (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" - Flutter (1.0.0) - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) - - GoogleAppMeasurement (9.6.0): - - GoogleAppMeasurement/AdIdSupport (= 9.6.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (9.6.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 9.6.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (9.6.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (< 2.30910.0, >= 2.30908.0) - GoogleDataTransport (9.2.0): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) @@ -127,8 +65,6 @@ PODS: - PromisesObjC (< 3.0, >= 1.2) - GoogleUtilities/Logger (7.8.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.8.0): - - GoogleUtilities/Logger - GoogleUtilities/Network (7.8.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" @@ -173,11 +109,9 @@ PODS: DEPENDENCIES: - country_codes (from `.symlinks/plugins/country_codes/ios`) - - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) - - firebase_remote_config (from `.symlinks/plugins/firebase_remote_config/ios`) - Flutter (from `Flutter`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) @@ -193,17 +127,13 @@ DEPENDENCIES: SPEC REPOS: trunk: - Firebase - - FirebaseABTesting - - FirebaseAnalytics - FirebaseAuth - FirebaseCore - FirebaseCoreDiagnostics - FirebaseCoreInternal - FirebaseCrashlytics - FirebaseInstallations - - FirebaseRemoteConfig - FMDB - - GoogleAppMeasurement - GoogleDataTransport - GoogleUtilities - GTMSessionFetcher @@ -215,16 +145,12 @@ SPEC REPOS: EXTERNAL SOURCES: country_codes: :path: ".symlinks/plugins/country_codes/ios" - firebase_analytics: - :path: ".symlinks/plugins/firebase_analytics/ios" firebase_auth: :path: ".symlinks/plugins/firebase_auth/ios" firebase_core: :path: ".symlinks/plugins/firebase_core/ios" firebase_crashlytics: :path: ".symlinks/plugins/firebase_crashlytics/ios" - firebase_remote_config: - :path: ".symlinks/plugins/firebase_remote_config/ios" Flutter: :path: Flutter image_cropper: @@ -251,23 +177,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: country_codes: b0900f46ad686281d5dab438e354e44ad10f5941 Firebase: 5ae8b7cf8efce559a653aef0ad95bab3f427c351 - firebase_analytics: 9937a91df25cce5b0ee19eaf65277ff8bf0f88e5 firebase_auth: 07a4db69cfa447ac42cb7faa560fc100708b707c firebase_core: 7c28ecc1e5dd74e03829ac3e9ff5ba3314e737a9 firebase_crashlytics: 520a59314eaaadb34f9be4c2a285d99cfa88ebdb - firebase_remote_config: df81462997f4d1582113c543e5a39ef832cebb92 - FirebaseABTesting: 61826730ce9eee8781ba99a2b3420e9bce148dc9 - FirebaseAnalytics: 89ad762c6c3852a685794174757e2c60a36b6a82 FirebaseAuth: e4a5d3c36e778e41141b91cc861103a441d80bcc FirebaseCore: 2082fffcd855f95f883c0a1641133eb9bbe76d40 FirebaseCoreDiagnostics: 99a495094b10a57eeb3ae8efa1665700ad0bdaa6 FirebaseCoreInternal: bca76517fe1ed381e989f5e7d8abb0da8d85bed3 FirebaseCrashlytics: 3210572ddb77801e5a0bd9d7bc890769f2066a0c FirebaseInstallations: 0a115432c4e223c5ab20b0dbbe4cbefa793a0e8e - FirebaseRemoteConfig: ee09d77a7d7c7e31da6a0d1cf956cd611c85609c Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - GoogleAppMeasurement: 6de2b1a69e4326eb82ee05d138f6a5cb7311bcb1 GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7 GTMSessionFetcher: ffbb25ec00ebcb5201adab0a56d808f6f1902d9f diff --git a/lib/application/auth/auth_bloc.dart b/lib/application/auth/auth_bloc.dart index 203da2ec..2230bfe1 100644 --- a/lib/application/auth/auth_bloc.dart +++ b/lib/application/auth/auth_bloc.dart @@ -79,9 +79,8 @@ class AuthBloc extends Bloc { Emitter emit, _SignedOut event, ) async { - emit(const AuthState.signingOut()); await _authRepository.signOut(); - emit(const AuthState.unAuthenticated()); + emit(const AuthState.unauthenticated()); } FutureOr _mapAuthCheckRequestToState( @@ -92,7 +91,7 @@ class AuthBloc extends Bloc { emit( userOption.fold( - () => const AuthState.unAuthenticated(), + () => const AuthState.unauthenticated(), (user) => AuthState.authenticated(user), ), ); diff --git a/lib/application/auth/auth_state.dart b/lib/application/auth/auth_state.dart index 3c41459f..bc934d12 100644 --- a/lib/application/auth/auth_state.dart +++ b/lib/application/auth/auth_state.dart @@ -42,7 +42,5 @@ class AuthState with _$AuthState { /// Splash Auth states const factory AuthState.authenticated(User user) = _Authenticated; - const factory AuthState.signingOut() = _SigningOut; - - const factory AuthState.unAuthenticated() = _UnAuthenticated; + const factory AuthState.unauthenticated() = _UnAuthenticated; } diff --git a/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart b/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart index ae0e3159..037c73e9 100644 --- a/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart +++ b/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart @@ -49,6 +49,7 @@ class CrowdActionDetailsPageState extends State { participationBloc = getIt(); participate = _signUpModal; id = widget.crowdActionId ?? widget.crowdAction!.id; + crowdAction = widget.crowdAction; } @override diff --git a/lib/presentation/crowdaction/crowdaction_details/widgets/participation_count_text.dart b/lib/presentation/crowdaction/crowdaction_details/widgets/participation_count_text.dart index 64650db6..39b680af 100644 --- a/lib/presentation/crowdaction/crowdaction_details/widgets/participation_count_text.dart +++ b/lib/presentation/crowdaction/crowdaction_details/widgets/participation_count_text.dart @@ -1,6 +1,5 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collaction_app/application/crowdaction/crowdaction_details/crowdaction_details_bloc.dart'; -import 'package:collaction_app/presentation/routes/app_routes.gr.dart'; +import 'package:collaction_app/presentation/crowdaction/crowdaction_participants/crowdaction_participants_screen.dart'; import 'package:collaction_app/presentation/shared_widgets/shimmers/title_shimmer_line.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,9 +19,11 @@ class ParticipationCountText extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => context.router.push( - CrowdActionParticipantsRoute( - crowdActionId: crowdAction!.id, + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CrowdActionParticipantsPage( + crowdActionId: crowdAction!.id, + ), ), ), child: BlocProvider.value( diff --git a/lib/presentation/crowdaction/crowdaction_home/crowdaction_home_screen.dart b/lib/presentation/crowdaction/crowdaction_home/crowdaction_home_screen.dart index 43e9b633..ac0e6ef6 100644 --- a/lib/presentation/crowdaction/crowdaction_home/crowdaction_home_screen.dart +++ b/lib/presentation/crowdaction/crowdaction_home/crowdaction_home_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../application/crowdaction/spotlight/spotlight_bloc.dart'; +import '../../../infrastructure/core/injection.dart'; import '../../home/widgets/current_upcoming_layout.dart'; import '../../themes/constants.dart'; import 'widgets/in_spotlight_header.dart'; @@ -12,24 +13,30 @@ class CrowdActionHomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: RefreshIndicator( - onRefresh: () async => BlocProvider.of(context) - .add(const SpotlightEvent.getSpotLightCrowdActions()), - color: kAccentColor, - child: SingleChildScrollView( - child: SizedBox( - width: double.infinity, - child: Column( - children: const [ - InSpotLightHeader(), - CurrentAndUpcomingLayout(), - SizedBox(height: 20), - ShareCollActionCard(), - SizedBox(height: 20), - CurrentAndUpcomingLayout(isCurrent: false) - ], + return BlocProvider( + create: (context) => getIt() + ..add(const SpotlightEvent.getSpotLightCrowdActions()), + child: Builder( + builder: (context) => Scaffold( + body: SafeArea( + child: RefreshIndicator( + onRefresh: () async => BlocProvider.of(context) + .add(const SpotlightEvent.getSpotLightCrowdActions()), + color: kAccentColor, + child: SingleChildScrollView( + child: SizedBox( + width: double.infinity, + child: Column( + children: const [ + InSpotLightHeader(), + CurrentAndUpcomingLayout(), + SizedBox(height: 20), + ShareCollActionCard(), + SizedBox(height: 20), + CurrentAndUpcomingLayout(isCurrent: false) + ], + ), + ), ), ), ), diff --git a/lib/presentation/crowdaction/crowdaction_home/widgets/in_spotlight_header.dart b/lib/presentation/crowdaction/crowdaction_home/widgets/in_spotlight_header.dart index 26cecdb6..d9e360bc 100644 --- a/lib/presentation/crowdaction/crowdaction_home/widgets/in_spotlight_header.dart +++ b/lib/presentation/crowdaction/crowdaction_home/widgets/in_spotlight_header.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; -import '../../../../infrastructure/core/injection.dart'; import '../../../shared_widgets/content_placeholder.dart'; import '../../../shared_widgets/crowdaction_card.dart'; import '../../../themes/constants.dart'; @@ -34,9 +33,8 @@ class _InSpotLightHeaderState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt() - ..add(const SpotlightEvent.getSpotLightCrowdActions()), + return BlocProvider.value( + value: BlocProvider.of(context), child: LayoutBuilder( builder: (context, constraints) { return Container( diff --git a/lib/presentation/home/widgets/current_upcoming_layout.dart b/lib/presentation/home/widgets/current_upcoming_layout.dart index fcd6184c..fb61555d 100644 --- a/lib/presentation/home/widgets/current_upcoming_layout.dart +++ b/lib/presentation/home/widgets/current_upcoming_layout.dart @@ -1,5 +1,4 @@ import 'package:auto_route/auto_route.dart'; -import 'package:collaction_app/infrastructure/core/injection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -25,9 +24,8 @@ class CurrentAndUpcomingLayout extends StatefulWidget { class _CurrentAndUpcomingLayoutState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt() - ..add(const SpotlightEvent.getSpotLightCrowdActions()), + return BlocProvider.value( + value: BlocProvider.of(context), child: BlocBuilder( builder: (ctx, state) => LayoutBuilder( builder: (context, constraints) { diff --git a/lib/presentation/profile/profile_screen.dart b/lib/presentation/profile/profile_screen.dart index 0c442c0b..b60e007d 100644 --- a/lib/presentation/profile/profile_screen.dart +++ b/lib/presentation/profile/profile_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:share_plus/share_plus.dart'; +import '../../application/auth/auth_bloc.dart'; import '../../application/user/profile/profile_bloc.dart'; import '../core/collaction_icons.dart'; import '../routes/app_routes.gr.dart'; @@ -35,366 +36,372 @@ class _UserProfilePageState extends State { return BlocProvider.value( value: BlocProvider.of(context), - child: BlocBuilder( - builder: (context, state) { - bioController.value = - TextEditingValue(text: state.userProfile?.profile.bio ?? ''); + child: BlocListener( + listener: (context, state) { + BlocProvider.of(context).add(GetUserProfile()); + }, + child: BlocBuilder( + builder: (context, state) { + bioController.value = + TextEditingValue(text: state.userProfile?.profile.bio ?? ''); - final scaffold = Scaffold( - extendBodyBehindAppBar: true, - backgroundColor: kAlmostTransparent, - floatingActionButtonLocation: - FloatingActionButtonLocation.miniEndTop, - floatingActionButton: Column( - children: [ - const SizedBox(height: 10), - ElevatedButton( - onPressed: share, - style: ElevatedButton.styleFrom( - shape: const CircleBorder(), - backgroundColor: kEnabledButtonColor, - ).merge( - ButtonStyle( - elevation: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.pressed)) { - return 5; - } - return 4; - }, + final scaffold = Scaffold( + extendBodyBehindAppBar: true, + backgroundColor: kAlmostTransparent, + floatingActionButtonLocation: + FloatingActionButtonLocation.miniEndTop, + floatingActionButton: Column( + children: [ + const SizedBox(height: 10), + ElevatedButton( + onPressed: share, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + backgroundColor: kEnabledButtonColor, + ).merge( + ButtonStyle( + elevation: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.pressed)) { + return 5; + } + return 4; + }, + ), ), ), + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 8.0, horizontal: 4), + child: Icon(CollactionIcons.share), + ), ), - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 4), - child: Icon(CollactionIcons.share), - ), - ), - const SizedBox(height: 10), - ElevatedButton( - onPressed: () => - context.router.push(const SettingsRoute()).then((_) { - context.read().add(GetUserProfile()); - }), - style: ElevatedButton.styleFrom( - foregroundColor: kPrimaryColor0, - backgroundColor: Colors.white, - shape: const CircleBorder(), - tapTargetSize: MaterialTapTargetSize.padded, - ).merge( - ButtonStyle( - elevation: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.pressed)) { - return 5; - } - return 4; - }, + const SizedBox(height: 10), + ElevatedButton( + onPressed: () => context.router.push(const SettingsRoute()), + style: ElevatedButton.styleFrom( + foregroundColor: kPrimaryColor0, + backgroundColor: Colors.white, + shape: const CircleBorder(), + tapTargetSize: MaterialTapTargetSize.padded, + ).merge( + ButtonStyle( + elevation: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.pressed)) { + return 5; + } + return 4; + }, + ), ), ), - ), - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 4), - child: Icon( - CollactionIcons.settings, - color: kPrimaryColor300, + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 8.0, horizontal: 4), + child: Icon( + CollactionIcons.settings, + color: kPrimaryColor300, + ), ), ), - ), - ], - ), - body: SafeArea( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 30, - horizontal: 20, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: ProfilePicture( - image: _image, - profileImage: state - .userProfile?.profile.avatar != - null - ? '${dotenv.get('BASE_STATIC_ENDPOINT_URL')}/${state.userProfile?.profile.avatar}' - : null, - maxRadius: 50, - ), - ), - if (state.isEditing == true) ...[ - Positioned( - bottom: 0, - right: 0, - child: FloatingActionButton( - onPressed: () { - showModalBottomSheet( - context: context, - builder: (context) => PhotoSelector( - onSelected: (image) { - setState(() => _image = image); - context.router.pop("dialog"); - }, - ), - ); - }, - backgroundColor: kAccentColor, - mini: true, - child: const Icon(Icons.add), + ], + ), + body: SafeArea( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 30, + horizontal: 20, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: ProfilePicture( + image: _image, + profileImage: state.userProfile?.profile + .avatar != + null + ? '${dotenv.get('BASE_STATIC_ENDPOINT_URL')}/${state.userProfile?.profile.avatar}' + : null, + maxRadius: 50, ), ), - ] - ], - ), - ), - const SizedBox(height: 10), - Center( - child: Text( - state.userProfile?.profile.firstName ?? 'You', - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 22, + if (state.isEditing == true) ...[ + Positioned( + bottom: 0, + right: 0, + child: FloatingActionButton( + onPressed: () { + showModalBottomSheet( + context: context, + builder: (context) => PhotoSelector( + onSelected: (image) { + setState(() => _image = image); + context.router.pop("dialog"); + }, + ), + ); + }, + backgroundColor: kAccentColor, + mini: true, + child: const Icon(Icons.add), + ), + ), + ] + ], ), ), - ), - if (state.userProfile != null) ...[ - const SizedBox(height: 40), - const Text( - 'About me', - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 11, - color: Color(0xFF666666), + const SizedBox(height: 10), + Center( + child: Text( + state.userProfile?.profile.firstName ?? 'You', + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 22, + ), ), - textAlign: TextAlign.left, ), - const SizedBox(height: 10), - if (state.isEditing == true) ...[ - Row( - children: [ - Expanded( - child: TextFormField( - controller: bioController, - cursorColor: kAccentColor, - maxLength: 150, - decoration: InputDecoration( - counterText: '', - fillColor: Colors.white, - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), - borderSide: BorderSide.none, - ), - enabledBorder: OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), - borderSide: BorderSide.none, - ), - focusedBorder: OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), - borderSide: BorderSide.none, + if (state.userProfile != null) ...[ + const SizedBox(height: 40), + const Text( + 'About me', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 11, + color: Color(0xFF666666), + ), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + if (state.isEditing == true) ...[ + Row( + children: [ + Expanded( + child: TextFormField( + controller: bioController, + cursorColor: kAccentColor, + maxLength: 150, + decoration: InputDecoration( + counterText: '', + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: + BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: + BorderRadius.circular(10), + borderSide: BorderSide.none, + ), ), + minLines: 3, + maxLines: 5, + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith( + fontWeight: FontWeight.w300, + fontSize: 17, + color: kPrimaryColor400, + ), ), - minLines: 3, - maxLines: 5, + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + const SizedBox(width: 16), + Text( + 'Maximum 150 characters', style: Theme.of(context) .textTheme .bodyText2! .copyWith( - fontWeight: FontWeight.w300, - fontSize: 17, - color: kPrimaryColor400, + fontSize: 12, + fontWeight: FontWeight.w400, + color: kPrimaryColor300, ), ), - ), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - const SizedBox(width: 16), - Text( - 'Maximum 150 characters', - style: Theme.of(context) - .textTheme - .bodyText2! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.w400, - color: kPrimaryColor300, - ), - ), - ], - ) - ] else ...[ - Text( - state.userProfile?.profile.bio ?? '', - style: Theme.of(context) - .textTheme - .bodyText2! - .copyWith( - fontWeight: FontWeight.w300, - fontSize: 17, - color: kPrimaryColor400, - ), - ), - ], - const SizedBox(height: 40), - RichText( - text: TextSpan( - text: 'Joined ', - style: const TextStyle( - color: kPrimaryColor200, - fontWeight: FontWeight.w700, - fontSize: 11, - ), - children: [ - TextSpan( - text: state - .userProfile?.user.formattedJoinDate, - style: const TextStyle( - color: kPrimaryColor300, - fontWeight: FontWeight.w700, - fontSize: 11, - ), - ) - ], - ), - ), - const SizedBox(height: 40), - TextButton( - key: const Key('save_edit_button'), - style: ButtonStyle( - overlayColor: MaterialStateColor.resolveWith( - (states) => state.isEditing == true - ? Colors.white.withOpacity(0.1) - : kAccentColor.withOpacity(0.1), - ), - backgroundColor: state.isEditing == true - ? MaterialStateProperty.all(kAccentColor) - : null, - minimumSize: MaterialStateProperty.all( - const Size(double.infinity * 0.75, 52), + ], + ) + ] else ...[ + Text( + state.userProfile?.profile.bio ?? '', + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith( + fontWeight: FontWeight.w300, + fontSize: 17, + color: kPrimaryColor400, + ), ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(200), - side: const BorderSide( - color: kAccentColor, - ), + ], + const SizedBox(height: 40), + RichText( + text: TextSpan( + text: 'Joined ', + style: const TextStyle( + color: kPrimaryColor200, + fontWeight: FontWeight.w700, + fontSize: 11, ), + children: [ + TextSpan( + text: state + .userProfile?.user.formattedJoinDate, + style: const TextStyle( + color: kPrimaryColor300, + fontWeight: FontWeight.w700, + fontSize: 11, + ), + ) + ], ), ), - onPressed: () { - if (state.isEditing == true) { - /// TODO: Implement save profile image - BlocProvider.of(context).add( - SaveProfile( - bio: bioController.text, - image: _image, - ), - ); - } else { - context - .read() - .add(EditProfile()); - } - }, - child: Text( - state.isEditing == true - ? 'Save changes' - : 'Edit profile', - style: TextStyle( - color: state.isEditing == true - ? Colors.white - : kAccentColor, - fontWeight: FontWeight.w700, - ), - ), - ), - if (state.isEditing == true) ...[ - const SizedBox(height: 10), + const SizedBox(height: 40), TextButton( - key: const Key('cancel_edit_button'), + key: const Key('save_edit_button'), style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith( + (states) => state.isEditing == true + ? Colors.white.withOpacity(0.1) + : kAccentColor.withOpacity(0.1), + ), + backgroundColor: state.isEditing == true + ? MaterialStateProperty.all(kAccentColor) + : null, minimumSize: MaterialStateProperty.all( const Size(double.infinity * 0.75, 52), ), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(200), + side: const BorderSide( + color: kAccentColor, + ), ), ), ), onPressed: () { - BlocProvider.of(context) - .add(CancelEditProfile()); - - _image = null; - bioController.value = TextEditingValue( - text: state.userProfile?.profile.bio ?? '', - ); + if (state.isEditing == true) { + /// TODO: Implement save profile image + BlocProvider.of(context).add( + SaveProfile( + bio: bioController.text, + image: _image, + ), + ); + } else { + context + .read() + .add(EditProfile()); + } }, - child: const Text( - 'Cancel', + child: Text( + state.isEditing == true + ? 'Save changes' + : 'Edit profile', style: TextStyle( - color: kAccentColor, + color: state.isEditing == true + ? Colors.white + : kAccentColor, fontWeight: FontWeight.w700, ), ), ), + if (state.isEditing == true) ...[ + const SizedBox(height: 10), + TextButton( + key: const Key('cancel_edit_button'), + style: ButtonStyle( + minimumSize: MaterialStateProperty.all( + const Size(double.infinity * 0.75, 52), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(200), + ), + ), + ), + onPressed: () { + BlocProvider.of(context) + .add(CancelEditProfile()); + + _image = null; + bioController.value = TextEditingValue( + text: + state.userProfile?.profile.bio ?? '', + ); + }, + child: const Text( + 'Cancel', + style: TextStyle( + color: kAccentColor, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ] else ...[ + // TODO only for MVP (remove later) + const SizedBox(height: 40), + PillButton( + text: 'Create account or sign in', + onTap: () => context.router + .push(const AuthRoute()) + .then((_) { + // Refresh profile + context + .read() + .add(GetUserProfile()); + }), + ), ], - ] else ...[ - // TODO only for MVP (remove later) - const SizedBox(height: 40), - PillButton( - text: 'Create account or sign in', - onTap: () => context.router - .push(const AuthRoute()) - .then((_) { - // Refresh profile - context - .read() - .add(GetUserProfile()); - }), - ), + const SizedBox(height: 20), ], - const SizedBox(height: 20), - ], + ), ), - ), - UserProfileTab(user: state.userProfile?.user), - ], + UserProfileTab(user: state.userProfile?.user), + ], + ), ), ), - ), - ); + ); - return BlocListener( - listener: (context, state) { - if (state.wasProfilePictureUpdated == true) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Profile picture will be reviewed!"), - duration: Duration(seconds: 5), - ), - ); - } - }, - child: scaffold, - ); - }, + return BlocListener( + listener: (context, state) { + if (state.wasProfilePictureUpdated == true) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Profile picture will be reviewed!"), + duration: Duration(seconds: 5), + ), + ); + } + }, + child: scaffold, + ); + }, + ), ), ); } diff --git a/lib/presentation/shared_widgets/content_placeholder.dart b/lib/presentation/shared_widgets/content_placeholder.dart index 0cf2d4d4..fd680513 100644 --- a/lib/presentation/shared_widgets/content_placeholder.dart +++ b/lib/presentation/shared_widgets/content_placeholder.dart @@ -24,14 +24,13 @@ class ContentPlaceholder extends StatelessWidget { final signedIn = state.maybeWhen( orElse: () => false, authenticated: (_) => true, - unAuthenticated: () => false, + unauthenticated: () => false, ); return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ - // TODO use giphy.com API or custom service for more variety and smaller builds Image.asset( 'assets/images/content_placeholder.gif', width: 125, diff --git a/pubspec.lock b/pubspec.lock index cc0c6e25..3b22bbd0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -140,21 +140,21 @@ packages: name: cached_network_image url: "https://pub.dartlang.org" source: hosted - version: "3.2.1" + version: "3.2.2" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "2.0.0" cached_network_image_web: dependency: transitive description: name: cached_network_image_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" characters: dependency: transitive description: @@ -253,13 +253,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" dart_jsonwebtoken: dependency: transitive description: @@ -344,27 +337,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.4" - firebase_analytics: - dependency: "direct main" - description: - name: firebase_analytics - url: "https://pub.dartlang.org" - source: hosted - version: "9.3.8" - firebase_analytics_platform_interface: - dependency: transitive - description: - name: firebase_analytics_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "3.3.7" - firebase_analytics_web: - dependency: transitive - description: - name: firebase_analytics_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.2+7" firebase_auth: dependency: "direct main" description: @@ -428,27 +400,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.3.0" - firebase_remote_config: - dependency: "direct main" - description: - name: firebase_remote_config - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.20" - firebase_remote_config_platform_interface: - dependency: transitive - description: - name: firebase_remote_config_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.19" - firebase_remote_config_web: - dependency: transitive - description: - name: firebase_remote_config_web - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.8" fixnum: dependency: transitive description: @@ -731,26 +682,19 @@ packages: source: hosted version: "0.1.5" meta: - dependency: "direct main" + dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted version: "1.8.0" mime: - dependency: "direct main" + dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted version: "1.0.2" - mockito: - dependency: "direct dev" - description: - name: mockito - url: "https://pub.dartlang.org" - source: hosted - version: "5.3.2" mocktail: dependency: "direct main" description: @@ -765,13 +709,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" - network_image_mock: - dependency: "direct dev" - description: - name: network_image_mock - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" node_preamble: dependency: transitive description: @@ -799,42 +736,14 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.4.3+1" - package_info_plus_linux: - dependency: transitive - description: - name: package_info_plus_linux - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_info_plus_macos: - dependency: transitive - description: - name: package_info_plus_macos - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "3.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" - package_info_plus_web: - dependency: transitive - description: - name: package_info_plus_web - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - package_info_plus_windows: - dependency: transitive - description: - name: package_info_plus_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" + version: "2.0.0" path: dependency: transitive description: @@ -988,21 +897,7 @@ packages: name: share_plus url: "https://pub.dartlang.org" source: hosted - version: "4.5.3" - share_plus_linux: - dependency: transitive - description: - name: share_plus_linux - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - share_plus_macos: - dependency: transitive - description: - name: share_plus_macos - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" + version: "6.0.0" share_plus_platform_interface: dependency: transitive description: @@ -1010,20 +905,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.1" - share_plus_web: - dependency: transitive - description: - name: share_plus_web - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - share_plus_windows: - dependency: transitive - description: - name: share_plus_windows - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" shared_preferences: dependency: "direct main" description: @@ -1408,5 +1289,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=3.3.0-0" + dart: ">=2.18.2 <3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 516f3258..a327fe01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,67 +18,60 @@ publish_to: "none" version: 1.2.0+1 environment: - sdk: ">=2.18.0 <3.0.0" + sdk: ">=2.18.2 <3.0.0" dependencies: - auto_route: ^5.0.1 - bloc: ^8.0.2 - cached_network_image: ^3.2.0 - country_codes: ^2.1.1 + auto_route: ^5.0.2 + bloc: ^8.1.0 + cached_network_image: ^3.2.2 + country_codes: ^2.2.0 country_icons: ^2.0.2 - cupertino_icons: ^1.0.4 dartz: ^0.10.1 dots_indicator: ^2.0.0 email_validator: ^2.0.1 equatable: ^2.0.3 expandable_page_view: ^1.0.10 - firebase_analytics: ^9.1.0 firebase_auth: ^3.3.6 firebase_core: ^1.12.0 firebase_crashlytics: ^2.5.1 - firebase_remote_config: ^2.0.0 flutter: sdk: flutter - flutter_bloc: ^8.0.1 + flutter_bloc: ^8.1.1 flutter_dotenv: ^5.0.2 - freezed_annotation: ^2.0.3 + freezed_annotation: ^2.2.0 get_it: ^7.2.0 - http: ^0.13.4 - image: ^3.1.1 + http: ^0.13.5 + image: ^3.2.2 image_cropper: ^3.0.0 - image_picker: ^0.8.4+7 + image_picker: ^0.8.6 infinite_scroll_pagination: ^3.2.0 injectable: ^1.5.3 intl: ^0.17.0 - json_annotation: ^4.6.0 - meta: ^1.7.0 - mime: ^1.0.2 + json_annotation: ^4.7.0 mocktail: ^0.3.0 - package_info_plus: ^1.3.0 + package_info_plus: ^3.0.0 phone_number: ^1.0.0 - rive: ^0.9.0 - rxdart: ^0.27.3 - share_plus: ^4.0.10 - shared_preferences: ^2.0.13 + rive: ^0.9.1 + rxdart: ^0.27.5 + share_plus: ^6.0.0 + shared_preferences: ^2.0.15 shimmer: ^2.0.0 - url_launcher: ^6.0.18 - webview_flutter: ^3.0.0 + url_launcher: ^6.1.6 + webview_flutter: ^3.0.4 dev_dependencies: auto_route_generator: ^5.0.2 - bloc_test: ^9.0.2 - build_runner: ^2.1.7 - firebase_auth_mocks: ^0.8.2 + bloc_test: ^9.1.0 + build_runner: ^2.3.0 + firebase_auth_mocks: ^0.8.6 flutter_launcher_icons: ^0.10.0 flutter_oss_licenses: ^2.0.1 flutter_test: sdk: flutter - freezed: ^2.0.3+1 - injectable_generator: ^1.5.2 - json_serializable: ^6.1.3 - lint: ^1.8.2 - mockito: ^5.0.17 - network_image_mock: ^2.0.1 + freezed: ^2.2.0 + injectable_generator: ^1.5.4 + json_serializable: ^6.5.3 + lint: ^1.10.0 flutter_icons: android: true @@ -89,15 +82,14 @@ flutter_icons: remove_alpha_ios: true flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true + assets: - .env - assets/images/ - assets/animations/ - assets/vectors/ + fonts: - family: CollactionIcons fonts: