Skip to content

Commit d37c50a

Browse files
authored
Merge pull request #82 from layground/dev/mvp
Add weekly and six month analytic charts
2 parents 038fadb + a6f0ee6 commit d37c50a

29 files changed

+1048
-221
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ lib/firebase_options.dart
5555

5656
.github/instructions/pockaw-instruction.instructions.md
5757
GEMINI.md
58-
*.txt
58+
*.txt
59+
sqlite

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Connect with other Pockies, share tips, and contribute to the growth of Pockaw.
134134

135135
## 📆 Roadmap
136136

137-
### Q1 (Closed Testing #1) - Core Setup & MVP Launch ![100%](https://progress-bar.xyz/100/?width=40)
137+
### Round 1 (Closed Testing #1) - Core Setup & MVP Launch ![100%](https://progress-bar.xyz/100/?width=40)
138138

139139
* [x] **Splash & Onboarding** - Design and implement welcoming splash screen and onboarding process.
140140
* [x] **Authentication?** - Just fill personal data for offline use, no registration.
@@ -143,7 +143,7 @@ Connect with other Pockies, share tips, and contribute to the growth of Pockaw.
143143
* [x] **Custom Categories** - Enable users to create and manage custom categories for better personalization.
144144
* [x] **Publish on GitHub** - The first fully offline experience of Pockaw.
145145

146-
### Q2 (Open Beta) - Goals & Budgeting with Progression ![100%](https://progress-bar.xyz/100/?width=40)
146+
### Round 2 (Open Beta) - Goals & Budgeting with Progression ![100%](https://progress-bar.xyz/100/?width=40)
147147

148148
* [x] **Goals Tracking** - Create goals and its checklist items to breakdown your target.
149149
* [x] **Budget Tracking** - Set monthly/weekly spending budgets and get the visualization.
@@ -153,14 +153,14 @@ Connect with other Pockies, share tips, and contribute to the growth of Pockaw.
153153
* [x] **Basic Transaction Filters** - Add filters for date, category, and other custom criteria for transactions.
154154
* [x] **Open Beta Test** - Pockaw will be available on **Google PlayStore** as Early Access open beta test.
155155

156-
### Q3 - Enhanced User Experience ![0%](https://progress-bar.xyz/0/?width=40)
156+
### Round 3 - Enhanced User Experience ![0%](https://progress-bar.xyz/0/?width=40)
157157

158158
* [ ] **Advanced Summary & Analytics** - Add charts for monthly income/expense trends, custom filters, and analytics.
159159
* [ ] **Advanced Transaction Filters** - Add filters for date, category, and other custom criteria for transactions.
160160
* [ ] **Transaction Reminder** - Forgetting things? Chill! Pockaw will remind you to record transactions.
161161
* [ ] **User Reminders & Notifications** - Implement notifications for due dates and spending goals.
162162

163-
### Q4 - Shields On! ![0%](https://progress-bar.xyz/0/?width=40)
163+
### Round 4 - Shields On! ![0%](https://progress-bar.xyz/0/?width=40)
164164

165165
* [ ] **Release on Web** - Get a wider view and perspective of Pockaw (of course need a network connection).
166166
* [ ] **Release on Desktop** - Windows users will get the official desktop look and feel of Pockaw. **
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:pockaw/core/constants/app_colors.dart';
3+
import 'package:pockaw/core/constants/app_spacing.dart';
4+
import 'package:pockaw/core/constants/app_text_styles.dart';
5+
6+
class ChartContainer extends StatelessWidget {
7+
final String title;
8+
final String subtitle;
9+
final Widget chart;
10+
final EdgeInsets? margin;
11+
final EdgeInsets? padding;
12+
final double? height;
13+
const ChartContainer({
14+
super.key,
15+
required this.title,
16+
required this.subtitle,
17+
required this.chart,
18+
this.margin,
19+
this.padding,
20+
this.height,
21+
});
22+
23+
@override
24+
Widget build(BuildContext context) {
25+
return Container(
26+
height: height,
27+
margin: margin,
28+
padding: padding ?? const EdgeInsets.all(AppSpacing.spacing16),
29+
decoration: BoxDecoration(
30+
color: context.purpleBackground,
31+
borderRadius: BorderRadius.circular(16),
32+
boxShadow: [
33+
BoxShadow(
34+
color: Colors.black.withAlpha(10),
35+
blurRadius: 10,
36+
offset: const Offset(0, 4),
37+
),
38+
],
39+
),
40+
child: Column(
41+
spacing: AppSpacing.spacing4,
42+
children: [
43+
Text(
44+
title,
45+
style: AppTextStyles.heading6.copyWith(
46+
color: context.cardTitleText,
47+
fontWeight: FontWeight.bold,
48+
),
49+
),
50+
Text(
51+
subtitle,
52+
style: AppTextStyles.body3.copyWith(
53+
color: context.cardSubtitleText,
54+
),
55+
),
56+
Expanded(child: chart),
57+
],
58+
),
59+
);
60+
}
61+
}

lib/core/constants/app_colors.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ extension ColorExtensions on BuildContext {
242242
Color get disabledText =>
243243
themeMode == ThemeMode.dark ? AppColors.neutral700 : AppColors.neutral400;
244244

245+
Color get cardTitleText =>
246+
themeMode == ThemeMode.dark ? AppColors.neutral100 : AppColors.neutral900;
247+
248+
Color get cardSubtitleText =>
249+
themeMode == ThemeMode.dark ? AppColors.neutral300 : AppColors.neutral700;
250+
245251
Color get progressBackground => themeMode == ThemeMode.dark
246252
? AppColors.neutral900
247253
: AppColors.purpleAlpha10;

lib/core/database/pockaw_database.dart

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
import 'dart:io';
2-
31
import 'package:drift/drift.dart';
4-
import 'package:drift/native.dart';
5-
import 'package:flutter/foundation.dart';
6-
import 'package:path/path.dart';
2+
import 'package:drift_flutter/drift_flutter.dart';
73
import 'package:path_provider/path_provider.dart';
84
import 'package:pockaw/core/database/daos/budget_dao.dart';
95
import 'package:pockaw/core/database/daos/category_dao.dart';
@@ -274,17 +270,16 @@ class AppDatabase extends _$AppDatabase {
274270
Log.i('Populating default wallets...', label: 'database');
275271
await WalletPopulationService.populate(this);
276272
}
277-
}
278273

279-
/// https://github.com/simolus3/drift/issues/188
280-
LazyDatabase _openConnection() {
281-
return LazyDatabase(() async {
282-
final dbFolder =
283-
await getApplicationSupportDirectory(); // Use support directory for database
284-
final file = File(join(dbFolder.path, 'pockaw.sqlite'));
285-
if (kDebugMode) {
286-
// await file.delete(); // Uncomment for fresh DB on every run in debug
287-
}
288-
return NativeDatabase(file);
289-
});
274+
static QueryExecutor _openConnection() {
275+
return driftDatabase(
276+
name: 'my_database',
277+
native: const DriftNativeOptions(
278+
// By default, `driftDatabase` from `package:drift_flutter` stores the
279+
// database files in `getApplicationDocumentsDirectory()`.
280+
databaseDirectory: getApplicationSupportDirectory,
281+
),
282+
// If you need web support, see https://drift.simonbinder.eu/platforms/web/
283+
);
284+
}
290285
}

lib/core/extensions/date_time_extension.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ extension DateTimeExtension on DateTime {
7272
.inDays;
7373

7474
String baseText;
75+
bool useComma = differenceInDays == 0 || differenceInDays == 1;
7576
if (differenceInDays == 0) {
7677
baseText = 'Today';
7778
} else if (differenceInDays == 1) {
@@ -84,7 +85,12 @@ extension DateTimeExtension on DateTime {
8485
final time = use24Hours
8586
? DateFormat('HH.mm').format(this)
8687
: DateFormat('hh.mm a').format(this);
87-
return '$baseText, $time';
88+
89+
if (useComma) {
90+
return '$baseText, $time';
91+
}
92+
93+
return '$baseText $time';
8894
}
8995
return baseText;
9096
}

lib/core/extensions/double_extension.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ extension DoubleFormattingExtensions on double {
1919
}
2020
}
2121

22+
String toCompact({
23+
String? symbol,
24+
int? decimalDigits,
25+
String? locale,
26+
}) {
27+
return NumberFormat.compactCurrency(
28+
symbol: symbol,
29+
decimalDigits: decimalDigits,
30+
locale: locale,
31+
).format(this);
32+
}
33+
2234
/// Formats the double as a human-readable short price (e.g., 1K, 2,5M)
2335
/// Uses comma as decimal separator and up to 2 decimals for M, K, etc.
2436
String toShortPriceFormat() {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'dart:math';
2+
3+
import 'package:flutter/material.dart';
4+
5+
class ColorGenerator {
6+
/// Generates a completely random color.
7+
///
8+
/// This creates a color by randomizing the Red, Green, and Blue channels
9+
/// between 0 and 255. The opacity is always set to 1.0 (fully opaque).
10+
static Color generateRandomColor() {
11+
final Random random = Random();
12+
return Color.fromARGB(
13+
255, // Alpha (Opacity) - 255 means fully opaque
14+
random.nextInt(256), // Red (0-255)
15+
random.nextInt(256), // Green (0-255)
16+
random.nextInt(256), // Blue (0-255)
17+
);
18+
}
19+
}

lib/features/authentication/presentation/screens/login_screen.dart

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:gap/gap.dart';
55
import 'package:go_router/go_router.dart';
66
import 'package:hooks_riverpod/hooks_riverpod.dart';
77
import 'package:hugeicons/hugeicons.dart';
8+
import 'package:pockaw/core/components/bottom_sheets/alert_bottom_sheet.dart';
89
import 'package:pockaw/core/components/bottom_sheets/custom_bottom_sheet.dart';
910
import 'package:pockaw/core/components/buttons/custom_icon_button.dart';
1011
import 'package:pockaw/core/components/buttons/primary_button.dart';
@@ -24,6 +25,7 @@ import 'package:pockaw/features/authentication/presentation/components/create_fi
2425
import 'package:pockaw/features/authentication/presentation/components/google_signin_button.dart';
2526
import 'package:pockaw/features/authentication/presentation/riverpod/auth_provider.dart';
2627
import 'package:pockaw/features/backup_and_restore/presentation/components/restore_dialog.dart';
28+
import 'package:pockaw/features/backup_and_restore/presentation/riverpod/backup_controller.dart';
2729
import 'package:pockaw/features/image_picker/presentation/screens/image_picker_dialog.dart';
2830
import 'package:pockaw/features/settings/presentation/components/report_log_file_dialog.dart';
2931
import 'package:pockaw/features/theme_switcher/presentation/components/theme_mode_switcher.dart';
@@ -42,17 +44,29 @@ class LoginScreen extends HookConsumerWidget {
4244
final nameField = useTextEditingController();
4345

4446
void restoreData() {
45-
showModalBottomSheet(
46-
context: context,
47-
showDragHandle: true,
48-
builder: (dialogContext) => CustomBottomSheet(
47+
context.openBottomSheet(
48+
child: AlertBottomSheet(
4949
title: 'Restore Data',
50-
child: RestoreDialog(
50+
context: context,
51+
confirmText: 'Continue Restore',
52+
showCancelButton: false,
53+
onConfirm: () async {
54+
final success = await ref
55+
.read(backupControllerProvider.notifier)
56+
.restoreFromLocalFile();
57+
58+
if (success) {
59+
if (context.mounted) {
60+
context.pop();
61+
context.replace(Routes.main);
62+
}
63+
}
64+
},
65+
content: RestoreDialog(
5166
onSuccess: () async {
5267
await Future.delayed(Duration(milliseconds: 1500));
5368

5469
if (context.mounted) {
55-
dialogContext.pop();
5670
context.replace(Routes.main);
5771
}
5872
},

lib/features/backup_and_restore/presentation/components/local_backup_section.dart

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,30 +50,15 @@ class LocalBackupSection extends HookConsumerWidget {
5050
context: context,
5151
confirmText: 'Select Backup Folder',
5252
onConfirm: () async {
53-
Toast.show(
54-
'Starting restore...',
55-
type: ToastificationType.info,
56-
);
57-
5853
final success = await ref
5954
.read(backupControllerProvider.notifier)
6055
.restoreFromLocalFile();
6156

6257
if (success) {
63-
Toast.show(
64-
'Data restored successfully! refreshing app...',
65-
type: ToastificationType.success,
66-
);
67-
6858
if (context.mounted) {
6959
context.pop();
7060
context.replace(Routes.main);
7161
}
72-
} else {
73-
Toast.show(
74-
'Restore failed or cancelled.',
75-
type: ToastificationType.error,
76-
);
7762
}
7863
},
7964
showCancelButton: false,

0 commit comments

Comments
 (0)