diff --git a/assets/images/1.5x/onboarding_branding.png b/assets/images/1.5x/onboarding_branding.png new file mode 100644 index 0000000..1043eba Binary files /dev/null and b/assets/images/1.5x/onboarding_branding.png differ diff --git a/assets/images/1.5x/onboarding_describeApp.png b/assets/images/1.5x/onboarding_describeApp.png new file mode 100644 index 0000000..c5fc1f6 Binary files /dev/null and b/assets/images/1.5x/onboarding_describeApp.png differ diff --git a/assets/images/1.5x/onboarding_favourite.png b/assets/images/1.5x/onboarding_favourite.png new file mode 100644 index 0000000..e56bee9 Binary files /dev/null and b/assets/images/1.5x/onboarding_favourite.png differ diff --git a/assets/images/1.5x/onboarding_gestures.png b/assets/images/1.5x/onboarding_gestures.png new file mode 100644 index 0000000..c9d0770 Binary files /dev/null and b/assets/images/1.5x/onboarding_gestures.png differ diff --git a/assets/images/1.5x/onboarding_logo_app.png b/assets/images/1.5x/onboarding_logo_app.png new file mode 100644 index 0000000..c5dbf31 Binary files /dev/null and b/assets/images/1.5x/onboarding_logo_app.png differ diff --git a/assets/images/2.0x/onboarding_branding.png b/assets/images/2.0x/onboarding_branding.png new file mode 100644 index 0000000..cabf1d5 Binary files /dev/null and b/assets/images/2.0x/onboarding_branding.png differ diff --git a/assets/images/2.0x/onboarding_describeApp.png b/assets/images/2.0x/onboarding_describeApp.png new file mode 100644 index 0000000..a4fa679 Binary files /dev/null and b/assets/images/2.0x/onboarding_describeApp.png differ diff --git a/assets/images/2.0x/onboarding_favourite.png b/assets/images/2.0x/onboarding_favourite.png new file mode 100644 index 0000000..6dc8292 Binary files /dev/null and b/assets/images/2.0x/onboarding_favourite.png differ diff --git a/assets/images/2.0x/onboarding_gestures.png b/assets/images/2.0x/onboarding_gestures.png new file mode 100644 index 0000000..2947866 Binary files /dev/null and b/assets/images/2.0x/onboarding_gestures.png differ diff --git a/assets/images/2.0x/onboarding_logo_app.png b/assets/images/2.0x/onboarding_logo_app.png new file mode 100644 index 0000000..3f9d974 Binary files /dev/null and b/assets/images/2.0x/onboarding_logo_app.png differ diff --git a/assets/images/3.0x/onboarding_branding.png b/assets/images/3.0x/onboarding_branding.png new file mode 100644 index 0000000..555c3c4 Binary files /dev/null and b/assets/images/3.0x/onboarding_branding.png differ diff --git a/assets/images/3.0x/onboarding_describeApp.png b/assets/images/3.0x/onboarding_describeApp.png new file mode 100644 index 0000000..4573344 Binary files /dev/null and b/assets/images/3.0x/onboarding_describeApp.png differ diff --git a/assets/images/3.0x/onboarding_favourite.png b/assets/images/3.0x/onboarding_favourite.png new file mode 100644 index 0000000..b972c42 Binary files /dev/null and b/assets/images/3.0x/onboarding_favourite.png differ diff --git a/assets/images/3.0x/onboarding_gestures.png b/assets/images/3.0x/onboarding_gestures.png new file mode 100644 index 0000000..da6e6f2 Binary files /dev/null and b/assets/images/3.0x/onboarding_gestures.png differ diff --git a/assets/images/3.0x/onboarding_logo_app.png b/assets/images/3.0x/onboarding_logo_app.png new file mode 100644 index 0000000..0f3684f Binary files /dev/null and b/assets/images/3.0x/onboarding_logo_app.png differ diff --git a/assets/images/4.0x/onboarding_branding.png b/assets/images/4.0x/onboarding_branding.png new file mode 100644 index 0000000..85dea77 Binary files /dev/null and b/assets/images/4.0x/onboarding_branding.png differ diff --git a/assets/images/4.0x/onboarding_describeApp.png b/assets/images/4.0x/onboarding_describeApp.png new file mode 100644 index 0000000..0bf6fc5 Binary files /dev/null and b/assets/images/4.0x/onboarding_describeApp.png differ diff --git a/assets/images/4.0x/onboarding_favourite.png b/assets/images/4.0x/onboarding_favourite.png new file mode 100644 index 0000000..376f3dc Binary files /dev/null and b/assets/images/4.0x/onboarding_favourite.png differ diff --git a/assets/images/4.0x/onboarding_gestures.png b/assets/images/4.0x/onboarding_gestures.png new file mode 100644 index 0000000..e762d2a Binary files /dev/null and b/assets/images/4.0x/onboarding_gestures.png differ diff --git a/assets/images/4.0x/onboarding_logo_app.png b/assets/images/4.0x/onboarding_logo_app.png new file mode 100644 index 0000000..ca26b11 Binary files /dev/null and b/assets/images/4.0x/onboarding_logo_app.png differ diff --git a/assets/images/onboarding_branding.png b/assets/images/onboarding_branding.png new file mode 100644 index 0000000..9e9e391 Binary files /dev/null and b/assets/images/onboarding_branding.png differ diff --git a/assets/images/onboarding_describeApp.png b/assets/images/onboarding_describeApp.png new file mode 100644 index 0000000..664b27c Binary files /dev/null and b/assets/images/onboarding_describeApp.png differ diff --git a/assets/images/onboarding_favourite.png b/assets/images/onboarding_favourite.png new file mode 100644 index 0000000..1bc8c74 Binary files /dev/null and b/assets/images/onboarding_favourite.png differ diff --git a/assets/images/onboarding_gestures.png b/assets/images/onboarding_gestures.png new file mode 100644 index 0000000..711eb2d Binary files /dev/null and b/assets/images/onboarding_gestures.png differ diff --git a/assets/images/onboarding_logo_app.png b/assets/images/onboarding_logo_app.png new file mode 100644 index 0000000..ecf4946 Binary files /dev/null and b/assets/images/onboarding_logo_app.png differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index deada20..8beebbf 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -141,4 +141,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 -COCOAPODS: 1.10.2 +COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 363265e..358840e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -1161,4 +1161,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/lib/core/di/di_repository_module.dart b/lib/core/di/di_repository_module.dart index e2a5a7f..5e99135 100644 --- a/lib/core/di/di_repository_module.dart +++ b/lib/core/di/di_repository_module.dart @@ -1,14 +1,11 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:fluttips/core/repository/session_repository.dart'; import 'package:fluttips/core/repository/tip_repository.dart'; -import 'package:fluttips/core/source/local_source/auth_local_source.dart'; -import 'package:fluttips/core/source/remote_source/auth_remote_source.dart'; -import 'package:fluttips/core/source/common/auth_interceptor.dart'; import 'package:fluttips/core/source/common/http_service.dart'; import 'package:fluttips/core/source/remote_source/tip_remote_source.dart'; import 'package:get_it/get_it.dart'; - import 'package:fluttips/core/source/database.dart'; +import 'package:fluttips/core/source/local_source/session_local_source.dart'; class RepositoryDiModule { RepositoryDiModule._privateConstructor(); @@ -29,20 +26,19 @@ class RepositoryDiModule { extension _GetItUseCaseDiModuleExtensions on GetIt { void _setupProvidersAndUtils() { registerLazySingleton(FlutterSecureStorage.new); - registerLazySingleton(() => HttpServiceDio([AuthInterceptor(get())])); + registerLazySingleton(() => HttpServiceDio([])); registerSingletonAsync( () => $FloorAppDatabase.databaseBuilder('database.db').build(), ); } void _setupRepositories() { - registerLazySingleton(() => SessionRepository(get(), get(), get())); + registerLazySingleton(() => SessionRepository()); registerLazySingleton(() => TipRepository(get(), get(), get())); } void _setupSources() { - registerLazySingleton(() => AuthLocalSource(get())); - registerLazySingleton(() => AuthRemoteSource(get())); + registerLazySingleton(() => SessionLocalSource(get())); registerLazySingleton(() => TipRemoteSource(get())); registerLazySingleton(() => get().amountLocalSource); registerLazySingleton(() => get().tipsLocalSource); diff --git a/lib/core/model/authentication_status.dart b/lib/core/model/authentication_status.dart deleted file mode 100644 index a47bf4d..0000000 --- a/lib/core/model/authentication_status.dart +++ /dev/null @@ -1 +0,0 @@ -enum AuthenticationStatus { authenticated, unauthenticated } diff --git a/lib/core/model/onboarding_status.dart b/lib/core/model/onboarding_status.dart new file mode 100644 index 0000000..7ebbcad --- /dev/null +++ b/lib/core/model/onboarding_status.dart @@ -0,0 +1,4 @@ +enum AppSessionStatus { + notOnboarded, + onboarded, +} diff --git a/lib/core/model/service/auth_models.dart b/lib/core/model/service/auth_models.dart deleted file mode 100644 index 7772c8c..0000000 --- a/lib/core/model/service/auth_models.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:fluttips/core/model/user.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'auth_models.freezed.dart'; - -part 'auth_models.g.dart'; - -@freezed -class SignInResponse with _$SignInResponse { - @JsonSerializable() - factory SignInResponse({ - required String accessToken, - required User user, - }) = _SignInResponse; - - factory SignInResponse.fromJson(Map json) => - _$SignInResponseFromJson(json); -} - -@freezed -class SignInRequest with _$SignInRequest { - @JsonSerializable() - factory SignInRequest({ - @JsonKey(name: 'email') required String email, - @JsonKey(name: 'password') required String password, - }) = _SignInRequest; - - factory SignInRequest.fromJson(Map json) => - _$SignInRequestFromJson(json); -} diff --git a/lib/core/model/service/auth_models.freezed.dart b/lib/core/model/service/auth_models.freezed.dart deleted file mode 100644 index b56c3b0..0000000 --- a/lib/core/model/service/auth_models.freezed.dart +++ /dev/null @@ -1,353 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target - -part of 'auth_models.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -SignInResponse _$SignInResponseFromJson(Map json) { - return _SignInResponse.fromJson(json); -} - -/// @nodoc -mixin _$SignInResponse { - String get accessToken => throw _privateConstructorUsedError; - User get user => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $SignInResponseCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SignInResponseCopyWith<$Res> { - factory $SignInResponseCopyWith( - SignInResponse value, $Res Function(SignInResponse) then) = - _$SignInResponseCopyWithImpl<$Res, SignInResponse>; - @useResult - $Res call({String accessToken, User user}); - - $UserCopyWith<$Res> get user; -} - -/// @nodoc -class _$SignInResponseCopyWithImpl<$Res, $Val extends SignInResponse> - implements $SignInResponseCopyWith<$Res> { - _$SignInResponseCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accessToken = null, - Object? user = null, - }) { - return _then(_value.copyWith( - accessToken: null == accessToken - ? _value.accessToken - : accessToken // ignore: cast_nullable_to_non_nullable - as String, - user: null == user - ? _value.user - : user // ignore: cast_nullable_to_non_nullable - as User, - ) as $Val); - } - - @override - @pragma('vm:prefer-inline') - $UserCopyWith<$Res> get user { - return $UserCopyWith<$Res>(_value.user, (value) { - return _then(_value.copyWith(user: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$_SignInResponseCopyWith<$Res> - implements $SignInResponseCopyWith<$Res> { - factory _$$_SignInResponseCopyWith( - _$_SignInResponse value, $Res Function(_$_SignInResponse) then) = - __$$_SignInResponseCopyWithImpl<$Res>; - @override - @useResult - $Res call({String accessToken, User user}); - - @override - $UserCopyWith<$Res> get user; -} - -/// @nodoc -class __$$_SignInResponseCopyWithImpl<$Res> - extends _$SignInResponseCopyWithImpl<$Res, _$_SignInResponse> - implements _$$_SignInResponseCopyWith<$Res> { - __$$_SignInResponseCopyWithImpl( - _$_SignInResponse _value, $Res Function(_$_SignInResponse) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accessToken = null, - Object? user = null, - }) { - return _then(_$_SignInResponse( - accessToken: null == accessToken - ? _value.accessToken - : accessToken // ignore: cast_nullable_to_non_nullable - as String, - user: null == user - ? _value.user - : user // ignore: cast_nullable_to_non_nullable - as User, - )); - } -} - -/// @nodoc - -@JsonSerializable() -class _$_SignInResponse implements _SignInResponse { - _$_SignInResponse({required this.accessToken, required this.user}); - - factory _$_SignInResponse.fromJson(Map json) => - _$$_SignInResponseFromJson(json); - - @override - final String accessToken; - @override - final User user; - - @override - String toString() { - return 'SignInResponse(accessToken: $accessToken, user: $user)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_SignInResponse && - (identical(other.accessToken, accessToken) || - other.accessToken == accessToken) && - (identical(other.user, user) || other.user == user)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, accessToken, user); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_SignInResponseCopyWith<_$_SignInResponse> get copyWith => - __$$_SignInResponseCopyWithImpl<_$_SignInResponse>(this, _$identity); - - @override - Map toJson() { - return _$$_SignInResponseToJson( - this, - ); - } -} - -abstract class _SignInResponse implements SignInResponse { - factory _SignInResponse( - {required final String accessToken, - required final User user}) = _$_SignInResponse; - - factory _SignInResponse.fromJson(Map json) = - _$_SignInResponse.fromJson; - - @override - String get accessToken; - @override - User get user; - @override - @JsonKey(ignore: true) - _$$_SignInResponseCopyWith<_$_SignInResponse> get copyWith => - throw _privateConstructorUsedError; -} - -SignInRequest _$SignInRequestFromJson(Map json) { - return _SignInRequest.fromJson(json); -} - -/// @nodoc -mixin _$SignInRequest { - @JsonKey(name: 'email') - String get email => throw _privateConstructorUsedError; - @JsonKey(name: 'password') - String get password => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $SignInRequestCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SignInRequestCopyWith<$Res> { - factory $SignInRequestCopyWith( - SignInRequest value, $Res Function(SignInRequest) then) = - _$SignInRequestCopyWithImpl<$Res, SignInRequest>; - @useResult - $Res call( - {@JsonKey(name: 'email') String email, - @JsonKey(name: 'password') String password}); -} - -/// @nodoc -class _$SignInRequestCopyWithImpl<$Res, $Val extends SignInRequest> - implements $SignInRequestCopyWith<$Res> { - _$SignInRequestCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? email = null, - Object? password = null, - }) { - return _then(_value.copyWith( - email: null == email - ? _value.email - : email // ignore: cast_nullable_to_non_nullable - as String, - password: null == password - ? _value.password - : password // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$_SignInRequestCopyWith<$Res> - implements $SignInRequestCopyWith<$Res> { - factory _$$_SignInRequestCopyWith( - _$_SignInRequest value, $Res Function(_$_SignInRequest) then) = - __$$_SignInRequestCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {@JsonKey(name: 'email') String email, - @JsonKey(name: 'password') String password}); -} - -/// @nodoc -class __$$_SignInRequestCopyWithImpl<$Res> - extends _$SignInRequestCopyWithImpl<$Res, _$_SignInRequest> - implements _$$_SignInRequestCopyWith<$Res> { - __$$_SignInRequestCopyWithImpl( - _$_SignInRequest _value, $Res Function(_$_SignInRequest) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? email = null, - Object? password = null, - }) { - return _then(_$_SignInRequest( - email: null == email - ? _value.email - : email // ignore: cast_nullable_to_non_nullable - as String, - password: null == password - ? _value.password - : password // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc - -@JsonSerializable() -class _$_SignInRequest implements _SignInRequest { - _$_SignInRequest( - {@JsonKey(name: 'email') required this.email, - @JsonKey(name: 'password') required this.password}); - - factory _$_SignInRequest.fromJson(Map json) => - _$$_SignInRequestFromJson(json); - - @override - @JsonKey(name: 'email') - final String email; - @override - @JsonKey(name: 'password') - final String password; - - @override - String toString() { - return 'SignInRequest(email: $email, password: $password)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_SignInRequest && - (identical(other.email, email) || other.email == email) && - (identical(other.password, password) || - other.password == password)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, email, password); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_SignInRequestCopyWith<_$_SignInRequest> get copyWith => - __$$_SignInRequestCopyWithImpl<_$_SignInRequest>(this, _$identity); - - @override - Map toJson() { - return _$$_SignInRequestToJson( - this, - ); - } -} - -abstract class _SignInRequest implements SignInRequest { - factory _SignInRequest( - {@JsonKey(name: 'email') required final String email, - @JsonKey(name: 'password') required final String password}) = - _$_SignInRequest; - - factory _SignInRequest.fromJson(Map json) = - _$_SignInRequest.fromJson; - - @override - @JsonKey(name: 'email') - String get email; - @override - @JsonKey(name: 'password') - String get password; - @override - @JsonKey(ignore: true) - _$$_SignInRequestCopyWith<_$_SignInRequest> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/core/model/service/auth_models.g.dart b/lib/core/model/service/auth_models.g.dart deleted file mode 100644 index c8775b0..0000000 --- a/lib/core/model/service/auth_models.g.dart +++ /dev/null @@ -1,31 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'auth_models.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$_SignInResponse _$$_SignInResponseFromJson(Map json) => - _$_SignInResponse( - accessToken: json['access_token'] as String, - user: User.fromJson(json['user'] as Map), - ); - -Map _$$_SignInResponseToJson(_$_SignInResponse instance) => - { - 'access_token': instance.accessToken, - 'user': instance.user.toJson(), - }; - -_$_SignInRequest _$$_SignInRequestFromJson(Map json) => - _$_SignInRequest( - email: json['email'] as String, - password: json['password'] as String, - ); - -Map _$$_SignInRequestToJson(_$_SignInRequest instance) => - { - 'email': instance.email, - 'password': instance.password, - }; diff --git a/lib/core/model/user.dart b/lib/core/model/user.dart deleted file mode 100644 index ab31fcd..0000000 --- a/lib/core/model/user.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'user.freezed.dart'; - -part 'user.g.dart'; - -@freezed -class User with _$User { - @JsonSerializable() - factory User({ - required String email, - String? name, - }) = _User; - - factory User.fromJson(Map json) => _$UserFromJson(json); -} diff --git a/lib/core/model/user.freezed.dart b/lib/core/model/user.freezed.dart deleted file mode 100644 index 6c5aea1..0000000 --- a/lib/core/model/user.freezed.dart +++ /dev/null @@ -1,159 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target - -part of 'user.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -User _$UserFromJson(Map json) { - return _User.fromJson(json); -} - -/// @nodoc -mixin _$User { - String get email => throw _privateConstructorUsedError; - String? get name => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $UserCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $UserCopyWith<$Res> { - factory $UserCopyWith(User value, $Res Function(User) then) = - _$UserCopyWithImpl<$Res, User>; - @useResult - $Res call({String email, String? name}); -} - -/// @nodoc -class _$UserCopyWithImpl<$Res, $Val extends User> - implements $UserCopyWith<$Res> { - _$UserCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? email = null, - Object? name = freezed, - }) { - return _then(_value.copyWith( - email: null == email - ? _value.email - : email // ignore: cast_nullable_to_non_nullable - as String, - name: freezed == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$_UserCopyWith<$Res> implements $UserCopyWith<$Res> { - factory _$$_UserCopyWith(_$_User value, $Res Function(_$_User) then) = - __$$_UserCopyWithImpl<$Res>; - @override - @useResult - $Res call({String email, String? name}); -} - -/// @nodoc -class __$$_UserCopyWithImpl<$Res> extends _$UserCopyWithImpl<$Res, _$_User> - implements _$$_UserCopyWith<$Res> { - __$$_UserCopyWithImpl(_$_User _value, $Res Function(_$_User) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? email = null, - Object? name = freezed, - }) { - return _then(_$_User( - email: null == email - ? _value.email - : email // ignore: cast_nullable_to_non_nullable - as String, - name: freezed == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc - -@JsonSerializable() -class _$_User implements _User { - _$_User({required this.email, this.name}); - - factory _$_User.fromJson(Map json) => _$$_UserFromJson(json); - - @override - final String email; - @override - final String? name; - - @override - String toString() { - return 'User(email: $email, name: $name)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_User && - (identical(other.email, email) || other.email == email) && - (identical(other.name, name) || other.name == name)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, email, name); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_UserCopyWith<_$_User> get copyWith => - __$$_UserCopyWithImpl<_$_User>(this, _$identity); - - @override - Map toJson() { - return _$$_UserToJson( - this, - ); - } -} - -abstract class _User implements User { - factory _User({required final String email, final String? name}) = _$_User; - - factory _User.fromJson(Map json) = _$_User.fromJson; - - @override - String get email; - @override - String? get name; - @override - @JsonKey(ignore: true) - _$$_UserCopyWith<_$_User> get copyWith => throw _privateConstructorUsedError; -} diff --git a/lib/core/model/user.g.dart b/lib/core/model/user.g.dart deleted file mode 100644 index 4588246..0000000 --- a/lib/core/model/user.g.dart +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$_User _$$_UserFromJson(Map json) => _$_User( - email: json['email'] as String, - name: json['name'] as String?, - ); - -Map _$$_UserToJson(_$_User instance) => { - 'email': instance.email, - 'name': instance.name, - }; diff --git a/lib/core/repository/session_repository.dart b/lib/core/repository/session_repository.dart index 7a06ecb..0e3da35 100644 --- a/lib/core/repository/session_repository.dart +++ b/lib/core/repository/session_repository.dart @@ -1,44 +1,16 @@ import 'dart:async'; -import 'package:fluttips/core/model/authentication_status.dart'; -import 'package:fluttips/core/model/user.dart'; -import 'package:fluttips/core/source/local_source/auth_local_source.dart'; -import 'package:fluttips/core/source/remote_source/auth_remote_source.dart'; - -import 'package:fluttips/core/source/database.dart'; +import 'package:fluttips/core/di/di_provider.dart'; +import 'package:fluttips/core/model/onboarding_status.dart'; +import 'package:fluttips/core/source/local_source/session_local_source.dart'; class SessionRepository { - final AuthLocalSource _authLocalSource; - final AuthRemoteSource _authRemoteSource; - final AppDatabase _appDataBase; - - SessionRepository( - this._appDataBase, - this._authLocalSource, - this._authRemoteSource, - ); - - Stream get status => - _authLocalSource.getUserToken().map( - (token) => token == null - ? AuthenticationStatus.unauthenticated - : AuthenticationStatus.authenticated, - ); - - Stream getUserInfo() => _authLocalSource.getUser(); + final SessionLocalSource _sessionLocalSource = DiProvider.get(); - Future signInUser({ - required String email, - required String password, - }) async { - final response = await _authRemoteSource.signIn(email, password); - await _authLocalSource.saveUserToken(response.accessToken); - await _authLocalSource.saveUserInfo(response.user); - } + Stream getSessionStatus() => _sessionLocalSource + .getSessionStatus() + .map((status) => status ?? AppSessionStatus.notOnboarded); - Future logOut() async { - await _appDataBase.clearAllTables(); - await _authLocalSource.saveUserToken(null); - await _authLocalSource.saveUserInfo(null); - } + Future setSessionStatus(AppSessionStatus sessionStatus) => + _sessionLocalSource.setSessionStatus(sessionStatus); } diff --git a/lib/core/source/common/auth_interceptor.dart b/lib/core/source/common/auth_interceptor.dart deleted file mode 100644 index f2aca8a..0000000 --- a/lib/core/source/common/auth_interceptor.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:io'; - -import 'package:dio/dio.dart'; -import 'package:fluttips/core/source/local_source/auth_local_source.dart'; - -class AuthInterceptor extends Interceptor { - final AuthLocalSource _authLocalSource; - - AuthInterceptor(this._authLocalSource); - - @override - Future onRequest( - RequestOptions options, - RequestInterceptorHandler handler, - ) async { - final accessToken = await _authLocalSource.getUserToken().first; - if (accessToken != null) { - options.headers[HttpHeaders.authorizationHeader] = 'Bearer $accessToken'; - } - handler.next(options); - } - - @override - Future onError( - DioError err, - ErrorInterceptorHandler handler, - ) async { - if (err.response?.statusCode == HttpStatus.unauthorized) { - await _authLocalSource.saveUserToken(null); - } - handler.next(err); - } -} diff --git a/lib/core/source/local_source/auth_local_source.dart b/lib/core/source/local_source/auth_local_source.dart deleted file mode 100644 index e011c74..0000000 --- a/lib/core/source/local_source/auth_local_source.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:fluttips/core/common/store/secure_storage_cached_source.dart'; -import 'package:fluttips/core/model/user.dart'; -import 'package:stock/stock.dart'; - -class AuthLocalSource { - static const _storageAuthPrefix = 'AuthLocalSource'; - static const _keyToken = '$_storageAuthPrefix.token'; - static const _keyUser = '$_storageAuthPrefix.user'; - - late SourceOfTruth _userTokenStorage; - late SourceOfTruth _userStorage; - - AuthLocalSource(FlutterSecureStorage storage) { - final secureStorage = SecuredStorageSourceOfTruth(storage); - _userTokenStorage = secureStorage; - _userStorage = secureStorage.mapToUsingMapper(UserStockTypeMapper()); - } - - Stream getUserToken() => _userTokenStorage.reader(_keyToken); - - Stream getUser() => _userStorage.reader(_keyUser); - - Future saveUserToken(String? token) => - _userTokenStorage.write(_keyToken, token); - - Future saveUserInfo(User? user) => _userStorage.write(_keyUser, user); -} - -class UserStockTypeMapper extends StockTypeMapper { - @override - User fromInput(String value) => User.fromJson(json.decode(value)); - - @override - String fromOutput(User value) => json.encode(value.toJson()); -} diff --git a/lib/core/source/local_source/session_local_source.dart b/lib/core/source/local_source/session_local_source.dart new file mode 100644 index 0000000..c97b985 --- /dev/null +++ b/lib/core/source/local_source/session_local_source.dart @@ -0,0 +1,36 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:fluttips/core/common/helper/enum_helpers.dart'; +import 'package:fluttips/core/common/store/secure_storage_cached_source.dart'; +import 'package:fluttips/core/model/onboarding_status.dart'; +import 'package:stock/stock.dart'; + +class SessionLocalSource { + static const _storageAuthPrefix = 'AuthLocalSource'; + static const _keyOnboarding = '$_storageAuthPrefix.hasCompletedOnboarding'; + + late SourceOfTruth _userOnboardedStorage; + + SessionLocalSource(FlutterSecureStorage storage) { + final secureStorage = SecuredStorageSourceOfTruth(storage); + _userOnboardedStorage = + secureStorage.mapToUsingMapper(_AppSessionStatusStockTypeMapper()); + } + + Stream getSessionStatus() => + _userOnboardedStorage.reader(_keyOnboarding); + + Future setSessionStatus(AppSessionStatus status) async { + await _userOnboardedStorage.write(_keyOnboarding, status); + } +} + +class _AppSessionStatusStockTypeMapper + extends StockTypeMapper { + @override + AppSessionStatus fromInput(String value) => + enumFromString(AppSessionStatus.values, value) ?? + AppSessionStatus.notOnboarded; + + @override + String fromOutput(AppSessionStatus value) => value.name; +} diff --git a/lib/core/source/remote_source/auth_remote_source.dart b/lib/core/source/remote_source/auth_remote_source.dart deleted file mode 100644 index bbdda6d..0000000 --- a/lib/core/source/remote_source/auth_remote_source.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:fluttips/core/model/service/auth_models.dart'; -import 'package:fluttips/core/model/service/responses/service_response.dart'; -import 'package:fluttips/core/source/common/http_service.dart'; - -class AuthRemoteSource { - final HttpServiceDio _httpService; - - static const _urlLogin = 'auth/v1/token'; - - AuthRemoteSource(this._httpService); - - Future signIn(String email, String password) async => - (await _httpService.postAndProcessResponse( - _urlLogin, - queryParameters: {'grant_type': 'password'}, - data: SignInRequest(email: email, password: password).toJson(), - serializer: (data) => SignInResponse.fromJson(data), - )) - .getDataOrThrow(); -} diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index ad38508..1185e11 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -9,8 +9,43 @@ import 'package:flutter/widgets.dart'; +class $AssetsImagesGen { + const $AssetsImagesGen(); + + /// File path: assets/images/onboarding_branding.png + AssetGenImage get onboardingBranding => + const AssetGenImage('assets/images/onboarding_branding.png'); + + /// File path: assets/images/onboarding_describeApp.png + AssetGenImage get onboardingDescribeApp => + const AssetGenImage('assets/images/onboarding_describeApp.png'); + + /// File path: assets/images/onboarding_favourite.png + AssetGenImage get onboardingFavourite => + const AssetGenImage('assets/images/onboarding_favourite.png'); + + /// File path: assets/images/onboarding_gestures.png + AssetGenImage get onboardingGestures => + const AssetGenImage('assets/images/onboarding_gestures.png'); + + /// File path: assets/images/onboarding_logo_app.png + AssetGenImage get onboardingLogoApp => + const AssetGenImage('assets/images/onboarding_logo_app.png'); + + /// List of all assets + List get values => [ + onboardingBranding, + onboardingDescribeApp, + onboardingFavourite, + onboardingGestures, + onboardingLogoApp + ]; +} + class Assets { Assets._(); + + static const $AssetsImagesGen images = $AssetsImagesGen(); } class AssetGenImage { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index bb51095..3331fbf 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,4 +1,7 @@ { + "button_lets_go": "Let's go!", + "button_skip": "Skip", + "button_start": "Start", "error": "Error: {text}", "@error": { "description": "Gives the user an error explanation", @@ -20,6 +23,12 @@ "mail": "Mail", "messageEmptyFavoritesScreen": "You don't have any favorites yet :(", "myFavorites": "My Favorites", + "onboarding_favourite_first_text": "Save your favorite tips", + "onboarding_favourite_second_text": "to have all of them together", + "onboarding_gestures_first_text": "Navigate through the tip pages", + "onboarding_gestures_second_text": "swiping up and down", + "onboarding_tips_and_trick_second_text": "for Flutter development", + "onboarding_tips_and_tricks_first_text": "Find lots of tips and tricks", "password": "Password", "search": "Search", "xmartlabs_projects": "Xmartlabs' projects" diff --git a/lib/ui/app_router.dart b/lib/ui/app_router.dart index 0550ff9..99fbd67 100644 --- a/lib/ui/app_router.dart +++ b/lib/ui/app_router.dart @@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:fluttips/ui/catalog/catalog_screen.dart'; import 'package:fluttips/ui/favourites/list_favourites_tips_screen.dart'; import 'package:fluttips/ui/home/home_screen.dart'; +import 'package:fluttips/ui/onboarding/onboarding_screen.dart'; import 'package:fluttips/ui/section/section_router.dart'; import 'package:fluttips/ui/tips/show_tips_type.dart'; import 'package:fluttips/ui/tips/tips_screen.dart'; @@ -20,12 +21,18 @@ part 'app_router.gr.dart'; page: CatalogScreen, ), AutoRoute( - name: 'UnauthenticatedRouter', + name: 'UncompletedOnboardingRouter', page: SectionRouter, - children: [], + children: [ + AutoRoute( + initial: true, + name: 'OnboardingRoute', + page: OnboardingScreen, + ), + ], ), AutoRoute( - name: 'AuthenticatedRouter', + name: 'UserOnboardedRouter', page: SectionRouter, children: [ AutoRoute( diff --git a/lib/ui/app_router.gr.dart b/lib/ui/app_router.gr.dart index 42f7288..6d65469 100644 --- a/lib/ui/app_router.gr.dart +++ b/lib/ui/app_router.gr.dart @@ -23,18 +23,24 @@ class _$AppRouter extends RootStackRouter { child: const CatalogScreen(), ); }, - UnauthenticatedRouter.name: (routeData) { + UncompletedOnboardingRouter.name: (routeData) { return MaterialPageX( routeData: routeData, child: const SectionRouter(), ); }, - AuthenticatedRouter.name: (routeData) { + UserOnboardedRouter.name: (routeData) { return MaterialPageX( routeData: routeData, child: const SectionRouter(), ); }, + OnboardingRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const OnboardingScreen(), + ); + }, HomeScreenRoute.name: (routeData) { return MaterialPageX( routeData: routeData, @@ -84,17 +90,24 @@ class _$AppRouter extends RootStackRouter { path: '/catalog-screen', ), RouteConfig( - UnauthenticatedRouter.name, + UncompletedOnboardingRouter.name, path: '/section-router', + children: [ + RouteConfig( + OnboardingRoute.name, + path: '', + parent: UncompletedOnboardingRouter.name, + ) + ], ), RouteConfig( - AuthenticatedRouter.name, + UserOnboardedRouter.name, path: '/section-router', children: [ RouteConfig( HomeScreenRoute.name, path: '', - parent: AuthenticatedRouter.name, + parent: UserOnboardedRouter.name, children: [ RouteConfig( HomeTipsScreenRoute.name, @@ -116,7 +129,7 @@ class _$AppRouter extends RootStackRouter { RouteConfig( ListFavouritesTipsScreenRoute.name, path: 'list_favourite', - parent: AuthenticatedRouter.name, + parent: UserOnboardedRouter.name, ), ], ), @@ -137,27 +150,40 @@ class CatalogRouter extends PageRouteInfo { /// generated route for /// [SectionRouter] -class UnauthenticatedRouter extends PageRouteInfo { - const UnauthenticatedRouter() +class UncompletedOnboardingRouter extends PageRouteInfo { + const UncompletedOnboardingRouter({List? children}) : super( - UnauthenticatedRouter.name, + UncompletedOnboardingRouter.name, path: '/section-router', + initialChildren: children, ); - static const String name = 'UnauthenticatedRouter'; + static const String name = 'UncompletedOnboardingRouter'; } /// generated route for /// [SectionRouter] -class AuthenticatedRouter extends PageRouteInfo { - const AuthenticatedRouter({List? children}) +class UserOnboardedRouter extends PageRouteInfo { + const UserOnboardedRouter({List? children}) : super( - AuthenticatedRouter.name, + UserOnboardedRouter.name, path: '/section-router', initialChildren: children, ); - static const String name = 'AuthenticatedRouter'; + static const String name = 'UserOnboardedRouter'; +} + +/// generated route for +/// [OnboardingScreen] +class OnboardingRoute extends PageRouteInfo { + const OnboardingRoute() + : super( + OnboardingRoute.name, + path: '', + ); + + static const String name = 'OnboardingRoute'; } /// generated route for diff --git a/lib/ui/common/animated_pager_dote.dart b/lib/ui/common/animated_pager_dote.dart new file mode 100644 index 0000000..d70a55d --- /dev/null +++ b/lib/ui/common/animated_pager_dote.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +const animationShortDuration = Duration(milliseconds: 200); + +class AnimatedPagerDot extends StatelessWidget { + const AnimatedPagerDot({ + required this.isDotSelected, + required this.color, + Key? key, + }) : super(key: key); + + final bool isDotSelected; + final Color color; + + @override + Widget build(BuildContext context) => AnimatedContainer( + duration: animationShortDuration, + height: 10.h, + width: 10.w, + margin: EdgeInsets.only(left: 4.w, right: 4.w, bottom: 15.h, top: 10.h), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isDotSelected ? color : color.withOpacity(0.5), + ), + ); +} diff --git a/lib/ui/common/border_button.dart b/lib/ui/common/border_button.dart new file mode 100644 index 0000000..c678ab1 --- /dev/null +++ b/lib/ui/common/border_button.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttips/ui/common/context_extensions.dart'; +import 'package:fluttips/ui/theme/app_theme.dart'; + +class BorderButton extends StatelessWidget { + final String text; + final VoidCallback action; + + const BorderButton({ + required this.text, + required this.action, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) => TextButton( + onPressed: action, + style: TextButton.styleFrom( + padding: EdgeInsets.only( + top: 5.h, + left: 10.w, + right: 5.w, + bottom: 5.h, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.r), + side: BorderSide( + color: context.theme.colors.surface, + ), + ), + backgroundColor: context.theme.colors.background, + alignment: Alignment.center, + ), + child: Text( + text, + style: context.theme.textStyles.labelLarge!.copyWith( + color: context.theme.colors.surface, + ), + ), + ); +} diff --git a/lib/ui/main/main_cubit.dart b/lib/ui/main/main_cubit.dart index 78573e6..7fdf4e3 100644 --- a/lib/ui/main/main_cubit.dart +++ b/lib/ui/main/main_cubit.dart @@ -3,10 +3,11 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:fluttips/core/common/extension/stream_future_extensions.dart'; import 'package:fluttips/core/di/di_provider.dart'; -import 'package:fluttips/core/model/authentication_status.dart'; import 'package:fluttips/core/repository/session_repository.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:fluttips/core/model/onboarding_status.dart'; + part 'main_cubit.freezed.dart'; part 'main_state.dart'; @@ -14,13 +15,13 @@ part 'main_state.dart'; class MainCubit extends Cubit { final SessionRepository _sessionRepository = DiProvider.get(); - late StreamSubscription - _authenticationStatusSubscription; + late StreamSubscription _authenticationStatusSubscription; MainCubit() : super(const MainBaseState.state()) { - _authenticationStatusSubscription = _sessionRepository.status + _authenticationStatusSubscription = _sessionRepository + .getSessionStatus() .filterSuccess() - .listen((status) => emit(state.copyWith(authenticationStatus: status))); + .listen((status) => emit(state.copyWith(appSessionStatus: status))); } @override diff --git a/lib/ui/main/main_cubit.freezed.dart b/lib/ui/main/main_cubit.freezed.dart index 475d15e..50f64fd 100644 --- a/lib/ui/main/main_cubit.freezed.dart +++ b/lib/ui/main/main_cubit.freezed.dart @@ -16,21 +16,20 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$MainBaseState { - AuthenticationStatus? get authenticationStatus => - throw _privateConstructorUsedError; + AppSessionStatus? get appSessionStatus => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(AuthenticationStatus? authenticationStatus) state, + required TResult Function(AppSessionStatus? appSessionStatus) state, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AuthenticationStatus? authenticationStatus)? state, + TResult? Function(AppSessionStatus? appSessionStatus)? state, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(AuthenticationStatus? authenticationStatus)? state, + TResult Function(AppSessionStatus? appSessionStatus)? state, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -62,7 +61,7 @@ abstract class $MainBaseStateCopyWith<$Res> { MainBaseState value, $Res Function(MainBaseState) then) = _$MainBaseStateCopyWithImpl<$Res, MainBaseState>; @useResult - $Res call({AuthenticationStatus? authenticationStatus}); + $Res call({AppSessionStatus? appSessionStatus}); } /// @nodoc @@ -78,13 +77,13 @@ class _$MainBaseStateCopyWithImpl<$Res, $Val extends MainBaseState> @pragma('vm:prefer-inline') @override $Res call({ - Object? authenticationStatus = freezed, + Object? appSessionStatus = freezed, }) { return _then(_value.copyWith( - authenticationStatus: freezed == authenticationStatus - ? _value.authenticationStatus - : authenticationStatus // ignore: cast_nullable_to_non_nullable - as AuthenticationStatus?, + appSessionStatus: freezed == appSessionStatus + ? _value.appSessionStatus + : appSessionStatus // ignore: cast_nullable_to_non_nullable + as AppSessionStatus?, ) as $Val); } } @@ -97,7 +96,7 @@ abstract class _$$_MainStateCopyWith<$Res> __$$_MainStateCopyWithImpl<$Res>; @override @useResult - $Res call({AuthenticationStatus? authenticationStatus}); + $Res call({AppSessionStatus? appSessionStatus}); } /// @nodoc @@ -111,13 +110,13 @@ class __$$_MainStateCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? authenticationStatus = freezed, + Object? appSessionStatus = freezed, }) { return _then(_$_MainState( - authenticationStatus: freezed == authenticationStatus - ? _value.authenticationStatus - : authenticationStatus // ignore: cast_nullable_to_non_nullable - as AuthenticationStatus?, + appSessionStatus: freezed == appSessionStatus + ? _value.appSessionStatus + : appSessionStatus // ignore: cast_nullable_to_non_nullable + as AppSessionStatus?, )); } } @@ -125,15 +124,15 @@ class __$$_MainStateCopyWithImpl<$Res> /// @nodoc class _$_MainState implements _MainState { - const _$_MainState({this.authenticationStatus = null}); + const _$_MainState({this.appSessionStatus = null}); @override @JsonKey() - final AuthenticationStatus? authenticationStatus; + final AppSessionStatus? appSessionStatus; @override String toString() { - return 'MainBaseState.state(authenticationStatus: $authenticationStatus)'; + return 'MainBaseState.state(appSessionStatus: $appSessionStatus)'; } @override @@ -141,12 +140,12 @@ class _$_MainState implements _MainState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$_MainState && - (identical(other.authenticationStatus, authenticationStatus) || - other.authenticationStatus == authenticationStatus)); + (identical(other.appSessionStatus, appSessionStatus) || + other.appSessionStatus == appSessionStatus)); } @override - int get hashCode => Object.hash(runtimeType, authenticationStatus); + int get hashCode => Object.hash(runtimeType, appSessionStatus); @JsonKey(ignore: true) @override @@ -157,27 +156,27 @@ class _$_MainState implements _MainState { @override @optionalTypeArgs TResult when({ - required TResult Function(AuthenticationStatus? authenticationStatus) state, + required TResult Function(AppSessionStatus? appSessionStatus) state, }) { - return state(authenticationStatus); + return state(appSessionStatus); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AuthenticationStatus? authenticationStatus)? state, + TResult? Function(AppSessionStatus? appSessionStatus)? state, }) { - return state?.call(authenticationStatus); + return state?.call(appSessionStatus); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AuthenticationStatus? authenticationStatus)? state, + TResult Function(AppSessionStatus? appSessionStatus)? state, required TResult orElse(), }) { if (state != null) { - return state(authenticationStatus); + return state(appSessionStatus); } return orElse(); } @@ -212,11 +211,11 @@ class _$_MainState implements _MainState { } abstract class _MainState implements MainBaseState { - const factory _MainState({final AuthenticationStatus? authenticationStatus}) = + const factory _MainState({final AppSessionStatus? appSessionStatus}) = _$_MainState; @override - AuthenticationStatus? get authenticationStatus; + AppSessionStatus? get appSessionStatus; @override @JsonKey(ignore: true) _$$_MainStateCopyWith<_$_MainState> get copyWith => diff --git a/lib/ui/main/main_screen.dart b/lib/ui/main/main_screen.dart index 241a618..deb3e61 100644 --- a/lib/ui/main/main_screen.dart +++ b/lib/ui/main/main_screen.dart @@ -4,12 +4,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:fluttips/core/di/di_provider.dart'; -import 'package:fluttips/core/model/authentication_status.dart'; import 'package:fluttips/ui/app_router.dart'; import 'package:fluttips/ui/main/main_cubit.dart'; import 'package:fluttips/ui/resources.dart'; import 'package:fluttips/ui/theme/app_theme.dart'; +import 'package:fluttips/core/model/onboarding_status.dart'; + class MainScreen extends StatelessWidget { const MainScreen({Key? key}) : super(key: key); @@ -48,11 +49,11 @@ class _SplashContentScreen extends StatelessWidget { ); List> provideRoutes(MainBaseState state) { - switch (state.authenticationStatus) { - // TODO: Fix redirection to implement the Onboarding - case AuthenticationStatus.unauthenticated: - case AuthenticationStatus.authenticated: - return [const AuthenticatedRouter()]; + switch (state.appSessionStatus) { + case AppSessionStatus.onboarded: + return [const UserOnboardedRouter()]; + case AppSessionStatus.notOnboarded: + return [const UncompletedOnboardingRouter()]; case null: return []; } diff --git a/lib/ui/main/main_state.dart b/lib/ui/main/main_state.dart index f4a124e..3149b14 100644 --- a/lib/ui/main/main_state.dart +++ b/lib/ui/main/main_state.dart @@ -3,6 +3,6 @@ part of 'main_cubit.dart'; @freezed class MainBaseState with _$MainBaseState { const factory MainBaseState.state({ - @Default(null) AuthenticationStatus? authenticationStatus, + @Default(null) AppSessionStatus? appSessionStatus, }) = _MainState; } diff --git a/lib/ui/onboarding/onboarding_cubit.dart b/lib/ui/onboarding/onboarding_cubit.dart new file mode 100644 index 0000000..365610f --- /dev/null +++ b/lib/ui/onboarding/onboarding_cubit.dart @@ -0,0 +1,36 @@ +import 'package:bloc/bloc.dart'; +import 'package:fluttips/core/model/onboarding_status.dart'; +import 'package:fluttips/ui/section/error_handler/error_handler_cubit.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:fluttips/core/repository/session_repository.dart'; +import 'package:fluttips/core/di/di_provider.dart'; +import 'package:fluttips/ui/onboarding/onboarding_step.dart'; + +part 'onboarding_cubit.freezed.dart'; + +part 'onboarding_state.dart'; + +class OnboardingCubit extends Cubit { + // ignore: unused_field + final GeneralErrorHandler _errorHandler; + final SessionRepository _sessionRepository = DiProvider.get(); + + OnboardingCubit(this._errorHandler) + : super(const OnboardingBaseState.state()); + + void onPressedAction() { + if (state.onboardingStep.index > OnboardingStep.onboardingInitial.index) { + _sessionRepository.setSessionStatus(AppSessionStatus.onboarded); + } else { + emit( + state.copyWith( + onboardingStep: OnboardingStep.values[state.onboardingStep.index + 1], + ), + ); + } + } + + void setCurrentPage(int index) => emit( + state.copyWith(onboardingStep: OnboardingStep.onboardingPages[index]), + ); +} diff --git a/lib/ui/onboarding/onboarding_cubit.freezed.dart b/lib/ui/onboarding/onboarding_cubit.freezed.dart new file mode 100644 index 0000000..7a9a13f --- /dev/null +++ b/lib/ui/onboarding/onboarding_cubit.freezed.dart @@ -0,0 +1,224 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'onboarding_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$OnboardingBaseState { + OnboardingStep get onboardingStep => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(OnboardingStep onboardingStep) state, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(OnboardingStep onboardingStep)? state, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(OnboardingStep onboardingStep)? state, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_OnboardingState value) state, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_OnboardingState value)? state, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_OnboardingState value)? state, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $OnboardingBaseStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OnboardingBaseStateCopyWith<$Res> { + factory $OnboardingBaseStateCopyWith( + OnboardingBaseState value, $Res Function(OnboardingBaseState) then) = + _$OnboardingBaseStateCopyWithImpl<$Res, OnboardingBaseState>; + @useResult + $Res call({OnboardingStep onboardingStep}); +} + +/// @nodoc +class _$OnboardingBaseStateCopyWithImpl<$Res, $Val extends OnboardingBaseState> + implements $OnboardingBaseStateCopyWith<$Res> { + _$OnboardingBaseStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? onboardingStep = null, + }) { + return _then(_value.copyWith( + onboardingStep: null == onboardingStep + ? _value.onboardingStep + : onboardingStep // ignore: cast_nullable_to_non_nullable + as OnboardingStep, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_OnboardingStateCopyWith<$Res> + implements $OnboardingBaseStateCopyWith<$Res> { + factory _$$_OnboardingStateCopyWith( + _$_OnboardingState value, $Res Function(_$_OnboardingState) then) = + __$$_OnboardingStateCopyWithImpl<$Res>; + @override + @useResult + $Res call({OnboardingStep onboardingStep}); +} + +/// @nodoc +class __$$_OnboardingStateCopyWithImpl<$Res> + extends _$OnboardingBaseStateCopyWithImpl<$Res, _$_OnboardingState> + implements _$$_OnboardingStateCopyWith<$Res> { + __$$_OnboardingStateCopyWithImpl( + _$_OnboardingState _value, $Res Function(_$_OnboardingState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? onboardingStep = null, + }) { + return _then(_$_OnboardingState( + onboardingStep: null == onboardingStep + ? _value.onboardingStep + : onboardingStep // ignore: cast_nullable_to_non_nullable + as OnboardingStep, + )); + } +} + +/// @nodoc + +class _$_OnboardingState implements _OnboardingState { + const _$_OnboardingState( + {this.onboardingStep = OnboardingStep.onboardingInitial}); + + @override + @JsonKey() + final OnboardingStep onboardingStep; + + @override + String toString() { + return 'OnboardingBaseState.state(onboardingStep: $onboardingStep)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_OnboardingState && + (identical(other.onboardingStep, onboardingStep) || + other.onboardingStep == onboardingStep)); + } + + @override + int get hashCode => Object.hash(runtimeType, onboardingStep); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_OnboardingStateCopyWith<_$_OnboardingState> get copyWith => + __$$_OnboardingStateCopyWithImpl<_$_OnboardingState>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(OnboardingStep onboardingStep) state, + }) { + return state(onboardingStep); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(OnboardingStep onboardingStep)? state, + }) { + return state?.call(onboardingStep); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(OnboardingStep onboardingStep)? state, + required TResult orElse(), + }) { + if (state != null) { + return state(onboardingStep); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_OnboardingState value) state, + }) { + return state(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_OnboardingState value)? state, + }) { + return state?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_OnboardingState value)? state, + required TResult orElse(), + }) { + if (state != null) { + return state(this); + } + return orElse(); + } +} + +abstract class _OnboardingState implements OnboardingBaseState { + const factory _OnboardingState({final OnboardingStep onboardingStep}) = + _$_OnboardingState; + + @override + OnboardingStep get onboardingStep; + @override + @JsonKey(ignore: true) + _$$_OnboardingStateCopyWith<_$_OnboardingState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/ui/onboarding/onboarding_pager.dart b/lib/ui/onboarding/onboarding_pager.dart new file mode 100644 index 0000000..f483fe7 --- /dev/null +++ b/lib/ui/onboarding/onboarding_pager.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttips/ui/common/context_extensions.dart'; +import 'package:fluttips/ui/onboarding/onboarding_step.dart'; +import 'package:fluttips/ui/theme/app_theme.dart'; +import 'package:fluttips/ui/onboarding/onboarding_cubit.dart'; +import 'package:fluttips/ui/common/animated_pager_dote.dart'; + +class OnboardingContentScreen extends StatelessWidget { + const OnboardingContentScreen({super.key}); + + @override + Widget build(BuildContext context) => Stack( + alignment: Alignment.bottomCenter, + children: [ + const _PagerView(), + buildViewPagerIndicator(), + ], + ); + + Widget buildViewPagerIndicator() => + BlocBuilder( + builder: (context, state) => Positioned( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: OnboardingStep.onboardingPages + .map( + (pages) => AnimatedPagerDot( + isDotSelected: state.onboardingStep.index == pages.index, + color: context.theme.colors.surface, + ), + ) + .toList(), + ), + ), + ); +} + +class _PagerView extends StatefulWidget { + const _PagerView({ + Key? key, + }) : super(key: key); + + @override + State<_PagerView> createState() => _PagerViewState(); +} + +class _PagerViewState extends State<_PagerView> { + @override + Widget build(BuildContext context) => pageViewContent(context); + + final PageController _pageController = PageController(); + + PageView pageViewContent(BuildContext context) => PageView( + controller: _pageController, + onPageChanged: (index) => + context.read().setCurrentPage(index), + children: OnboardingStep.onboardingPages + .map( + (onboardingStep) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + onboardingStep.getIcon(context)!.image(), + SizedBox(width: 20.w), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + onboardingStep.getPrimaryText(context), + style: context.theme.textStyles.titleLarge!.copyWith( + color: context.theme.colors.surface, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 4.h), + Text( + onboardingStep.getSecondaryText(context), + style: context.theme.textStyles.bodyLarge!.copyWith( + color: context.theme.colors.surface, + ), + ), + ], + ), + ], + ), + ) + .toList(), + ); +} diff --git a/lib/ui/onboarding/onboarding_screen.dart b/lib/ui/onboarding/onboarding_screen.dart new file mode 100644 index 0000000..8517899 --- /dev/null +++ b/lib/ui/onboarding/onboarding_screen.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fluttips/ui/common/context_extensions.dart'; +import 'package:fluttips/ui/onboarding/onboarding_step.dart'; +import 'package:fluttips/ui/section/error_handler/error_handler_cubit.dart'; +import 'package:fluttips/ui/onboarding/onboarding_cubit.dart'; +import 'package:fluttips/ui/onboarding/onboarding_pager.dart'; +import 'package:fluttips/ui/onboarding/onboarding_start.dart'; +import 'package:fluttips/ui/theme/app_theme.dart'; +import 'package:fluttips/ui/common/border_button.dart'; + +class OnboardingScreen extends StatelessWidget { + const OnboardingScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => BlocProvider( + lazy: false, + create: (context) => OnboardingCubit(context.read()), + child: const OnboardingScreenContent(), + ); +} + +class OnboardingScreenContent extends StatelessWidget { + const OnboardingScreenContent({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) => + BlocBuilder( + builder: (context, state) { + final isStartButtonPressed = state.onboardingStep.index > + OnboardingStep.onboardingInitial.index; + final btnText = state.onboardingStep.getButtonText(context); + return Scaffold( + backgroundColor: context.theme.colors.background, + body: Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + isStartButtonPressed + ? const OnboardingContentScreen() + : const OnboardingStartScreen(), + Positioned( + bottom: 15, + right: 15, + child: BorderButton( + text: btnText, + action: context.read().onPressedAction, + ), + ), + ], + ), + ); + }, + ); +} diff --git a/lib/ui/onboarding/onboarding_start.dart b/lib/ui/onboarding/onboarding_start.dart new file mode 100644 index 0000000..d826f1c --- /dev/null +++ b/lib/ui/onboarding/onboarding_start.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttips/ui/common/context_extensions.dart'; +import 'package:fluttips/ui/theme/app_theme.dart'; +import 'package:fluttips/gen/assets.gen.dart'; + +class OnboardingStartScreen extends StatelessWidget { + const OnboardingStartScreen({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) => Container( + color: context.theme.colors.background, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 130.w), + Assets.images.onboardingLogoApp.image(), + SizedBox(height: 70.w), + Assets.images.onboardingBranding.image(), + ], + ), + ); +} diff --git a/lib/ui/onboarding/onboarding_state.dart b/lib/ui/onboarding/onboarding_state.dart new file mode 100644 index 0000000..8feadc7 --- /dev/null +++ b/lib/ui/onboarding/onboarding_state.dart @@ -0,0 +1,8 @@ +part of 'onboarding_cubit.dart'; + +@freezed +class OnboardingBaseState with _$OnboardingBaseState { + const factory OnboardingBaseState.state({ + @Default(OnboardingStep.onboardingInitial) OnboardingStep onboardingStep, + }) = _OnboardingState; +} diff --git a/lib/ui/onboarding/onboarding_step.dart b/lib/ui/onboarding/onboarding_step.dart new file mode 100644 index 0000000..8e704bd --- /dev/null +++ b/lib/ui/onboarding/onboarding_step.dart @@ -0,0 +1,69 @@ +import 'package:flutter/widgets.dart'; +import 'package:fluttips/gen/assets.gen.dart'; +import 'package:fluttips/ui/common/context_extensions.dart'; + +enum OnboardingStep { + onboardingInitial, + onboardingTipsAndTricks, + onboardingFavourites, + onboardingGestures; + + static List get onboardingPages => [ + onboardingTipsAndTricks, + onboardingFavourites, + onboardingGestures, + ]; +} + +extension OnboardingTextExtensions on OnboardingStep { + String getPrimaryText(BuildContext context) { + switch (this) { + case OnboardingStep.onboardingInitial: + return ""; + case OnboardingStep.onboardingTipsAndTricks: + return context.localizations.onboarding_tips_and_tricks_first_text; + case OnboardingStep.onboardingFavourites: + return context.localizations.onboarding_favourite_first_text; + case OnboardingStep.onboardingGestures: + return context.localizations.onboarding_gestures_first_text; + } + } + + String getSecondaryText(BuildContext context) { + switch (this) { + case OnboardingStep.onboardingInitial: + return ""; + case OnboardingStep.onboardingTipsAndTricks: + return context.localizations.onboarding_tips_and_trick_second_text; + case OnboardingStep.onboardingFavourites: + return context.localizations.onboarding_favourite_second_text; + case OnboardingStep.onboardingGestures: + return context.localizations.onboarding_gestures_second_text; + } + } + + AssetGenImage? getIcon(BuildContext context) { + switch (this) { + case OnboardingStep.onboardingInitial: + return null; + case OnboardingStep.onboardingTipsAndTricks: + return Assets.images.onboardingDescribeApp; + case OnboardingStep.onboardingFavourites: + return Assets.images.onboardingFavourite; + case OnboardingStep.onboardingGestures: + return Assets.images.onboardingGestures; + } + } + + String getButtonText(BuildContext context) { + switch (this) { + case OnboardingStep.onboardingInitial: + return context.localizations.button_start; + case OnboardingStep.onboardingTipsAndTricks: + case OnboardingStep.onboardingFavourites: + return context.localizations.button_skip; + case OnboardingStep.onboardingGestures: + return context.localizations.button_lets_go; + } + } +} diff --git a/lib/ui/theme/app_colors.dart b/lib/ui/theme/app_colors.dart index 39d113e..b54f54e 100644 --- a/lib/ui/theme/app_colors.dart +++ b/lib/ui/theme/app_colors.dart @@ -82,7 +82,7 @@ class AppColors extends ColorScheme { onSecondary: onSecondary, error: error, onError: onError, - background: background, + background: _background, onBackground: onBackground, surface: surface, onSurface: onSurface, diff --git a/lib/ui/theme/app_theme.dart b/lib/ui/theme/app_theme.dart index 872519e..157a0df 100644 --- a/lib/ui/theme/app_theme.dart +++ b/lib/ui/theme/app_theme.dart @@ -17,6 +17,7 @@ class AppTheme { return ThemeData( primaryColor: _colors.primary, colorScheme: _colors, + backgroundColor: _colors.background, textTheme: _styles.getThemeData(), primaryTextTheme: _styles.getThemeData(), ); diff --git a/pubspec.yaml b/pubspec.yaml index 516edfb..5c6c570 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,7 @@ flutter: uses-material-design: true assets: - assets/ + - assets/images/ - environments/ flutter_gen: