Skip to content

Commit

Permalink
feat: adds Product selection when using ticket (#567)
Browse files Browse the repository at this point in the history
  • Loading branch information
marfavi authored Jan 28, 2024
1 parent 242f176 commit 65c1fb7
Show file tree
Hide file tree
Showing 95 changed files with 1,618 additions and 832 deletions.
2 changes: 1 addition & 1 deletion lib/core/firebase_analytics_event_logging.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:coffeecard/features/product/domain/entities/product.dart';
import 'package:coffeecard/features/product/product_model.dart';
import 'package:coffeecard/features/purchase/domain/entities/payment.dart';
import 'package:firebase_analytics/firebase_analytics.dart';

Expand Down
10 changes: 9 additions & 1 deletion lib/core/network/network_request_executor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import 'package:logger/logger.dart';
part 'network_request_executor_mapping.dart';

typedef _NetworkRequest<BodyType> = Future<Response<BodyType>> Function();
typedef _ExecutorResult<R> = Future<Either<NetworkFailure, R>>;
typedef _ExecutorResult<R> = Future<Either<Failure, R>>;
typedef _ExecutorTaskEither<R> = TaskEither<Failure, R>;

class NetworkRequestExecutor {
final Logger logger;
Expand Down Expand Up @@ -43,6 +44,13 @@ class NetworkRequestExecutor {
}
}

/// Executes a network request inside a [TaskEither].
///
/// See [execute] for more information.
_ExecutorTaskEither<Body> executeAsTask<Body>(_NetworkRequest<Body> request) {
return TaskEither(() => execute(request));
}

/// Executes the network [request] and returns the result as an [Either].
///
/// If the request fails, a [NetworkFailure] is returned in a [Left].
Expand Down
2 changes: 1 addition & 1 deletion lib/core/widgets/components/barista_perks_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:coffeecard/core/strings.dart';
import 'package:coffeecard/core/widgets/components/helpers/grid.dart';
import 'package:coffeecard/core/widgets/components/section_title.dart';
import 'package:coffeecard/core/widgets/components/user_role_indicator.dart';
import 'package:coffeecard/features/product/domain/entities/purchasable_products.dart';
import 'package:coffeecard/features/product/purchasable_products.dart';
import 'package:coffeecard/features/ticket/presentation/widgets/perk_card.dart';
import 'package:coffeecard/features/user/domain/entities/role.dart';
import 'package:flutter/material.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
import 'package:coffeecard/core/strings.dart';
import 'package:coffeecard/core/styles/app_colors.dart';
import 'package:coffeecard/core/styles/app_text_styles.dart';
import 'package:coffeecard/core/widgets/components/card.dart';
import 'package:coffeecard/core/widgets/components/helpers/shimmer_builder.dart';
import 'package:coffeecard/features/ticket/domain/entities/ticket.dart';
import 'package:coffeecard/features/ticket/presentation/widgets/swipe_ticket_confirm.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';

class CoffeeCard extends StatelessWidget {
final String title;
final int amountOwned;
final int productId;
part 'tickets_card_placeholder.dart';

/// A card representing a group of tickets owned by the user.
///
/// See also [NoTicketsPlaceholder], which is used when the user has no tickets.
class TicketsCard extends StatelessWidget {
/// Creates a [TicketsCard] with the given [ticket].
const TicketsCard(this.ticket);

/// A shimmering placeholder for a [TicketsCard].
const TicketsCard.loadingPlaceholder() : ticket = const Ticket.empty();

final Ticket ticket;

const CoffeeCard({
required this.title,
required this.amountOwned,
required this.productId,
});
bool get isLoadingPlaceholder => ticket == const Ticket.empty();

@override
Widget build(BuildContext context) {
return CardBase(
color: AppColors.ticket,
top: CardTitle(
title: Text(title, style: AppTextStyle.ownedTicket),
),
bottom: CardBottomRow(
left: _TicketDots(amountOwned: amountOwned),
right: _TicketAmountText(amountOwned: amountOwned),
),
gap: 36,
onTap: (context) {
final _ = showSwipeTicketConfirm(
context: context,
productName: title,
amountOwned: amountOwned,
productId: productId,
return ShimmerBuilder(
showShimmer: isLoadingPlaceholder,
builder: (context, colorIfShimmer) {
return CardBase(
color: isLoadingPlaceholder ? colorIfShimmer : AppColors.ticket,
top: CardTitle(
title: Text(ticket.product.name, style: AppTextStyle.ownedTicket),
),
bottom: CardBottomRow(
left: _TicketDots(amountOwned: ticket.amountLeft),
right: _TicketAmountText(amountOwned: ticket.amountLeft),
),
gap: 36,
onTap: (context) {
final _ = showSwipeTicketConfirm(context: context, ticket: ticket);
},
);
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import 'package:coffeecard/core/strings.dart';
import 'package:coffeecard/core/styles/app_colors.dart';
import 'package:coffeecard/core/styles/app_text_styles.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
part of 'tickets_card.dart';

class CoffeeCardPlaceholder extends StatelessWidget {
const CoffeeCardPlaceholder();
/// A widget that shows instead of a [TicketsCard] when the user has no tickets.
class NoTicketsPlaceholder extends StatelessWidget {
const NoTicketsPlaceholder();

@override
Widget build(BuildContext context) {
Expand Down
2 changes: 1 addition & 1 deletion lib/core/widgets/pages/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:coffeecard/core/widgets/routers/app_flow.dart';
import 'package:coffeecard/features/leaderboard/presentation/cubit/leaderboard_cubit.dart';
import 'package:coffeecard/features/leaderboard/presentation/pages/leaderboard_page.dart';
import 'package:coffeecard/features/opening_hours/presentation/cubit/opening_hours_cubit.dart';
import 'package:coffeecard/features/product/domain/entities/purchasable_products.dart';
import 'package:coffeecard/features/product/purchasable_products.dart';
import 'package:coffeecard/features/receipt/presentation/cubit/receipt_cubit.dart';
import 'package:coffeecard/features/receipt/presentation/pages/receipts_page.dart';
import 'package:coffeecard/features/settings/presentation/pages/settings_page.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import 'package:coffeecard/features/authentication/data/datasources/authentication_local_data_source.dart';
import 'package:hive_flutter/hive_flutter.dart';

class ClearAuthenticatedUser {
final AuthenticationLocalDataSource dataSource;

ClearAuthenticatedUser({required this.dataSource});

Future<void> call() async {
// Clear last used menu item
final box = await Hive.openBox<int>('lastUsedMenuItemByProductId');
await box.clear();

await dataSource.clearAuthenticatedUser();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class EnvironmentRemoteDataSource {
final CoffeecardApiV2 apiV2;
final NetworkRequestExecutor executor;

Future<Either<NetworkFailure, Environment>> getEnvironmentType() {
Future<Either<Failure, Environment>> getEnvironmentType() {
return executor
.execute(apiV2.apiV2AppconfigGet)
.map(Environment.fromAppConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class LeaderboardRemoteDataSource {
required this.executor,
});

Future<Either<NetworkFailure, List<LeaderboardUser>>> getLeaderboard(
Future<Either<Failure, List<LeaderboardUser>>> getLeaderboard(
LeaderboardFilter category,
int top,
) {
Expand All @@ -34,7 +34,7 @@ class LeaderboardRemoteDataSource {
.mapAll(LeaderboardUserModel.fromDTO);
}

Future<Either<NetworkFailure, LeaderboardUser>> getLeaderboardUser(
Future<Either<Failure, LeaderboardUser>> getLeaderboardUser(
LeaderboardFilter category,
) {
return executor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AccountRemoteDataSource {
);
}

Future<Either<NetworkFailure, Unit>> resendVerificationEmail(
Future<Either<Failure, Unit>> resendVerificationEmail(
String email,
) {
return executor.executeAndDiscard(
Expand All @@ -64,17 +64,17 @@ class AccountRemoteDataSource {
);
}

Future<Either<NetworkFailure, User>> getUser() {
Future<Either<Failure, User>> getUser() {
return executor.execute(apiV2.apiV2AccountGet).map(UserModel.fromResponse);
}

Future<Either<NetworkFailure, Unit>> requestPasscodeReset(String email) {
Future<Either<Failure, Unit>> requestPasscodeReset(String email) {
return executor.executeAndDiscard(
() => apiV1.apiV1AccountForgotpasswordPost(body: EmailDto(email: email)),
);
}

Future<Either<NetworkFailure, bool>> emailExists(String email) {
Future<Either<Failure, bool>> emailExists(String email) {
final body = EmailExistsRequest(email: email);
return executor
.execute(() => apiV2.apiV2AccountEmailExistsPost(body: body))
Expand Down
2 changes: 1 addition & 1 deletion lib/features/login/domain/usecases/resend_email.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class ResendEmail {

ResendEmail({required this.remoteDataSource});

Future<Either<NetworkFailure, void>> call(String email) async {
Future<Either<Failure, void>> call(String email) async {
return remoteDataSource.resendVerificationEmail(email);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class OccupationRemoteDataSource {
required this.executor,
});

Future<Either<NetworkFailure, List<OccupationModel>>> getOccupations() {
Future<Either<Failure, List<OccupationModel>>> getOccupations() {
return executor
.execute(api.apiV1ProgrammesGet)
.mapAll(OccupationModel.fromDTOV1);
Expand Down

This file was deleted.

24 changes: 0 additions & 24 deletions lib/features/product/data/models/product_model.dart

This file was deleted.

22 changes: 0 additions & 22 deletions lib/features/product/domain/entities/product.dart

This file was deleted.

This file was deleted.

21 changes: 0 additions & 21 deletions lib/features/product/domain/usecases/get_all_products.dart

This file was deleted.

15 changes: 15 additions & 0 deletions lib/features/product/menu_item_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:coffeecard/generated/api/coffeecard_api_v2.models.swagger.dart';
import 'package:equatable/equatable.dart';

class MenuItem extends Equatable {
const MenuItem({required this.id, required this.name});

factory MenuItem.fromResponse(MenuItemResponse response) =>
MenuItem(id: response.id, name: response.name);

final int id;
final String name;

@override
List<Object?> get props => [id, name];
}
20 changes: 11 additions & 9 deletions lib/features/product/presentation/cubit/product_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import 'package:coffeecard/core/errors/failures.dart';
import 'package:coffeecard/features/product/domain/entities/purchasable_products.dart';
import 'package:coffeecard/features/product/domain/usecases/get_all_products.dart';
import 'package:coffeecard/features/product/product_model.dart';
import 'package:coffeecard/features/product/product_repository.dart';
import 'package:coffeecard/features/product/purchasable_products.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

part 'product_state.dart';

class ProductCubit extends Cubit<ProductState> {
final GetAllProducts getAllProducts;
final ProductRepository productRepository;

ProductCubit({required this.getAllProducts}) : super(const ProductsLoading());
ProductCubit({required this.productRepository})
: super(const ProductsLoading());

Future<void> getProducts() async {
emit(const ProductsLoading());
final result = await getAllProducts();
emit(result.fold(ProductsError.fromFailure, ProductsLoaded.new));
}
Future<void> getProducts() => productRepository
.getProducts()
.match(ProductsError.new, ProductsLoaded.new)
.map(emit)
.run();
}
10 changes: 7 additions & 3 deletions lib/features/product/presentation/cubit/product_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ class ProductsLoading extends ProductState {
class ProductsLoaded extends ProductState {
final PurchasableProducts products;

const ProductsLoaded(this.products);
ProductsLoaded(Iterable<Product> products)
: products = (
clipCards: products.where((p) => p.amount > 1),
singleDrinks: products.where((p) => p.amount == 1),
perks: products.where((p) => p.isPerk),
);

@override
List<Object?> get props => [products];
Expand All @@ -23,8 +28,7 @@ class ProductsLoaded extends ProductState {
class ProductsError extends ProductState {
final String error;

const ProductsError(this.error);
ProductsError.fromFailure(Failure failure) : error = failure.reason;
ProductsError(Failure failure) : error = failure.reason;

@override
List<Object?> get props => [error];
Expand Down
Loading

0 comments on commit 65c1fb7

Please sign in to comment.