Skip to content

Commit d961ac5

Browse files
committed
docs,fix,style: Improve README.md, revamp home screen, restructuring home folder with BLoC approach, etc.
1 parent 2066f43 commit d961ac5

39 files changed

Lines changed: 1507 additions & 676 deletions

README.md

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,69 @@
33
<div align="center">
44

55
![Android](https://img.shields.io/badge/Android-34A853?style=flat&logo=android&logoColor=white)
6+
![iOS](https://img.shields.io/badge/iOS-000000?style=flat&logo=apple&logoColor=white)
67
![Dart](https://img.shields.io/badge/Dart-0175C2?style=flat&logo=dart&logoColor=white)
78
![Flutter](https://img.shields.io/badge/Flutter-02569B?style=flat&logo=flutter&logoColor=white)
89

910
</div>
1011

11-
<p align="center">Monet is a simple application to track your money with NFC banking card.</p>
12+
<p align="center">Monet is a simple, frictionless personal finance application that tracks your money using NFC banking cards.</p>
13+
14+
## 📖 Overview
15+
16+
Monet is a cross-platform mobile application ecosystem designed to simplify personal finance tracking. By leveraging the built-in Near Field Communication (NFC) hardware on smartphones, Monet provides a unique, frictionless experience. Users can simply tap their physical banking or e-money cards against their device to trigger an automated, zero-touch transaction logging workflow.
17+
18+
> **Disclaimer:** This repository currently contains ONLY the **Flutter Frontend Client** of the Monet application. The Golang backend API (as specified in the SRS) is maintained in a separate repository or is pending implementation.
19+
20+
## 📱 Screenshots
21+
22+
| Home Dashboard | NFC Scanner | Expense Analytics |
23+
| :----------------------------------------------------------------: | :------------------------------------------------------------: | :----------------------------------------------------------------: |
24+
| <img src="docs/assets/home.png" width="200" alt="Home Dashboard"/> | <img src="docs/assets/nfc.png" width="200" alt="NFC Scanner"/> | <img src="docs/assets/analytics.png" width="200" alt="Analytics"/> |
25+
26+
_(Note: Replace the image paths with actual screenshots of the application)._
27+
28+
## ✨ Key Features
29+
30+
- **NFC Hardware Integration:** Reads public metadata and UID from physical banking or e-money cards utilizing platform-specific NFC channels (e.g., `nfc_manager`), linking physical assets directly to digital tracking without processing actual financial transactions.
31+
- **Zero-Touch Expense Routing (Smart Auto-Fill):** Tapping a previously registered card instantly routes the user to a transaction form. The "Payment Method" and "Card Alias" are automatically locked and pre-filled, requiring the user to only input the transaction amount.
32+
- **Offline-First Caching Mechanism:** Ensures transactions can be logged even in areas with poor cellular reception. Utilizing local SQLite or Hive databases, pending entries are cached and later synchronized securely with the backend API once connectivity is restored via a background isolate.
33+
- **Budgeting & Analytics Dashboard:** Offers an interactive visualization of financial health, featuring pie charts for category breakdowns and line charts for spending trends, powered by the `fl_chart` library.
34+
- **Secure Authentication & Session Management:** Implements secure user onboarding using email and password, OTP verification for password recovery, and stateless API access managed via JSON Web Tokens (JWT). JWTs are stored client-side using platform-specific Secure Storage (Keychain for iOS, Keystore for Android).
35+
- **Manual Financial Management:** Provides traditional capabilities for users to manually input income and expenses, attach notes, and categorize transactions.
1236

1337
## 💻 Tech Stack
1438

1539
- **Framework**: Flutter
1640
- **Language**: Dart
17-
- **Platform**: Android
18-
- **NFC**: Android NFC API
41+
- **Architecture Pattern**: Feature-First BLoC (Business Logic Component)
42+
- **Routing**: GoRouter (Declarative Routing)
43+
- **Network**: Dio
44+
- **Platform**: Android (Target API 26+) & iOS (Target iOS 13.0+)
45+
- **NFC**: Android NFC API & iOS CoreNFC
46+
- **Data Visualization**: fl_chart
47+
- **Local Storage**: Flutter Secure Storage (Keystore/Keychain) & SQLite/Hive (for offline caching)
48+
49+
## 📁 Project Architecture
50+
51+
This project follows a **Feature-First Architecture** utilizing the BLoC pattern for predictable state management.
52+
53+
```text
54+
lib/
55+
├── core/ # Global utilities, shared widgets, theme, and constants
56+
│ ├── constants/ # API endpoints, colors (Palette), routes
57+
│ ├── services/ # Secure storage, region formatters
58+
│ ├── utils/ # Loggers and formatters
59+
│ └── widgets/ # Reusable UI components (Buttons, Inputs)
60+
├── features/ # Isolated feature modules
61+
│ ├── auth/ # Login, Register, OTP, Password Reset
62+
│ ├── expense_tracker/ # Analytics, Charts, History
63+
│ ├── home/ # Dashboard, Balance Card, Quick Actions
64+
│ ├── nfc_scanner/ # NFC Hardware Integration
65+
│ ├── profile/ # User Profile Management
66+
│ └── settings/ # App Preferences and Configurations
67+
└── routes/ # GoRouter configuration and route generation
68+
```
1969

2070
## 🌐 Installation for Development
2171

@@ -24,35 +74,44 @@
2474
- Flutter SDK (latest stable version)
2575
- Dart SDK (included with Flutter)
2676
- Android Studio or VS Code
27-
- Android emulator or physical device with NFC capability
77+
- Android emulator or physical device with **NFC capability** (required for automated card reading features; manual entry fallback is available for non-NFC devices).
2878

29-
### Setup
79+
### Setup & Environment Configuration
3080

31-
1. Clone the repository:
81+
1. **Clone the repository:**
3282

3383
```bash
3484
git clone https://www.github.com/a6iyyu/monet
3585
cd monet
3686
```
3787

38-
2. Install dependencies:
88+
2. **Install dependencies:**
3989

4090
```bash
4191
flutter pub get
4292
```
4393

44-
3. Run the app:
94+
3. **Run the app with Environment Variables:**
95+
The application requires an API Base URL to connect to the backend. You must pass this during runtime using `--dart-define`.
4596

4697
```bash
47-
flutter run
98+
flutter run --dart-define=API_BASE_URL=https://your-backend-api.com # Coming Soon
4899
```
49100

50-
4. Build release APK:
51-
101+
4. **Build release APK:**
52102
```bash
53-
flutter build apk --release
103+
flutter build apk --release --dart-define=API_BASE_URL=https://your-production-api.com # Coming Soon
54104
```
55105

106+
## 🔒 Security & Privacy
107+
108+
Monet handles banking card metadata (UIDs) and adheres strictly to security best practices:
109+
110+
- **No Financial Processing:** Monet does not act as a payment gateway and will not execute or process actual financial transactions (EMV protocols) from scanned cards.
111+
- **Data Privacy:** The system only reads and stores the public UID and user-assigned alias. It is strictly prohibited from attempting to read, decode, or store sensitive financial elements such as EMV chip cryptograms, CVV, or PIN blocks.
112+
- **Secure Communication:** All client-server data exchange must be transmitted over HTTPS utilizing TLS 1.2 or TLS 1.3.
113+
- **Client-Side Storage:** Sensitive session data (JWTs) must be stored using platform-level hardware-backed enclaves (`EncryptedSharedPreferences` / `KeyStore` on Android and `Keychain Services` on iOS).
114+
56115
---
57116

58117
<p align="center">Made with ♥️ by Rafi Abiyyu Airlangga</p>

assets/icons/login.png

544 KB
Loading

lib/core/constants/api_endpoints.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class ApiEndpoints {
1717
static const String otp = '$baseUrlPrefix/auth/otp';
1818
static const String profile = '$baseUrlPrefix/user/profile';
1919
static const String register = '$baseUrlPrefix/auth/register';
20+
static const String resendOtp = '$baseUrlPrefix/auth/resend-otp';
21+
static const String notifications = '$baseUrlPrefix/notifications';
2022
static const String resetPassword = '$baseUrlPrefix/auth/reset-password';
2123

2224
// NFC Banking Card Endpoints

lib/core/utils/formatters.dart

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,22 @@ class Formatters {
1111
return text[0].toUpperCase() + text.substring(1).toLowerCase();
1212
}
1313

14-
/// Formats a double value into a currency string with the specified symbol and locale.
15-
static String currency(double amount, {String symbol = 'Rp '}) {
16-
return NumberFormat.currency(decimalDigits: 0, locale: 'id_ID', symbol: symbol).format(amount);
17-
}
14+
/// Formats a double value into a currency string.
15+
/// Mendukung format kode mata uang (IDR, USD, EUR) maupun simbol kustom.
16+
static String currency(double amount, {String currency = 'IDR', String? symbol, int decimalDigits = 0}) {
17+
final effectiveSymbol = symbol ?? (currency.toUpperCase() == 'IDR' ? 'Rp ' : '$currency ');
18+
final locale = currency.toUpperCase() == 'IDR' ? 'id_ID' : 'en_US';
1819

19-
/// Formats a date and time value into a string with the specified format and locale.
20-
static String dateTime(DateTime date) {
21-
return DateFormat('dd MMM yyyy, HH:mm', 'id_ID').format(date);
20+
return NumberFormat.currency(locale: locale, symbol: effectiveSymbol, decimalDigits: decimalDigits).format(amount);
2221
}
2322

2423
/// Masks a card number by replacing all but the last four digits with asterisks.
2524
static String maskCardNumber(String cardNumber) {
2625
if (cardNumber.length <= 4) {
27-
return cardNumber; // Return the original number if it's too short to mask
26+
return cardNumber;
2827
}
2928

3029
final String lastFour = cardNumber.substring(cardNumber.length - 4);
31-
return '**** **** **** $lastFour'; // Mask all but the last four digits
30+
return '•••• •••• •••• $lastFour';
3231
}
3332
}

lib/features/auth/bloc/auth_bloc.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
1111
on<LoginSubmitted>(_onLoginSubmitted);
1212
on<OtpVerified>(_onOtpVerified);
1313
on<RegisterSubmitted>(_onRegisterSubmitted);
14+
on<ResendOtpSubmitted>(_onResendOtpSubmitted);
1415
on<ResetPasswordSubmitted>(_onResetPasswordSubmitted);
1516
}
1617

@@ -41,7 +42,18 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
4142

4243
try {
4344
await authRepository.verifyOtp(event.request);
44-
emit(AuthSuccess(message: 'OTP Verified Successfully!'));
45+
emit(OtpVerificationSuccess(message: 'OTP Verified Successfully!'));
46+
} catch (e) {
47+
emit(AuthError(e.toString().replaceAll('Exception: ', '')));
48+
}
49+
}
50+
51+
Future<void> _onResendOtpSubmitted(ResendOtpSubmitted event, Emitter<AuthState> emit) async {
52+
emit(AuthLoading());
53+
54+
try {
55+
await authRepository.resendOtp(event.email);
56+
emit(OtpResendSuccess(message: 'Verification code resent successfully!'));
4557
} catch (e) {
4658
emit(AuthError(e.toString().replaceAll('Exception: ', '')));
4759
}

lib/features/auth/bloc/auth_event.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ class ForgotPasswordSubmitted extends AuthEvent {
2626
ForgotPasswordSubmitted(this.email);
2727
}
2828

29+
class ResendOtpSubmitted extends AuthEvent {
30+
final String email;
31+
ResendOtpSubmitted(this.email);
32+
}
33+
2934
class ResetPasswordSubmitted extends AuthEvent {
3035
final String token;
3136
final String newPassword;

lib/features/auth/bloc/auth_state.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import 'package:flutter/foundation.dart';
33
@immutable
44
sealed class AuthState {}
55

6+
class AuthError extends AuthState {
7+
final String errorMessage;
8+
AuthError(this.errorMessage);
9+
}
10+
611
class AuthInitial extends AuthState {}
712

813
class AuthLoading extends AuthState {}
@@ -14,7 +19,12 @@ class AuthSuccess extends AuthState {
1419
AuthSuccess({required this.message, this.emailContext});
1520
}
1621

17-
class AuthError extends AuthState {
18-
final String errorMessage;
19-
AuthError(this.errorMessage);
22+
class OtpVerificationSuccess extends AuthState {
23+
final String message;
24+
OtpVerificationSuccess({this.message = 'OTP Verified Successfully!'});
25+
}
26+
27+
class OtpResendSuccess extends AuthState {
28+
final String message;
29+
OtpResendSuccess({this.message = 'A new verification code has been sent.'});
2030
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
enum OtpPurpose {
2+
forgotPassword,
3+
registration,
4+
}

lib/features/auth/data/repositories/auth_repository.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ class AuthRepository {
8585
}
8686
}
8787

88+
Future<void> resendOtp(String email) async {
89+
try {
90+
final response = await _dio.post(ApiEndpoints.resendOtp, data: {'email': email});
91+
92+
if (response.statusCode != 200 && response.statusCode != 201) {
93+
throw Exception('Failed to resend OTP code.');
94+
}
95+
} on DioException catch (e) {
96+
throw Exception(_handleDioError(e, context: 'resend_otp'));
97+
} catch (e) {
98+
throw Exception(e.toString().replaceAll('Exception: ', ''));
99+
}
100+
}
101+
88102
Future<void> resetPassword(String token, String newPassword) async {
89103
try {
90104
final response = await _dio.post(
@@ -133,6 +147,7 @@ class AuthRepository {
133147
(404, 'forgot_password') => 'Email address not found.',
134148
(409, 'register') => 'This email is already registered. Try logging in instead.',
135149
(400 || 422 || 401, 'otp') => 'Invalid OTP code. Please try again.',
150+
(429, 'resend_otp') => 'Too many requests. Please wait before requesting another OTP.',
136151
(400 || 422, _) => 'Invalid data provided. Please check your input fields.',
137152
_ => 'An unexpected error occurred.',
138153
};

lib/features/auth/presentation/screens/otp_screen.dart

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,8 @@ class OtpScreen extends StatelessWidget {
1010
Widget build(BuildContext context) {
1111
return Scaffold(
1212
backgroundColor: Palette.background,
13-
appBar: AppBar(
14-
backgroundColor: Colors.transparent,
15-
elevation: 0,
16-
iconTheme: const IconThemeData(color: Palette.textPrimary),
17-
),
1813
body: SafeArea(
19-
child: SingleChildScrollView(
20-
padding: const EdgeInsets.symmetric(horizontal: 24.0),
21-
child: OtpForm(email: email),
22-
),
14+
child: SingleChildScrollView(child: OtpForm(email: email)),
2315
),
2416
);
2517
}

0 commit comments

Comments
 (0)