Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile: Prepare demo app POC with hardcoded data #960

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
16 changes: 10 additions & 6 deletions recipients_app/lib/core/cubits/auth/auth_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ class AuthCubit extends Cubit<AuthState> {
if (user != null) {
try {
final recipient = await userRepository.fetchRecipient(user);
final Organization? organization = await _fetchOrganization(recipient);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can always just write
final organization = await ....;
instead of
final Organization? organization = await ....;

Type will be assigned automatically. Just FYI :D


emit(
AuthState(
status: AuthStatus.authenticated,
firebaseUser: user,
recipient: recipient,
organization: organization,
),
);
} on Exception catch (ex, stackTrace) {
Expand Down Expand Up @@ -56,12 +58,7 @@ class AuthCubit extends Cubit<AuthState> {

if (user != null) {
final recipient = await userRepository.fetchRecipient(user);
Organization? organization;

if (recipient?.organizationRef != null) {
organization = await organizationRepository
.fetchOrganization(recipient!.organizationRef!);
}
final Organization? organization = await _fetchOrganization(recipient);

emit(
AuthState(
Expand Down Expand Up @@ -104,4 +101,11 @@ class AuthCubit extends Cubit<AuthState> {
await userRepository.signOut();
emit(const AuthState());
}

Future<Organization?> _fetchOrganization(Recipient? recipient) async {
if (recipient?.organizationRef != null) {
return await organizationRepository.fetchOrganization(recipient!.organizationRef!);
}
return null;
}
}
4 changes: 3 additions & 1 deletion recipients_app/lib/core/cubits/auth/auth_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,21 @@ class AuthState extends Equatable {
});

@override
List<Object?> get props => [status, firebaseUser, recipient, exception];
List<Object?> get props => [status, firebaseUser, recipient, exception, organization];

AuthState copyWith({
AuthStatus? status,
User? firebaseUser,
Recipient? recipient,
Exception? exception,
Organization? organization,
}) {
return AuthState(
status: status ?? this.status,
firebaseUser: firebaseUser ?? this.firebaseUser,
recipient: recipient ?? this.recipient,
exception: exception ?? this.exception,
organization: organization ?? this.organization,
);
}
}
149 changes: 149 additions & 0 deletions recipients_app/lib/data/datasource/demo/demo_user.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import "package:firebase_auth/firebase_auth.dart";

class DemoUser implements User {
@override
String? get displayName => "demo user";

@override
String? get email => null;

@override
bool get emailVerified => true;

@override
Future<String?> getIdToken([bool forceRefresh = false]) {
throw UnimplementedError();
}

@override
Future<void> delete() {
throw UnimplementedError();
}

@override
Future<IdTokenResult> getIdTokenResult([bool forceRefresh = false]) {
throw UnimplementedError();
}

@override
bool get isAnonymous => throw UnimplementedError();

@override
Future<UserCredential> linkWithCredential(AuthCredential credential) {
throw UnimplementedError();
}

@override
Future<ConfirmationResult> linkWithPhoneNumber(String phoneNumber, [RecaptchaVerifier? verifier]) {
throw UnimplementedError();
}

@override
Future<UserCredential> linkWithPopup(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<UserCredential> linkWithProvider(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<void> linkWithRedirect(AuthProvider provider) {
throw UnimplementedError();
}

@override
UserMetadata get metadata => throw UnimplementedError();

@override
MultiFactor get multiFactor => throw UnimplementedError();

@override
String? get phoneNumber => throw UnimplementedError();

@override
String? get photoURL => throw UnimplementedError();

@override
List<UserInfo> get providerData => throw UnimplementedError();

@override
Future<UserCredential> reauthenticateWithCredential(AuthCredential credential) {
throw UnimplementedError();
}

@override
Future<UserCredential> reauthenticateWithPopup(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<UserCredential> reauthenticateWithProvider(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<void> reauthenticateWithRedirect(AuthProvider provider) {
throw UnimplementedError();
}

@override
String? get refreshToken => throw UnimplementedError();

@override
Future<void> reload() {
throw UnimplementedError();
}

@override
Future<void> sendEmailVerification([ActionCodeSettings? actionCodeSettings]) {
throw UnimplementedError();
}

@override
String? get tenantId => throw UnimplementedError();

@override
String get uid => throw UnimplementedError();
Comment on lines +107 to +108
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement required uid property

The uid property is critical for user identification and should return a consistent demo value instead of throwing.

- String get uid => throw UnimplementedError();
+ String get uid => "demo-user-id";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@override
String get uid => throw UnimplementedError();
@override
String get uid => "demo-user-id";


@override
Future<User> unlink(String providerId) {
throw UnimplementedError();
}

@override
Future<void> updateDisplayName(String? displayName) {
throw UnimplementedError();
}

@override
Future<void> updateEmail(String newEmail) {
throw UnimplementedError();
}

@override
Future<void> updatePassword(String newPassword) {
throw UnimplementedError();
}

@override
Future<void> updatePhoneNumber(PhoneAuthCredential phoneCredential) {
throw UnimplementedError();
}

@override
Future<void> updatePhotoURL(String? photoURL) {
throw UnimplementedError();
}

@override
Future<void> updateProfile({String? displayName, String? photoURL}) {
throw UnimplementedError();
}

@override
Future<void> verifyBeforeUpdateEmail(String newEmail, [ActionCodeSettings? actionCodeSettings]) {
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import "package:cloud_firestore/cloud_firestore.dart";

// We are using DocumentReference in repository / data source. That's why we need to get
// no-op implementation for it for demo data source.

// ignore: subtype_of_sealed_class
class NoOpDocumentReference implements DocumentReference<Map<String, dynamic>> {
const NoOpDocumentReference();

@override
CollectionReference<Map<String, dynamic>> collection(String collectionPath) {
throw UnimplementedError();
}

@override
Future<void> delete() {
throw UnimplementedError();
}

@override
FirebaseFirestore get firestore => throw UnimplementedError();

@override
Future<DocumentSnapshot<Map<String, dynamic>>> get([GetOptions? options]) {
throw UnimplementedError();
}

@override
String get id => throw UnimplementedError();

@override
CollectionReference<Map<String, dynamic>> get parent => throw UnimplementedError();

@override
String get path => throw UnimplementedError();

@override
Future<void> set(Map<String, dynamic> data, [SetOptions? options]) {
throw UnimplementedError();
}

@override
Stream<DocumentSnapshot<Map<String, dynamic>>> snapshots(
{bool includeMetadataChanges = false, ListenSource source = ListenSource.defaultSource,}) {
throw UnimplementedError();
}

@override
Future<void> update(Map<Object, Object?> data) {
throw UnimplementedError();
}

@override
DocumentReference<R> withConverter<R>(
{required FromFirestore<R> fromFirestore, required ToFirestore<R> toFirestore,}) {
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import "package:app/data/datasource/organization_data_source.dart";
import "package:app/data/models/models.dart";
import "package:cloud_firestore/cloud_firestore.dart";

class OrganizationDemoDataSource implements OrganizationDataSource {
final Organization _organization = _generateOrganization();

static Organization _generateOrganization() {
return const Organization(name: "Demo organization", contactName: "Demo manager", contactNumber: "+232 123456789");
}

@override
Future<Organization?> fetchOrganization(DocumentReference<Object?> organizationRef) async {
return _organization;
}
Comment on lines +13 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error simulation capabilities

The demo implementation should simulate real-world scenarios, including error cases.

   @override
   Future<Organization?> fetchOrganization(DocumentReference<Object?> organizationRef) async {
+    // Simulate network delay
+    await Future.delayed(const Duration(milliseconds: 500));
+    
+    // Simulate errors for specific refs (optional)
+    if (organizationRef.id == 'error_case') {
+      throw FirebaseException(plugin: 'demo', message: 'Simulated error');
+    }
+    
     return _organization;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<Organization?> fetchOrganization(DocumentReference<Object?> organizationRef) async {
return _organization;
}
Future<Organization?> fetchOrganization(DocumentReference<Object?> organizationRef) async {
// Simulate network delay
await Future.delayed(const Duration(milliseconds: 500));
// Simulate errors for specific refs (optional)
if (organizationRef.id == 'error_case') {
throw FirebaseException(plugin: 'demo', message: 'Simulated error');
}
return _organization;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import "dart:math";

import "package:app/data/datasource/payment_data_source.dart";
import "package:app/data/models/models.dart";
import "package:cloud_firestore/cloud_firestore.dart";

const String paymentCollection = "payments";

class PaymentDemoDataSource implements PaymentDataSource {
List<SocialIncomePayment> payments = initData();

static List<SocialIncomePayment> initData() {
final List<SocialIncomePayment> payments = <SocialIncomePayment>[];

final nowDate = DateTime.now();
final random = Random();

final confirmedPaymentsCount = random.nextInt(12) + 1;
final notConfirmedPaymentsCount = random.nextInt(2) + 1;

for (int i = 0; i < confirmedPaymentsCount; i++) {
final currentDateTime = DateTime(
nowDate.year,
nowDate.month - confirmedPaymentsCount - notConfirmedPaymentsCount + i,
15,
);
payments.add(
SocialIncomePayment(
id: "${currentDateTime.year}-${currentDateTime.month}",
paymentAt: Timestamp.fromDate(currentDateTime),
currency: "SLE",
amount: 700,
status: PaymentStatus.confirmed,
),
);
}

for (int i = 0; i < notConfirmedPaymentsCount; i++) {
final currentDateTime = DateTime(
nowDate.year,
nowDate.month - notConfirmedPaymentsCount + i,
15,
);
payments.add(
SocialIncomePayment(
id: "${currentDateTime.year}-${currentDateTime.month}",
paymentAt: Timestamp.fromDate(currentDateTime),
currency: "SLE",
amount: 700,
status: PaymentStatus.paid,
),
);
}

payments.sort((a, b) => a.id.compareTo(b.id));

return payments;
}

@override
Future<List<SocialIncomePayment>> fetchPayments({
required String recipientId,
}) async {
return payments;
}

/// This updates the payment status to confirmed
/// and also sets lastUpdatedAt and lastUpdatedBy to the
/// current time and recipient
@override
Future<void> confirmPayment({
required Recipient recipient,
required SocialIncomePayment payment,
}) async {
final updatedPayment = payment.copyWith(
status: PaymentStatus.confirmed,
updatedBy: recipient.userId,
);

final indexWhere = payments.indexWhere((element) => element.id == updatedPayment.id);
payments[indexWhere] = updatedPayment;
Comment on lines +80 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add bounds checking for payment updates

The array access could throw if payment ID is not found. Add error handling:

   final indexWhere = payments.indexWhere((element) => element.id == updatedPayment.id);
+  if (indexWhere == -1) {
+    throw Exception('Payment not found: ${updatedPayment.id}');
+  }
   payments[indexWhere] = updatedPayment;

Also applies to: 96-97

}

@override
Future<void> contestPayment({
required Recipient recipient,
required SocialIncomePayment payment,
required String contestReason,
}) async {
final updatedPayment = payment.copyWith(
status: PaymentStatus.contested,
comments: contestReason,
updatedBy: recipient.userId,
);

final indexWhere = payments.indexWhere((element) => element.id == updatedPayment.id);
payments[indexWhere] = updatedPayment;
}
}
Loading
Loading