diff --git a/.metadata b/.metadata
index aa90aa80..5d420ae5 100644
--- a/.metadata
+++ b/.metadata
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: "ba393198430278b6595976de84fe170f553cc728"
+ revision: "09de023485e95e6d1225c2baa44b8feb85e0d45f"
channel: "stable"
project_type: app
@@ -13,26 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: ba393198430278b6595976de84fe170f553cc728
- base_revision: ba393198430278b6595976de84fe170f553cc728
- - platform: android
- create_revision: ba393198430278b6595976de84fe170f553cc728
- base_revision: ba393198430278b6595976de84fe170f553cc728
+ create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f
+ base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f
- platform: ios
- create_revision: ba393198430278b6595976de84fe170f553cc728
- base_revision: ba393198430278b6595976de84fe170f553cc728
- - platform: linux
- create_revision: ba393198430278b6595976de84fe170f553cc728
- base_revision: ba393198430278b6595976de84fe170f553cc728
- - platform: macos
- create_revision: ba393198430278b6595976de84fe170f553cc728
- base_revision: ba393198430278b6595976de84fe170f553cc728
- - platform: web
- create_revision: ba393198430278b6595976de84fe170f553cc728
- base_revision: ba393198430278b6595976de84fe170f553cc728
- - platform: windows
- create_revision: ba393198430278b6595976de84fe170f553cc728
- base_revision: ba393198430278b6595976de84fe170f553cc728
+ create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f
+ base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f
# User provided section
diff --git a/assets/icons/add_card.svg b/assets/icons/add_card.svg
new file mode 100644
index 00000000..4acb17cd
--- /dev/null
+++ b/assets/icons/add_card.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/card.svg b/assets/icons/card.svg
new file mode 100644
index 00000000..38c03089
--- /dev/null
+++ b/assets/icons/card.svg
@@ -0,0 +1,5 @@
+
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index a0fd3539..7fbba8a2 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -365,7 +365,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 47;
+ CURRENT_PROJECT_VERSION = 49;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -383,7 +383,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- MARKETING_VERSION = 1.0.47;
+ MARKETING_VERSION = 1.0.49;
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -514,7 +514,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 47;
+ CURRENT_PROJECT_VERSION = 49;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -532,7 +532,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- MARKETING_VERSION = 1.0.47;
+ MARKETING_VERSION = 1.0.49;
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -551,7 +551,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 47;
+ CURRENT_PROJECT_VERSION = 49;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -569,7 +569,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- MARKETING_VERSION = 1.0.47;
+ MARKETING_VERSION = 1.0.49;
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 00000000..797d452e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart
index 1e8a78d0..527e58d7 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.dart
@@ -158,8 +158,8 @@ class AppRouter {
builder: (context, state) => UserFinanceScreen(),
),
GoRoute(
- path: '/card-settings-screen',
- name: 'cardSettingsScreen',
+ path: CardSettingsScreen.path,
+ name: CardSettingsScreen.name,
builder: (context, state) => CardSettingsScreen(),
),
GoRoute(
diff --git a/lib/core/const.dart b/lib/core/const.dart
index 4f38a54e..31c47e9c 100644
--- a/lib/core/const.dart
+++ b/lib/core/const.dart
@@ -1,5 +1,5 @@
-// String baseUrl = 'https://eatvio.ru';
-String baseUrl = 'https://dev2.eatvio.com';
+String baseUrl = 'https://eatvio.ru';
+// String baseUrl = 'https://dev2.eatvio.com';
const List durationValues = [7, 14, 21];
diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart
index 99846f1a..3a3f5342 100644
--- a/lib/core/locator_service.dart
+++ b/lib/core/locator_service.dart
@@ -49,6 +49,9 @@ import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cub
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
+import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
+import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
+import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
@@ -117,6 +120,7 @@ Future init() async {
sl.registerFactory(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
sl.registerFactory(() => AuthCubit(authRepository: sl()));
sl.registerFactory(() => ComplainingCubit(repository: sl()));
+ sl.registerFactory(() => UserCardsCubit(cardsRepository: sl()));
// Repository
sl.registerLazySingleton(() => ChatRepository(chatService: sl()));
@@ -139,6 +143,7 @@ Future init() async {
sl.registerLazySingleton(() => AboutRecipeRepository(recipeService: sl()));
sl.registerLazySingleton(() => ComplainingRepository(service: sl()));
sl.registerLazySingleton(() => AuthRepository(authService: sl()));
+ sl.registerLazySingleton(() => CardsRepository(cardsService: sl()));
// Services
sl.registerLazySingleton(() => ChatService(sl()));
@@ -162,6 +167,7 @@ Future init() async {
sl.registerLazySingleton(() => AboutRecipeService(sl()));
sl.registerLazySingleton(() => ComplainingService(sl()));
sl.registerLazySingleton(() => AuthService(sl()));
+ sl.registerLazySingleton(() => CardsService(sl()));
// External
sl.registerLazySingleton(
diff --git a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart
index 1eb1027f..312a27c3 100644
--- a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart
+++ b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart
@@ -63,7 +63,7 @@ class RecipesCubit extends Cubit {
lastPage = currentPage;
} catch (e, st) {
log(e.toString(), stackTrace: st);
- emit(state.copyWith(errorMessage: e.toString()));
+ emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
}
}
@@ -94,7 +94,7 @@ class RecipesCubit extends Cubit {
}
lastPage = currentPage;
} catch (e) {
- emit(state.copyWith(errorMessage: e.toString()));
+ emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
}
}
}
diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart
index 41c9706c..a03bd3e3 100644
--- a/lib/features/menu/presentation/pages/menu_screen.dart
+++ b/lib/features/menu/presentation/pages/menu_screen.dart
@@ -16,54 +16,32 @@ import 'package:go_router/go_router.dart';
class MenuScreen extends StatelessWidget {
final ProfileModel profileModel;
- const MenuScreen({
- super.key,
- required this.profileModel,
- });
+ const MenuScreen({super.key, required this.profileModel});
@override
Widget build(BuildContext context) {
return Scaffold(
- appBar: AppBar(
- centerTitle: false,
- title: Text('Меню'),
- ),
+ appBar: AppBar(centerTitle: false, title: Text('Меню')),
body: SingleChildScrollView(
child: Column(
children: [
- _tile(
- title: 'Настройки профиля',
- leading: ImageConstant.icnProfileSettings,
- onTap: () => GoRouter.of(context).push('/settings-screen'),
- ),
+ _tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
- _tile(
- leading: ImageConstant.icnRecipes,
- title: 'Мои рецепты',
- onTap: () => GoRouter.of(context).push('/recipe-select-screen'),
- ),
+ _tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push('/recipe-select-screen')),
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
- _tile(
- leading: 'assets/icons/diets.svg',
- title: 'Мои диеты',
- onTap: () => GoRouter.of(context).push(MyDietsScreen.path),
- ),
+ _tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
- _tile(
- leading: ImageConstant.icnPlans,
- title: 'Мои планы',
- onTap: () => GoRouter.of(context).push(MyPlansScreen.path),
- ),
+ _tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)),
_divider(context),
_tile(
leading: 'assets/icons/finance.svg',
title: 'Мои финансы',
onTap: () {
if (profileModel.roles.contains('consultant')) {
- GoRouter.of(context).pushNamed('consultantFinanceScreen');
+ GoRouter.of(context).pushNamed('userFinanceScreen');
} else {
GoRouter.of(context).pushNamed('userFinanceScreen');
}
@@ -76,17 +54,9 @@ class MenuScreen extends StatelessWidget {
// onTap: () => GoRouter.of(context).push('/calendar-screen'),
// ),
_divider(context),
- _tile(
- leading: ImageConstant.icnGlossary,
- title: 'Глоссарий',
- onTap: () => GoRouter.of(context).push('/glossary-screen'),
- ),
+ _tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
_divider(context),
- _tile(
- leading: ImageConstant.icnSupport,
- title: 'Поддержка',
- onTap: () => GoRouter.of(context).push('/support-screen'),
- ),
+ _tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
_divider(context),
_tile(
leading: ImageConstant.icnDevTools,
@@ -96,36 +66,19 @@ class MenuScreen extends StatelessWidget {
},
),
_divider(context),
- _tile(
- leading: ImageConstant.icnExit,
- title: 'Выход',
- onTap: _onTapLogout(context),
- ),
+ _tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
],
),
),
);
}
- _tile({
- required String title,
- required String leading,
- required Function() onTap,
- }) {
+ _tile({required String title, required String leading, required Function() onTap}) {
return ListTile(
onTap: onTap,
- leading: SvgPicture.asset(
- leading,
- height: 24,
- width: 24,
- colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
- ),
+ leading: SvgPicture.asset(leading, height: 24, width: 24, colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)),
title: Text(title),
- trailing: Icon(
- Icons.arrow_forward_ios_rounded,
- size: 12,
- color: Colors.black38,
- ),
+ trailing: Icon(Icons.arrow_forward_ios_rounded, size: 12, color: Colors.black38),
);
}
@@ -133,10 +86,7 @@ class MenuScreen extends StatelessWidget {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,
- decoration: BoxDecoration(
- color: Colors.black26,
- borderRadius: BorderRadius.circular(16),
- ),
+ decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
);
}
@@ -144,33 +94,32 @@ class MenuScreen extends StatelessWidget {
return () async {
showDialog(
context: context,
- builder: (context) => CupertinoAlertDialog(
- title: Text("Выход"),
- actions: [
- CupertinoDialogAction(
- onPressed: () async {
- try {
- await sl().post(
- baseUrl + '/api/profile/logout',
- );
- } catch (e) {
- } finally {
- await TokenStorage.removeTokenInStorage();
- GoRouter.of(context).go('/auth-screen', extra: true);
- }
- },
- child: Text("Выйти"),
- isDestructiveAction: true,
+ builder:
+ (context) => CupertinoAlertDialog(
+ title: Text("Выход"),
+ actions: [
+ CupertinoDialogAction(
+ onPressed: () async {
+ try {
+ await sl().post(baseUrl + '/api/profile/logout');
+ } catch (e) {
+ } finally {
+ await TokenStorage.removeTokenInStorage();
+ GoRouter.of(context).go('/auth-screen', extra: true);
+ }
+ },
+ child: Text("Выйти"),
+ isDestructiveAction: true,
+ ),
+ CupertinoDialogAction(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ child: Text("Отмена"),
+ ),
+ ],
+ content: Text("Вы уверены что хотите выйти из аккаунта?"),
),
- CupertinoDialogAction(
- onPressed: () {
- Navigator.of(context).pop();
- },
- child: Text("Отмена"),
- ),
- ],
- content: Text("Вы уверены что хотите выйти из аккаунта?"),
- ),
);
};
}
diff --git a/lib/features/my_finance/models/card_model.dart b/lib/features/my_finance/models/card_model.dart
new file mode 100644
index 00000000..c923519a
--- /dev/null
+++ b/lib/features/my_finance/models/card_model.dart
@@ -0,0 +1,23 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'card_model.freezed.dart';
+part 'card_model.g.dart';
+
+@freezed
+class CardModel with _$CardModel {
+
+ factory CardModel({
+ required int id,
+ @JsonKey(name: 'bank_pan') required String cardNumber,
+ @JsonKey(name: 'is_default', fromJson: parseBool) required bool isDefault,
+ }) = _CardModel;
+
+ factory CardModel.fromJson(Map json) => _$CardModelFromJson(json);
+}
+
+bool parseBool(json) {
+ if (json == 1 || json == true) {
+ return true;
+ }
+ return false;
+}
diff --git a/lib/features/my_finance/models/card_model.freezed.dart b/lib/features/my_finance/models/card_model.freezed.dart
new file mode 100644
index 00000000..81179841
--- /dev/null
+++ b/lib/features/my_finance/models/card_model.freezed.dart
@@ -0,0 +1,232 @@
+// 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, unnecessary_question_mark
+
+part of 'card_model.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#adding-getters-and-methods-to-our-models',
+);
+
+CardModel _$CardModelFromJson(Map json) {
+ return _CardModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$CardModel {
+ int get id => throw _privateConstructorUsedError;
+ @JsonKey(name: 'bank_pan')
+ String get cardNumber => throw _privateConstructorUsedError;
+ @JsonKey(name: 'is_default', fromJson: parseBool)
+ bool get isDefault => throw _privateConstructorUsedError;
+
+ /// Serializes this CardModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of CardModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $CardModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $CardModelCopyWith<$Res> {
+ factory $CardModelCopyWith(CardModel value, $Res Function(CardModel) then) =
+ _$CardModelCopyWithImpl<$Res, CardModel>;
+ @useResult
+ $Res call({
+ int id,
+ @JsonKey(name: 'bank_pan') String cardNumber,
+ @JsonKey(name: 'is_default', fromJson: parseBool) bool isDefault,
+ });
+}
+
+/// @nodoc
+class _$CardModelCopyWithImpl<$Res, $Val extends CardModel>
+ implements $CardModelCopyWith<$Res> {
+ _$CardModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of CardModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? cardNumber = null,
+ Object? isDefault = null,
+ }) {
+ return _then(
+ _value.copyWith(
+ id:
+ null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as int,
+ cardNumber:
+ null == cardNumber
+ ? _value.cardNumber
+ : cardNumber // ignore: cast_nullable_to_non_nullable
+ as String,
+ isDefault:
+ null == isDefault
+ ? _value.isDefault
+ : isDefault // ignore: cast_nullable_to_non_nullable
+ as bool,
+ )
+ as $Val,
+ );
+ }
+}
+
+/// @nodoc
+abstract class _$$CardModelImplCopyWith<$Res>
+ implements $CardModelCopyWith<$Res> {
+ factory _$$CardModelImplCopyWith(
+ _$CardModelImpl value,
+ $Res Function(_$CardModelImpl) then,
+ ) = __$$CardModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({
+ int id,
+ @JsonKey(name: 'bank_pan') String cardNumber,
+ @JsonKey(name: 'is_default', fromJson: parseBool) bool isDefault,
+ });
+}
+
+/// @nodoc
+class __$$CardModelImplCopyWithImpl<$Res>
+ extends _$CardModelCopyWithImpl<$Res, _$CardModelImpl>
+ implements _$$CardModelImplCopyWith<$Res> {
+ __$$CardModelImplCopyWithImpl(
+ _$CardModelImpl _value,
+ $Res Function(_$CardModelImpl) _then,
+ ) : super(_value, _then);
+
+ /// Create a copy of CardModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? cardNumber = null,
+ Object? isDefault = null,
+ }) {
+ return _then(
+ _$CardModelImpl(
+ id:
+ null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as int,
+ cardNumber:
+ null == cardNumber
+ ? _value.cardNumber
+ : cardNumber // ignore: cast_nullable_to_non_nullable
+ as String,
+ isDefault:
+ null == isDefault
+ ? _value.isDefault
+ : isDefault // ignore: cast_nullable_to_non_nullable
+ as bool,
+ ),
+ );
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$CardModelImpl implements _CardModel {
+ _$CardModelImpl({
+ required this.id,
+ @JsonKey(name: 'bank_pan') required this.cardNumber,
+ @JsonKey(name: 'is_default', fromJson: parseBool) required this.isDefault,
+ });
+
+ factory _$CardModelImpl.fromJson(Map json) =>
+ _$$CardModelImplFromJson(json);
+
+ @override
+ final int id;
+ @override
+ @JsonKey(name: 'bank_pan')
+ final String cardNumber;
+ @override
+ @JsonKey(name: 'is_default', fromJson: parseBool)
+ final bool isDefault;
+
+ @override
+ String toString() {
+ return 'CardModel(id: $id, cardNumber: $cardNumber, isDefault: $isDefault)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$CardModelImpl &&
+ (identical(other.id, id) || other.id == id) &&
+ (identical(other.cardNumber, cardNumber) ||
+ other.cardNumber == cardNumber) &&
+ (identical(other.isDefault, isDefault) ||
+ other.isDefault == isDefault));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, id, cardNumber, isDefault);
+
+ /// Create a copy of CardModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$CardModelImplCopyWith<_$CardModelImpl> get copyWith =>
+ __$$CardModelImplCopyWithImpl<_$CardModelImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$CardModelImplToJson(this);
+ }
+}
+
+abstract class _CardModel implements CardModel {
+ factory _CardModel({
+ required final int id,
+ @JsonKey(name: 'bank_pan') required final String cardNumber,
+ @JsonKey(name: 'is_default', fromJson: parseBool)
+ required final bool isDefault,
+ }) = _$CardModelImpl;
+
+ factory _CardModel.fromJson(Map json) =
+ _$CardModelImpl.fromJson;
+
+ @override
+ int get id;
+ @override
+ @JsonKey(name: 'bank_pan')
+ String get cardNumber;
+ @override
+ @JsonKey(name: 'is_default', fromJson: parseBool)
+ bool get isDefault;
+
+ /// Create a copy of CardModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$CardModelImplCopyWith<_$CardModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/my_finance/models/card_model.g.dart b/lib/features/my_finance/models/card_model.g.dart
new file mode 100644
index 00000000..30fcc614
--- /dev/null
+++ b/lib/features/my_finance/models/card_model.g.dart
@@ -0,0 +1,21 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'card_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$CardModelImpl _$$CardModelImplFromJson(Map json) =>
+ _$CardModelImpl(
+ id: (json['id'] as num).toInt(),
+ cardNumber: json['bank_pan'] as String,
+ isDefault: parseBool(json['is_default']),
+ );
+
+Map _$$CardModelImplToJson(_$CardModelImpl instance) =>
+ {
+ 'id': instance.id,
+ 'bank_pan': instance.cardNumber,
+ 'is_default': instance.isDefault,
+ };
diff --git a/lib/features/my_finance/models/payment_url_model.dart b/lib/features/my_finance/models/payment_url_model.dart
new file mode 100644
index 00000000..cad57efc
--- /dev/null
+++ b/lib/features/my_finance/models/payment_url_model.dart
@@ -0,0 +1,14 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'payment_url_model.freezed.dart';
+part 'payment_url_model.g.dart';
+
+@freezed
+class PaymentUrlModel with _$PaymentUrlModel {
+
+ factory PaymentUrlModel({
+ required String paymentUrl,
+ }) = _PaymentUrlModel;
+
+ factory PaymentUrlModel.fromJson(Map json) => _$PaymentUrlModelFromJson(json);
+}
diff --git a/lib/features/my_finance/models/payment_url_model.freezed.dart b/lib/features/my_finance/models/payment_url_model.freezed.dart
new file mode 100644
index 00000000..2200800c
--- /dev/null
+++ b/lib/features/my_finance/models/payment_url_model.freezed.dart
@@ -0,0 +1,174 @@
+// 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, unnecessary_question_mark
+
+part of 'payment_url_model.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#adding-getters-and-methods-to-our-models',
+);
+
+PaymentUrlModel _$PaymentUrlModelFromJson(Map json) {
+ return _PaymentUrlModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$PaymentUrlModel {
+ String get paymentUrl => throw _privateConstructorUsedError;
+
+ /// Serializes this PaymentUrlModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of PaymentUrlModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $PaymentUrlModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $PaymentUrlModelCopyWith<$Res> {
+ factory $PaymentUrlModelCopyWith(
+ PaymentUrlModel value,
+ $Res Function(PaymentUrlModel) then,
+ ) = _$PaymentUrlModelCopyWithImpl<$Res, PaymentUrlModel>;
+ @useResult
+ $Res call({String paymentUrl});
+}
+
+/// @nodoc
+class _$PaymentUrlModelCopyWithImpl<$Res, $Val extends PaymentUrlModel>
+ implements $PaymentUrlModelCopyWith<$Res> {
+ _$PaymentUrlModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of PaymentUrlModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({Object? paymentUrl = null}) {
+ return _then(
+ _value.copyWith(
+ paymentUrl:
+ null == paymentUrl
+ ? _value.paymentUrl
+ : paymentUrl // ignore: cast_nullable_to_non_nullable
+ as String,
+ )
+ as $Val,
+ );
+ }
+}
+
+/// @nodoc
+abstract class _$$PaymentUrlModelImplCopyWith<$Res>
+ implements $PaymentUrlModelCopyWith<$Res> {
+ factory _$$PaymentUrlModelImplCopyWith(
+ _$PaymentUrlModelImpl value,
+ $Res Function(_$PaymentUrlModelImpl) then,
+ ) = __$$PaymentUrlModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({String paymentUrl});
+}
+
+/// @nodoc
+class __$$PaymentUrlModelImplCopyWithImpl<$Res>
+ extends _$PaymentUrlModelCopyWithImpl<$Res, _$PaymentUrlModelImpl>
+ implements _$$PaymentUrlModelImplCopyWith<$Res> {
+ __$$PaymentUrlModelImplCopyWithImpl(
+ _$PaymentUrlModelImpl _value,
+ $Res Function(_$PaymentUrlModelImpl) _then,
+ ) : super(_value, _then);
+
+ /// Create a copy of PaymentUrlModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({Object? paymentUrl = null}) {
+ return _then(
+ _$PaymentUrlModelImpl(
+ paymentUrl:
+ null == paymentUrl
+ ? _value.paymentUrl
+ : paymentUrl // ignore: cast_nullable_to_non_nullable
+ as String,
+ ),
+ );
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$PaymentUrlModelImpl implements _PaymentUrlModel {
+ _$PaymentUrlModelImpl({required this.paymentUrl});
+
+ factory _$PaymentUrlModelImpl.fromJson(Map json) =>
+ _$$PaymentUrlModelImplFromJson(json);
+
+ @override
+ final String paymentUrl;
+
+ @override
+ String toString() {
+ return 'PaymentUrlModel(paymentUrl: $paymentUrl)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$PaymentUrlModelImpl &&
+ (identical(other.paymentUrl, paymentUrl) ||
+ other.paymentUrl == paymentUrl));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, paymentUrl);
+
+ /// Create a copy of PaymentUrlModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$PaymentUrlModelImplCopyWith<_$PaymentUrlModelImpl> get copyWith =>
+ __$$PaymentUrlModelImplCopyWithImpl<_$PaymentUrlModelImpl>(
+ this,
+ _$identity,
+ );
+
+ @override
+ Map toJson() {
+ return _$$PaymentUrlModelImplToJson(this);
+ }
+}
+
+abstract class _PaymentUrlModel implements PaymentUrlModel {
+ factory _PaymentUrlModel({required final String paymentUrl}) =
+ _$PaymentUrlModelImpl;
+
+ factory _PaymentUrlModel.fromJson(Map json) =
+ _$PaymentUrlModelImpl.fromJson;
+
+ @override
+ String get paymentUrl;
+
+ /// Create a copy of PaymentUrlModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$PaymentUrlModelImplCopyWith<_$PaymentUrlModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/my_finance/models/payment_url_model.g.dart b/lib/features/my_finance/models/payment_url_model.g.dart
new file mode 100644
index 00000000..9bef3b73
--- /dev/null
+++ b/lib/features/my_finance/models/payment_url_model.g.dart
@@ -0,0 +1,15 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'payment_url_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$PaymentUrlModelImpl _$$PaymentUrlModelImplFromJson(
+ Map json,
+) => _$PaymentUrlModelImpl(paymentUrl: json['paymentUrl'] as String);
+
+Map _$$PaymentUrlModelImplToJson(
+ _$PaymentUrlModelImpl instance,
+) => {'paymentUrl': instance.paymentUrl};
diff --git a/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart
new file mode 100644
index 00000000..a2c22389
--- /dev/null
+++ b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart
@@ -0,0 +1,66 @@
+import 'dart:developer';
+
+import 'package:easy_debounce/easy_debounce.dart';
+import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart';
+import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart';
+import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class UserCardsCubit extends Cubit {
+ final CardsRepository cardsRepository;
+ UserCardsCubit({required this.cardsRepository}) : super(UserCardsState());
+
+ Future addCard() async {
+ try {
+ return await cardsRepository.addCard();
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
+ }
+ return null;
+ }
+
+ Future getCards() async {
+ try {
+ if (isClosed) return;
+ final cards = await cardsRepository.getCards();
+ emit(state.copyWith(cards: cards));
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
+ }
+ }
+
+ Future markAsDefault({required int id}) async {
+ final oldCards = [...state.cards];
+ if (isClosed) return;
+ final cards =
+ oldCards.map((card) {
+ if (card.id == id) {
+ return card.copyWith(isDefault: true);
+ }
+ return card.copyWith(isDefault: false);
+ }).toList();
+ emit(state.copyWith(cards: cards));
+
+ EasyDebounce.debounce('markAsDefault', Duration(milliseconds: id), () async {
+ try {
+ await cardsRepository.markAsDefault(id: id);
+ } catch (e, st) {
+ emit(state.copyWith(cards: oldCards));
+ log(e.toString(), stackTrace: st);
+ }
+ });
+ }
+
+ Future deleteCard({required int id}) async {
+ final oldCards = [...state.cards];
+ try {
+ if (isClosed) return;
+ final cards = oldCards.where((card) => card.id != id).toList();
+ emit(state.copyWith(cards: cards));
+ await cardsRepository.deleteCard(id: id);
+ } catch (e, st) {
+ emit(state.copyWith(cards: oldCards));
+ log(e.toString(), stackTrace: st);
+ }
+ }
+}
diff --git a/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart
new file mode 100644
index 00000000..d9fcd376
--- /dev/null
+++ b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart
@@ -0,0 +1,14 @@
+import 'package:eatvio_app/features/my_finance/models/card_model.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'user_cards_state.freezed.dart';
+
+@freezed
+class UserCardsState with _$UserCardsState {
+
+ factory UserCardsState({
+ @Default([]) List cards,
+ @Default(false) bool isLoading,
+ }) = _UserCardsState;
+
+}
\ No newline at end of file
diff --git a/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.freezed.dart b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.freezed.dart
new file mode 100644
index 00000000..6af8348f
--- /dev/null
+++ b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.freezed.dart
@@ -0,0 +1,186 @@
+// 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, unnecessary_question_mark
+
+part of 'user_cards_state.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#adding-getters-and-methods-to-our-models',
+);
+
+/// @nodoc
+mixin _$UserCardsState {
+ List get cards => throw _privateConstructorUsedError;
+ bool get isLoading => throw _privateConstructorUsedError;
+
+ /// Create a copy of UserCardsState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $UserCardsStateCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $UserCardsStateCopyWith<$Res> {
+ factory $UserCardsStateCopyWith(
+ UserCardsState value,
+ $Res Function(UserCardsState) then,
+ ) = _$UserCardsStateCopyWithImpl<$Res, UserCardsState>;
+ @useResult
+ $Res call({List cards, bool isLoading});
+}
+
+/// @nodoc
+class _$UserCardsStateCopyWithImpl<$Res, $Val extends UserCardsState>
+ implements $UserCardsStateCopyWith<$Res> {
+ _$UserCardsStateCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of UserCardsState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({Object? cards = null, Object? isLoading = null}) {
+ return _then(
+ _value.copyWith(
+ cards:
+ null == cards
+ ? _value.cards
+ : cards // ignore: cast_nullable_to_non_nullable
+ as List,
+ isLoading:
+ null == isLoading
+ ? _value.isLoading
+ : isLoading // ignore: cast_nullable_to_non_nullable
+ as bool,
+ )
+ as $Val,
+ );
+ }
+}
+
+/// @nodoc
+abstract class _$$UserCardsStateImplCopyWith<$Res>
+ implements $UserCardsStateCopyWith<$Res> {
+ factory _$$UserCardsStateImplCopyWith(
+ _$UserCardsStateImpl value,
+ $Res Function(_$UserCardsStateImpl) then,
+ ) = __$$UserCardsStateImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({List cards, bool isLoading});
+}
+
+/// @nodoc
+class __$$UserCardsStateImplCopyWithImpl<$Res>
+ extends _$UserCardsStateCopyWithImpl<$Res, _$UserCardsStateImpl>
+ implements _$$UserCardsStateImplCopyWith<$Res> {
+ __$$UserCardsStateImplCopyWithImpl(
+ _$UserCardsStateImpl _value,
+ $Res Function(_$UserCardsStateImpl) _then,
+ ) : super(_value, _then);
+
+ /// Create a copy of UserCardsState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({Object? cards = null, Object? isLoading = null}) {
+ return _then(
+ _$UserCardsStateImpl(
+ cards:
+ null == cards
+ ? _value._cards
+ : cards // ignore: cast_nullable_to_non_nullable
+ as List,
+ isLoading:
+ null == isLoading
+ ? _value.isLoading
+ : isLoading // ignore: cast_nullable_to_non_nullable
+ as bool,
+ ),
+ );
+ }
+}
+
+/// @nodoc
+
+class _$UserCardsStateImpl implements _UserCardsState {
+ _$UserCardsStateImpl({
+ final List cards = const [],
+ this.isLoading = false,
+ }) : _cards = cards;
+
+ final List _cards;
+ @override
+ @JsonKey()
+ List get cards {
+ if (_cards is EqualUnmodifiableListView) return _cards;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_cards);
+ }
+
+ @override
+ @JsonKey()
+ final bool isLoading;
+
+ @override
+ String toString() {
+ return 'UserCardsState(cards: $cards, isLoading: $isLoading)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$UserCardsStateImpl &&
+ const DeepCollectionEquality().equals(other._cards, _cards) &&
+ (identical(other.isLoading, isLoading) ||
+ other.isLoading == isLoading));
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ const DeepCollectionEquality().hash(_cards),
+ isLoading,
+ );
+
+ /// Create a copy of UserCardsState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$UserCardsStateImplCopyWith<_$UserCardsStateImpl> get copyWith =>
+ __$$UserCardsStateImplCopyWithImpl<_$UserCardsStateImpl>(
+ this,
+ _$identity,
+ );
+}
+
+abstract class _UserCardsState implements UserCardsState {
+ factory _UserCardsState({final List cards, final bool isLoading}) =
+ _$UserCardsStateImpl;
+
+ @override
+ List get cards;
+ @override
+ bool get isLoading;
+
+ /// Create a copy of UserCardsState
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$UserCardsStateImplCopyWith<_$UserCardsStateImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/my_finance/presentation/pages/card_settings.dart b/lib/features/my_finance/presentation/pages/card_settings.dart
index abba517d..caa6a6e0 100644
--- a/lib/features/my_finance/presentation/pages/card_settings.dart
+++ b/lib/features/my_finance/presentation/pages/card_settings.dart
@@ -1,21 +1,35 @@
+import 'package:eatvio_app/core/locator_service.dart';
+import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
+import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart';
+import 'package:eatvio_app/features/my_finance/presentation/widgets/card_widget.dart';
+import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_svg/flutter_svg.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:collection/collection.dart';
class CardSettingsScreen extends StatelessWidget {
+ static const path = '/card-settings-screen';
+ static const name = 'cardSettingsScreen';
const CardSettingsScreen({super.key});
@override
Widget build(BuildContext context) {
+ return BlocProvider(create: (context) => sl()..getCards(), child: CardsSettingsContent());
+ }
+}
+
+class CardsSettingsContent extends StatelessWidget {
+ CardsSettingsContent({super.key});
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
return Scaffold(
backgroundColor: Color(0xFFF0F2F5),
appBar: AppBar(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.vertical(
- bottom: Radius.circular(12),
- ),
- ),
- title: Text('Мои карты'),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
+ title: Text(s.my_cards),
centerTitle: true,
),
body: Column(
@@ -30,41 +44,42 @@ class CardSettingsScreen extends StatelessWidget {
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Text(
- 'Привязанные карты',
- style: TextStyle(
- color: Color(0xFF323F4B),
- fontSize: 18,
- fontWeight: FontWeight.w600,
- height: 1,
- ),
- ),
+ child: Text('Привязанные карты', style: TextStyle(color: Color(0xFF323F4B), fontSize: 18, fontWeight: FontWeight.w600, height: 1)),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Text(
- 'Выплату будут производиться\nна карту ** 0278',
- style: TextStyle(
- color: Color(0xFF323F4B),
- fontSize: 16,
- fontWeight: FontWeight.w500,
- height: 1,
- ),
+ child: BlocBuilder(
+ builder: (context, state) {
+ final defaultCardNumber = state.cards.firstWhereOrNull((element) => element.isDefault)?.cardNumber;
+ if (defaultCardNumber != null) {
+ return Text(
+ 'Выплату будут производиться\nна карту ${defaultCardNumber.substring(defaultCardNumber.length - 7)}',
+ style: TextStyle(color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500, height: 1),
+ );
+ }
+ return Text(s.no_default_cards, style: TextStyles.textMMedium.copyWith(color: appTheme.black));
+ },
),
),
const SizedBox(height: 16),
SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
- child: Row(
- children: [
- _card(true),
- const SizedBox(width: 8),
- _card(),
- const SizedBox(width: 8),
- _card(),
- ],
+ child: BlocBuilder(
+ builder: (context, state) {
+ if (state.isLoading) {}
+ return Row(
+ spacing: 12,
+ children: List.generate(state.cards.length + 1, (index) {
+ if (index < state.cards.length) {
+ final cardsNumber = state.cards[index].cardNumber.substring(state.cards[index].cardNumber.length - 7);
+ return CardWidget(isSelected: state.cards[index].isDefault, cardId: state.cards[index].id, cardNumber: cardsNumber);
+ }
+ return CardWidget(enabledAddCard: true);
+ }),
+ );
+ },
),
),
const SizedBox(height: 16),
@@ -75,40 +90,4 @@ class CardSettingsScreen extends StatelessWidget {
),
);
}
-
- _card([bool isSelected = false]) {
- return Container(
- padding: EdgeInsets.all(4),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(8),
- border: Border.all(
- color: isSelected ? appTheme.blueA400 : Color(0xFFF1F2F6),
- width: 2,
- ),
- ),
- child: Container(
- width: 148,
- padding: EdgeInsets.all(10),
- decoration: BoxDecoration(
- color: Color(0xFFF1F2F6),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Column(
- children: [
- SvgPicture.asset('assets/icons/mir.svg'),
- const SizedBox(height: 24),
- Text(
- '** 0278',
- style: TextStyle(
- color: Color(0xFF323F4B),
- fontSize: 14,
- height: 1,
- fontWeight: FontWeight.w600,
- ),
- ),
- ],
- ),
- ),
- );
- }
}
diff --git a/lib/features/my_finance/presentation/pages/user_finance_screen.dart b/lib/features/my_finance/presentation/pages/user_finance_screen.dart
index 2ac71c30..5bca9c4d 100644
--- a/lib/features/my_finance/presentation/pages/user_finance_screen.dart
+++ b/lib/features/my_finance/presentation/pages/user_finance_screen.dart
@@ -2,6 +2,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
import 'package:eatvio_app/features/my_finance/entities/consultant_finance_entity.dart';
+import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
@@ -13,11 +14,7 @@ class UserFinanceScreen extends StatelessWidget {
return Scaffold(
backgroundColor: Color(0xFFF0F2F5),
appBar: AppBar(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadiusDirectional.vertical(
- bottom: Radius.circular(12),
- ),
- ),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadiusDirectional.vertical(bottom: Radius.circular(12))),
title: Text('История операций'),
centerTitle: true,
),
@@ -28,26 +25,16 @@ class UserFinanceScreen extends StatelessWidget {
const SizedBox(height: 8),
InkWell(
onTap: () {
- GoRouter.of(context).pushNamed('cardSettingsScreen');
+ GoRouter.of(context).pushNamed(CardSettingsScreen.name);
},
borderRadius: BorderRadius.circular(20),
child: Ink(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20),
- color: appTheme.blueA400,
- ),
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
width: MediaQuery.of(context).size.width - 32,
child: Center(
child: Padding(
padding: const EdgeInsets.all(16),
- child: Text(
- 'Управление картами',
- style: TextStyle(
- color: Colors.white,
- fontSize: 16,
- fontWeight: FontWeight.w600,
- ),
- ),
+ child: Text('Управление картами', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)),
),
),
),
@@ -62,108 +49,73 @@ class UserFinanceScreen extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
- CustomDropDownButton(
- items: [],
- onChanged: (value) {},
- ),
+ CustomDropDownButton(items: [], onChanged: (value) {}),
const SizedBox(height: 12),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
- itemBuilder: (context, index) => Material(
- color: Color(0xFFF6F6F6),
- borderRadius: BorderRadius.circular(20),
- child: Padding(
- padding: const EdgeInsets.all(12),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ itemBuilder:
+ (context, index) => Material(
+ color: Color(0xFFF6F6F6),
+ borderRadius: BorderRadius.circular(20),
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(
- testFinanceEntity.consultationsList[index].id,
- style: TextStyle(
- color: Color(0xFF323F4B),
- height: 1,
- fontWeight: FontWeight.w500,
- ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ testFinanceEntity.consultationsList[index].id,
+ style: TextStyle(color: Color(0xFF323F4B), height: 1, fontWeight: FontWeight.w500),
+ ),
+ Text(
+ testFinanceEntity.consultationsList[index].date.format('dd.MM.yyy', 'RU'),
+ style: TextStyle(color: Color(0xFF6D6F71), height: 1, fontWeight: FontWeight.w500),
+ ),
+ ],
),
+ const SizedBox(height: 8),
Text(
- testFinanceEntity.consultationsList[index].date.format('dd.MM.yyy', 'RU'),
- style: TextStyle(
- color: Color(0xFF6D6F71),
- height: 1,
- fontWeight: FontWeight.w500,
- ),
+ testFinanceEntity.consultationsList[index].title,
+ style: TextStyle(color: appTheme.blueA400, height: 1, fontSize: 18, fontWeight: FontWeight.w600),
),
- ],
- ),
- const SizedBox(height: 8),
- Text(
- testFinanceEntity.consultationsList[index].title,
- style: TextStyle(
- color: appTheme.blueA400,
- height: 1,
- fontSize: 18,
- fontWeight: FontWeight.w600,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- 'Покупатель: ' + testFinanceEntity.consultationsList[index].clientName,
- style: TextStyle(
- color: Color(0xFF323F4B),
- height: 1,
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- testFinanceEntity.consultationsList[index].planDuration.toString() + ' дней',
- style: TextStyle(
- color: Color(0xFF323F4B),
- height: 1,
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- ),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
+ const SizedBox(height: 8),
Text(
- testFinanceEntity.consultationsList[index].price.toString() + ' ₽',
- style: TextStyle(
- color: Color(0xFF323F4B),
- height: 1,
- fontSize: 18,
- fontWeight: FontWeight.w600,
- ),
+ 'Покупатель: ' + testFinanceEntity.consultationsList[index].clientName,
+ style: TextStyle(color: Color(0xFF323F4B), height: 1, fontSize: 14, fontWeight: FontWeight.w500),
),
- Material(
- color: Color(0xFF0BB450).withAlpha(11),
- borderRadius: BorderRadius.circular(8),
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
- child: Text(
- testFinanceEntity.consultationsList[index].status.name,
- style: TextStyle(
- fontSize: 14,
- fontWeight: FontWeight.w400,
- color: Color(0xFF0BB450),
- height: 1,
+ const SizedBox(height: 8),
+ Text(
+ testFinanceEntity.consultationsList[index].planDuration.toString() + ' дней',
+ style: TextStyle(color: Color(0xFF323F4B), height: 1, fontSize: 14, fontWeight: FontWeight.w500),
+ ),
+ const SizedBox(height: 8),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ testFinanceEntity.consultationsList[index].price.toString() + ' ₽',
+ style: TextStyle(color: Color(0xFF323F4B), height: 1, fontSize: 18, fontWeight: FontWeight.w600),
+ ),
+ Material(
+ color: Color(0xFF0BB450).withAlpha(11),
+ borderRadius: BorderRadius.circular(8),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
+ child: Text(
+ testFinanceEntity.consultationsList[index].status.name,
+ style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xFF0BB450), height: 1),
+ ),
),
),
- ),
+ ],
),
],
),
- ],
+ ),
),
- ),
- ),
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemCount: testFinanceEntity.consultationsList.length,
),
diff --git a/lib/features/my_finance/presentation/widgets/card_widget.dart b/lib/features/my_finance/presentation/widgets/card_widget.dart
new file mode 100644
index 00000000..22789c21
--- /dev/null
+++ b/lib/features/my_finance/presentation/widgets/card_widget.dart
@@ -0,0 +1,68 @@
+import 'package:eatvio_app/core/theme/custom_text_style.dart';
+import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
+import 'package:eatvio_app/generated/l10n.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class CardWidget extends StatelessWidget {
+ const CardWidget({super.key, this.isSelected = false, this.enabledAddCard = false, this.cardId = 0, this.cardNumber = ''});
+ final int cardId;
+ final bool isSelected;
+ final bool enabledAddCard;
+ final String cardNumber;
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return GestureDetector(
+ onTap: enabledAddCard
+ ? () async {
+ final addCardUrl = await context.read().addCard();
+
+ if (addCardUrl != null) {
+ final uri = Uri.parse(addCardUrl.paymentUrl);
+ if (await canLaunchUrl(uri)) {
+ await launchUrl(uri, mode: LaunchMode.externalApplication);
+ }
+ }
+ }
+ : () {
+ context.read().markAsDefault(id: cardId);
+ },
+ child: Container(
+ padding: EdgeInsets.all(4),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: isSelected ? appTheme.blueA400 : Color(0xFFF1F2F6), width: 2),
+ ),
+ child: enabledAddCard
+ ? Container(
+ width: 148,
+ padding: EdgeInsets.all(10),
+ decoration: BoxDecoration(color: Color(0xFFF1F2F6), borderRadius: BorderRadius.circular(4)),
+ child: Column(
+ children: [
+ SvgPicture.asset('assets/icons/add_card.svg'),
+ const SizedBox(height: 24),
+ Text(s.new_card, style: TextStyles.textSSemibold.copyWith(color: appTheme.black)),
+ ],
+ ),
+ )
+ : Container(
+ width: 148,
+ padding: EdgeInsets.all(10),
+ decoration: BoxDecoration(color: Color(0xFFF1F2F6), borderRadius: BorderRadius.circular(4)),
+ child: Column(
+ children: [
+ SvgPicture.asset('assets/icons/card.svg'),
+ const SizedBox(height: 24),
+ Text(cardNumber, style: TextStyles.textSSemibold.copyWith(color: appTheme.black)),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/my_finance/repositories/cards_repository.dart b/lib/features/my_finance/repositories/cards_repository.dart
new file mode 100644
index 00000000..221a3376
--- /dev/null
+++ b/lib/features/my_finance/repositories/cards_repository.dart
@@ -0,0 +1,39 @@
+import 'package:eatvio_app/features/my_finance/models/card_model.dart';
+import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart';
+import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
+
+class CardsRepository {
+ final CardsService cardsService;
+ CardsRepository({required this.cardsService});
+ Future addCard() async {
+ try {
+ return await cardsService.addCard();
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future> getCards() async {
+ try {
+ return await cardsService.getCards();
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future markAsDefault({required int id}) async {
+ try {
+ await cardsService.markAsDefault(id: id);
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future deleteCard({required int id}) async {
+ try {
+ await cardsService.deleteCard(id: id);
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/lib/features/my_finance/services/cards_service.dart b/lib/features/my_finance/services/cards_service.dart
new file mode 100644
index 00000000..b42a8ed7
--- /dev/null
+++ b/lib/features/my_finance/services/cards_service.dart
@@ -0,0 +1,28 @@
+import 'package:dio/dio.dart';
+import 'package:eatvio_app/features/my_finance/models/card_model.dart';
+import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart';
+import 'package:retrofit/retrofit.dart';
+
+part 'cards_service.g.dart';
+
+@RestApi()
+abstract class CardsService {
+ factory CardsService(Dio dio, {String baseUrl}) = _CardsService;
+
+ @GET('/api/finances/cards')
+ Future> getCards();
+
+ @POST('/api/finances/cards/add')
+ Future addCard();
+
+ @POST('/api/finances/cards/{id}/default')
+ Future markAsDefault({
+ @Path('id') required int id,
+ });
+
+ @POST('/api/finances/cards/{id}/remove')
+ Future deleteCard({
+ @Path('id') required int id,
+ });
+}
+
diff --git a/lib/features/my_finance/services/cards_service.g.dart b/lib/features/my_finance/services/cards_service.g.dart
new file mode 100644
index 00000000..62b8d7ca
--- /dev/null
+++ b/lib/features/my_finance/services/cards_service.g.dart
@@ -0,0 +1,141 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'cards_service.dart';
+
+// **************************************************************************
+// RetrofitGenerator
+// **************************************************************************
+
+// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
+
+class _CardsService implements CardsService {
+ _CardsService(this._dio, {this.baseUrl, this.errorLogger});
+
+ final Dio _dio;
+
+ String? baseUrl;
+
+ final ParseErrorLogger? errorLogger;
+
+ @override
+ Future> getCards() async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType>(
+ Options(method: 'GET', headers: _headers, extra: _extra)
+ .compose(
+ _dio.options,
+ '/api/finances/cards',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+ );
+ final _result = await _dio.fetch>(_options);
+ late List _value;
+ try {
+ _value =
+ _result.data!
+ .map((dynamic i) => CardModel.fromJson(i as Map))
+ .toList();
+ } on Object catch (e, s) {
+ errorLogger?.logError(e, s, _options);
+ rethrow;
+ }
+ return _value;
+ }
+
+ @override
+ Future addCard() async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(
+ Options(method: 'POST', headers: _headers, extra: _extra)
+ .compose(
+ _dio.options,
+ '/api/finances/cards/add',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+ );
+ final _result = await _dio.fetch