Compare commits

...

5 Commits

44 changed files with 636 additions and 601 deletions

3
.gitignore vendored
View File

@ -44,5 +44,6 @@ app.*.map.json
/android/app/release /android/app/release
#generated files #generated files
lib/**/*.g.dart # lib/**/*.g.dart
.direnv/ .direnv/
*.tar.gz

View File

@ -5,4 +5,4 @@ linux
.metadata .metadata
analysis_options.yaml analysis_options.yaml
pubspec.lock pubspec.lock
// lib/**/*.g.dart lib/**/*.g.dart

View File

@ -44,7 +44,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.rluv" applicationId "com.fosscat.rluv"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion flutter.minSdkVersion

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rluv"> package="com.fosscat.rluv">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rluv"> package="com.fosscat.rluv">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application <application

View File

@ -1,4 +1,4 @@
package com.example.rluv package com.fosscat.rluv
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rluv"> package="com.fosscat.rluv">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.

BIN
assets/app_icon512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

27
build-flutter-app.sh Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Build the Flutter app and package into an archive.
# Exit if any command fails
set -e
# Echo all commands for debug purposes
set -x
projectName=rluvApp
archiveName=$projectName-Linux-Portable.tar.gz
baseDir=$(pwd)
# ----------------------------- Build Flutter app ---------------------------- #
flutter pub get
flutter build linux
cd build/linux/arm64/release/bundle || exit
tar -czaf $archiveName ./*
mv $archiveName "$baseDir"/

View File

@ -114,11 +114,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1712608508, "lastModified": 1716948383,
"narHash": "sha256-vMZ5603yU0wxgyQeHJryOI+O61yrX2AHwY6LOFyV1gM=", "narHash": "sha256-SzDKxseEcHR5KzPXLwsemyTR/kaM9whxeiJohbL04rs=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "4cba8b53da471aea2ab2b0c1f30a81e7c451f4b6", "rev": "ad57eef4ef0659193044870c731987a6df5cf56b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -32,21 +32,6 @@
platforms-android-34 platforms-android-34
emulator emulator
]); ]);
# See below link for configuring ruby env with bundix
# https://github.com/the-nix-way/nix-flake-dev-environments/tree/main/ruby-on-rails
rubyEnv = pkgs.bundlerEnv {
name = "ruby-env";
inherit (pkgs) ruby;
gemdir = ./android;
};
# buildNodeJs = pkgs.callPackage "${nixpkgs}/pkgs/development/web/nodejs/nodejs.nix" {
# python = pkgs.python3;
# };
# nodejs = buildNodeJs {
# enableNpm = true;
# version = "20.0.0";
# sha256 = "sha256-Q5xxqi84woYWV7+lOOmRkaVxJYBmy/1FSFhgScgTQZA=";
# };
in in
pkgs.mkShell { pkgs.mkShell {
# Fix an issue with Flutter using an older version of aapt2, which does not know # Fix an issue with Flutter using an older version of aapt2, which does not know
@ -60,13 +45,6 @@
# pkg-config # pkg-config
jdk17 jdk17
android-sdk android-sdk
rubyEnv
rubyEnv.wrappedRuby
# fastlane
bundix
# gems
firebase-tools
# nodejs
]; ];
shellHook = '' shellHook = ''
export PATH="$PATH":"$HOME/.pub-cache/bin" export PATH="$PATH":"$HOME/.pub-cache/bin"

View File

@ -296,7 +296,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.example.rluv; PRODUCT_BUNDLE_IDENTIFIER = com.fosscat.rluv;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -424,7 +424,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.example.rluv; PRODUCT_BUNDLE_IDENTIFIER = com.fosscat.rluv;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -446,7 +446,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.example.rluv; PRODUCT_BUNDLE_IDENTIFIER = com.fosscat.rluv;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Rluv</string> <string>rluv</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>rluv</string> <string>fosscatrluv</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:rluv/features/account/signup.dart'; import 'package:rluv/features/account/signup.dart';
import 'package:rluv/global/styles.dart'; import 'package:rluv/global/styles.dart';
@ -13,8 +12,7 @@ class AccountCreateScreen extends ConsumerStatefulWidget {
const AccountCreateScreen({super.key}); const AccountCreateScreen({super.key});
@override @override
ConsumerState<AccountCreateScreen> createState() => ConsumerState<AccountCreateScreen> createState() => _AccountCreateScreenState();
_AccountCreateScreenState();
} }
enum _AccountScreen { options, login, signup } enum _AccountScreen { options, login, signup }
@ -40,7 +38,7 @@ class _AccountCreateScreenState extends ConsumerState<AccountCreateScreen> {
}, },
); );
} }
final screen = BuildMedia(context).size; final screen = MediaQuery.of(context).size;
return Scaffold( return Scaffold(
backgroundColor: Styles.purpleNurple, backgroundColor: Styles.purpleNurple,
body: SafeArea( body: SafeArea(
@ -60,11 +58,8 @@ class _AccountCreateScreenState extends ConsumerState<AccountCreateScreen> {
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0), padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Image.asset("assets/app_icon.png", child: Image.asset("assets/app_icon.png",
height: height: screen.width > 500 ? 250 : screen.width * 0.5,
screen.width > 500 ? 250 : screen.width * 0.5, width: screen.width > 500 ? 250 : screen.width * 0.5),
width: screen.width > 500
? 250
: screen.width * 0.5),
), ),
UiButton( UiButton(
color: Styles.sunflower, color: Styles.sunflower,

View File

@ -1,7 +1,6 @@
import 'package:colorful_print/colorful_print.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:helpers/helpers/print.dart';
import '../../global/api.dart'; import '../../global/api.dart';
import '../../global/styles.dart'; import '../../global/styles.dart';
@ -19,7 +18,7 @@ class Login extends ConsumerStatefulWidget {
} }
class _LoginState extends ConsumerState<Login> { class _LoginState extends ConsumerState<Login> {
bool usingUsername = false; bool usingUsername = true;
final emailController = TextEditingController(); final emailController = TextEditingController();
final usernameController = TextEditingController(); final usernameController = TextEditingController();
final passwordController = TextEditingController(); final passwordController = TextEditingController();
@ -32,7 +31,7 @@ class _LoginState extends ConsumerState<Login> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screen = BuildMedia(context).size; final screen = MediaQuery.of(context).size;
return Form( return Form(
key: widget.formKey, key: widget.formKey,
child: Stack( child: Stack(
@ -134,7 +133,7 @@ class _LoginState extends ConsumerState<Login> {
text: 'Password: ', text: 'Password: ',
validatorFunc: (s) { validatorFunc: (s) {
if (s != null && s.length < 6) { if (s != null && s.length < 6) {
return 'Please do a better password (you have to)'; return 'Please do a better password, at least 6 characters (you have to)';
} }
return null; return null;
}, },
@ -205,35 +204,21 @@ class _LoginState extends ConsumerState<Login> {
Future login() async { Future login() async {
try { try {
if (widget.formKey.currentState != null && if (widget.formKey.currentState != null && !widget.formKey.currentState!.validate()) {
!widget.formKey.currentState!.validate()) {
return; return;
} }
final data = final Map<String, dynamic>? data = await ref.read(apiProvider.notifier).post(path: 'auth/login', data: {
await ref.read(apiProvider.notifier).post(path: 'auth/login', data: { 'username': usernameController.text.isEmpty ? null : usernameController.text,
'username':
usernameController.text.isEmpty ? null : usernameController.text,
'email': emailController.text.isEmpty ? null : emailController.text, 'email': emailController.text.isEmpty ? null : emailController.text,
'password': passwordController.text, 'password': passwordController.text,
}); });
String message = 'Login unsuccessful'; bool success = data?['success'] ?? false;
bool success = data?['success'] == 'false'; String message = data?['message'] ?? (success ? 'Login success!' : 'Login unsuccessful.');
if (data != null) { printColor(data, textColor: TextColor.yellow);
if (data['message'] != null) { showSnack(ref: ref, text: message, type: success ? SnackType.success : SnackType.error);
message = data['message'];
} else if (success) {
message = 'Logged in!';
}
}
// final bool success = data?['success'] ?? false;
printAmber(data);
showSnack(
ref: ref,
text: message,
type: !success ? SnackType.error : SnackType.success);
} catch (err, st) { } catch (err, st) {
printRed('Error in login: $err\n$st'); printColor('Error in login: $err\n$st', textColor: TextColor.red);
} }
} }
} }

View File

@ -1,7 +1,6 @@
import 'package:colorful_print/colorful_print.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:helpers/helpers/print.dart';
import '../../global/api.dart'; import '../../global/api.dart';
import '../../global/styles.dart'; import '../../global/styles.dart';
@ -36,7 +35,7 @@ class _SignupState extends ConsumerState<Signup> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screen = BuildMedia(context).size; final screen = MediaQuery.of(context).size;
return Form( return Form(
key: widget.formKey, key: widget.formKey,
child: Stack( child: Stack(
@ -126,8 +125,7 @@ class _SignupState extends ConsumerState<Signup> {
), ),
UiButton( UiButton(
width: screen.width * 0.4, width: screen.width * 0.4,
icon: const Icon(Icons.cancel, icon: const Icon(Icons.cancel, color: Styles.washedStone, size: 20),
color: Styles.washedStone, size: 20),
onPressed: () { onPressed: () {
setState( setState(
() => hasFamilyCode = false, () => hasFamilyCode = false,
@ -140,9 +138,7 @@ class _SignupState extends ConsumerState<Signup> {
: Padding( : Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: UiButton( child: UiButton(
width: screen.width * 0.5 > 400 width: screen.width * 0.5 > 400 ? 400 : screen.width * 0.5,
? 400
: screen.width * 0.5,
text: 'JOIN FAMILY', text: 'JOIN FAMILY',
// color: Styles.flounderBlue, // color: Styles.flounderBlue,
onPressed: () { onPressed: () {
@ -190,8 +186,7 @@ class _SignupState extends ConsumerState<Signup> {
text, text,
style: TextStyle(fontSize: size.width < 350 ? 16 : 20), style: TextStyle(fontSize: size.width < 350 ? 16 : 20),
), ),
if (subtext != null) if (subtext != null) Text(subtext, style: const TextStyle(fontSize: 12)),
Text(subtext, style: const TextStyle(fontSize: 12)),
], ],
), ),
), ),
@ -223,36 +218,29 @@ class _SignupState extends ConsumerState<Signup> {
Future signup() async { Future signup() async {
try { try {
if (widget.formKey.currentState != null && if (widget.formKey.currentState != null && !widget.formKey.currentState!.validate()) {
!widget.formKey.currentState!.validate()) {
return; return;
} }
final data = final data = await ref.read(apiProvider.notifier).post(path: 'auth/signup', data: {
await ref.read(apiProvider.notifier).post(path: 'auth/signup', data: {
'name': nameCotroller.text, 'name': nameCotroller.text,
'username': 'username': usernameController.text.isEmpty ? null : usernameController.text,
usernameController.text.isEmpty ? null : usernameController.text,
'email': emailController.text.isEmpty ? null : emailController.text, 'email': emailController.text.isEmpty ? null : emailController.text,
'password': passwordController.text, 'password': passwordController.text,
'family_code': familyCodeController.text.isEmpty 'family_code': familyCodeController.text.isEmpty ? null : familyCodeController.text.toUpperCase(),
? null
: familyCodeController.text.toUpperCase(),
'budget_name': 'Budget' 'budget_name': 'Budget'
}); });
final success = data?['success'] ?? false; final success = data?['success'] ?? false;
showSnack( showSnack(
ref: ref, ref: ref,
text: data?['message'] ?? success text: data?['message'] ?? success ? 'Login successful' : 'Login unsuccessful',
? 'Login successful'
: 'Login unsuccessful',
type: !success ? SnackType.error : SnackType.success); type: !success ? SnackType.error : SnackType.success);
printAmber(data); printColor(data, textColor: TextColor.yellow);
// final user = User.fromJson(data?['user']); // final user = User.fromJson(data?['user']);
// ref.read(tokenProvider.notifier).state = // ref.read(tokenProvider.notifier).state =
} catch (err) { } catch (err) {
printRed(err); printColor(err, textColor: TextColor.red);
} }
} }
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers.dart';
import 'package:rluv/features/budget/screens/transactions_listview.dart'; import 'package:rluv/features/budget/screens/transactions_listview.dart';
import 'package:rluv/features/budget/widgets/add_transaction_dialog.dart'; import 'package:rluv/features/budget/widgets/add_transaction_dialog.dart';
import 'package:rluv/features/budget/widgets/budget_category_bar.dart'; import 'package:rluv/features/budget/widgets/budget_category_bar.dart';
@ -19,8 +18,7 @@ class BudgetOverviewScreen extends ConsumerStatefulWidget {
final Map<String, dynamic> initialData; final Map<String, dynamic> initialData;
@override @override
ConsumerState<BudgetOverviewScreen> createState() => ConsumerState<BudgetOverviewScreen> createState() => _BudgetOverviewScreenState();
_BudgetOverviewScreenState();
} }
class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> { class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
@ -30,23 +28,18 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
final budget = ref.watch(budgetProvider); final budget = ref.watch(budgetProvider);
final budgetCategories = ref.watch(budgetCategoriesProvider); final budgetCategories = ref.watch(budgetCategoriesProvider);
final transactions = ref.watch(transactionsProvider); final transactions = ref.watch(transactionsProvider);
final screen = BuildMedia(context).size; final screen = MediaQuery.of(context).size;
double netExpense = 0.0; double netExpense = 0.0;
double netIncome = 0.0; double netIncome = 0.0;
double expectedExpenses = 0.0; double expectedExpenses = 0.0;
Map<int, double> budgetCategoryNetMap = {}; Map<int, double> budgetCategoryNetMap = {};
netExpense = transactions netExpense =
.where((t) => t.type == TransactionType.expense) transactions.where((t) => t.type == TransactionType.expense).fold(netExpense, (net, t) => net + t.amount);
.fold(netExpense, (net, t) => net + t.amount); netIncome = transactions.where((t) => t.type == TransactionType.income).fold(netIncome, (net, t) => net + t.amount);
netIncome = transactions
.where((t) => t.type == TransactionType.income)
.fold(netIncome, (net, t) => net + t.amount);
for (final bud in budgetCategories) { for (final bud in budgetCategories) {
double net = 0.0; double net = 0.0;
expectedExpenses += bud.amount; expectedExpenses += bud.amount;
net = transactions net = transactions.where((t) => t.budgetCategoryId == bud.id).fold(net, (net, t) => net + t.amount);
.where((t) => t.budgetCategoryId == bud.id)
.fold(net, (net, t) => net + t.amount);
budgetCategoryNetMap[bud.id!] = net; budgetCategoryNetMap[bud.id!] = net;
} }
return Stack( return Stack(
@ -64,23 +57,14 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
const Spacer(flex: 2), const Spacer(flex: 2),
Text( Text(
formatDate(DateTime.now()), formatDate(DateTime.now()),
style: const TextStyle( style: const TextStyle(fontSize: 16, color: Styles.electricBlue),
fontSize: 16, color: Styles.electricBlue),
), ),
const Spacer(), const Spacer(),
const Text('MONTHLY', const Text('MONTHLY', style: TextStyle(fontSize: 42, color: Styles.electricBlue)),
style:
TextStyle(fontSize: 42, color: Styles.electricBlue)),
const Spacer(flex: 2), const Spacer(flex: 2),
BudgetNetBar( BudgetNetBar(isPositive: true, net: netIncome, expected: budget?.expectedIncome ?? 0.0),
isPositive: true,
net: netIncome,
expected: budget?.expectedIncome ?? 0.0),
const Spacer(), const Spacer(),
BudgetNetBar( BudgetNetBar(isPositive: false, net: netExpense, expected: expectedExpenses),
isPositive: false,
net: netExpense,
expected: expectedExpenses),
const Spacer(), const Spacer(),
], ],
), ),
@ -107,8 +91,7 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Text( child: Text(
'BUDGET', 'BUDGET',
style: TextStyle( style: TextStyle(fontSize: 28, color: Styles.electricBlue),
fontSize: 28, color: Styles.electricBlue),
), ),
), ),
budgetCategories.isEmpty budgetCategories.isEmpty
@ -120,25 +103,18 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
children: [ children: [
const Padding( const Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Text( child: Text('No budget categories created yet, add some!'),
'No budget categories created yet, add some!'),
), ),
UiButton( UiButton(
onPressed: onPressed: ref.watch(budgetProvider) == null
ref.watch(budgetProvider) == ? null
null : () => showDialog(
? null context: context,
: () => showDialog( builder: (context) => Dialog(
context: context, shape: Styles.dialogShape,
builder: (context) => Dialog( backgroundColor: Styles.dialogColor,
shape: Styles child: const BudgetCategoryDialog()),
.dialogShape, ),
backgroundColor:
Styles
.dialogColor,
child:
const BudgetCategoryDialog()),
),
text: 'Add Category', text: 'Add Category',
), ),
], ],
@ -146,55 +122,42 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
), ),
) )
: Padding( : Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 4.0),
horizontal: 2.0, vertical: 4.0),
child: SizedBox( child: SizedBox(
height: screen.height * 0.36, height: screen.height * 0.36,
child: Scrollbar( child: Scrollbar(
controller: budgetListScrollController, controller: budgetListScrollController,
thumbVisibility: true, thumbVisibility: true,
child: ListView( child: ListView(
physics: physics: const BouncingScrollPhysics(),
const BouncingScrollPhysics(), controller: budgetListScrollController,
controller:
budgetListScrollController,
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
...budgetCategories ...budgetCategories.map((category) {
.mapIndexed((i, category) { final int i = budgetCategories.indexOf(category);
return BudgetCategoryBar( return BudgetCategoryBar(
budgetCategory: category, budgetCategory: category,
currentAmount: currentAmount: budgetCategoryNetMap[category.id]!,
budgetCategoryNetMap[
category.id]!,
index: i, index: i,
); );
}).toList(), }).toList(),
const SizedBox(height: 20), const SizedBox(height: 20),
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.center,
MainAxisAlignment.center,
children: [ children: [
SizedBox( SizedBox(
width: 140, width: 140,
child: ElevatedButton( child: ElevatedButton(
onPressed: ref.watch( onPressed: ref.watch(budgetProvider) == null
budgetProvider) ==
null
? null ? null
: () => showDialog( : () => showDialog(
context: context, context: context,
builder: (context) => Dialog( builder: (context) => Dialog(
shape: Styles shape: Styles.dialogShape,
.dialogShape, backgroundColor: Styles.dialogColor,
backgroundColor: child: const BudgetCategoryDialog()),
Styles
.dialogColor,
child:
const BudgetCategoryDialog()),
), ),
child: const Text( child: const Text('Add Category'),
'Add Category'),
), ),
), ),
], ],
@ -214,8 +177,7 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
children: [ children: [
Expanded( Expanded(
child: Padding( child: Padding(
padding: padding: const EdgeInsets.only(left: 20.0, right: 15.0),
const EdgeInsets.only(left: 20.0, right: 15.0),
child: Container( child: Container(
height: 70, height: 70,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -225,8 +187,7 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
child: InkWell( child: InkWell(
child: const Center( child: const Center(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 14.0),
horizontal: 20.0, vertical: 14.0),
child: FittedBox( child: FittedBox(
child: Text( child: Text(
'Transaction History', 'Transaction History',
@ -238,10 +199,7 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
), ),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context, MaterialPageRoute(builder: (context) => const TransactionsListview()));
MaterialPageRoute(
builder: (context) =>
const TransactionsListview()));
}, },
), ),
), ),
@ -292,8 +250,7 @@ class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
child: IconButton( child: IconButton(
icon: const Icon(Icons.loop), icon: const Icon(Icons.loop),
color: Styles.seaweedGreen, color: Styles.seaweedGreen,
onPressed: () => onPressed: () => ref.read(dashboardProvider.notifier).fetchDashboard(),
ref.read(dashboardProvider.notifier).fetchDashboard(),
), ),
), ),
), ),

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:rluv/features/budget/widgets/transaction_list_item.dart'; import 'package:rluv/features/budget/widgets/transaction_list_item.dart';
import 'package:rluv/global/styles.dart'; import 'package:rluv/global/styles.dart';
import 'package:rluv/models/transaction_model.dart'; import 'package:rluv/models/transaction_model.dart';
@ -12,8 +11,7 @@ class TransactionsListview extends ConsumerStatefulWidget {
const TransactionsListview({super.key}); const TransactionsListview({super.key});
@override @override
ConsumerState<TransactionsListview> createState() => ConsumerState<TransactionsListview> createState() => _TransactionsListviewState();
_TransactionsListviewState();
} }
class _TransactionsListviewState extends ConsumerState<TransactionsListview> { class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
@ -23,8 +21,7 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(selectedTransactionSortProvider.notifier).state = ref.read(selectedTransactionSortProvider.notifier).state = TransactionSort.all;
TransactionSort.all;
ref.read(selectedSortDateProvider.notifier).state = SortDate.decending; ref.read(selectedSortDateProvider.notifier).state = SortDate.decending;
}); });
super.initState(); super.initState();
@ -34,8 +31,8 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final budgetCategories = ref.watch(budgetCategoriesProvider); final budgetCategories = ref.watch(budgetCategoriesProvider);
if (ref.read(selectedCategory) == null && budgetCategories.isNotEmpty) { if (ref.read(selectedCategory) == null && budgetCategories.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) => WidgetsBinding.instance
ref.read(selectedCategory.notifier).state = budgetCategories.first); .addPostFrameCallback((_) => ref.read(selectedCategory.notifier).state = budgetCategories.first);
} }
final sortType = ref.watch(selectedTransactionSortProvider); final sortType = ref.watch(selectedTransactionSortProvider);
final sortDate = ref.watch(selectedSortDateProvider); final sortDate = ref.watch(selectedSortDateProvider);
@ -60,8 +57,7 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
if (ref.read(selectedCategory) != null) { if (ref.read(selectedCategory) != null) {
transactions = transactions transactions = transactions
.where( .where(
(element) => (element) => element.budgetCategoryId == ref.read(selectedCategory)!.id,
element.budgetCategoryId == ref.read(selectedCategory)!.id,
) )
.toList(); .toList();
} }
@ -79,15 +75,15 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
break; break;
} }
WidgetsBinding.instance.addPostFrameCallback((_) => WidgetsBinding.instance
ref.read(transactionHistoryListProvider.notifier).state = transactions); .addPostFrameCallback((_) => ref.read(transactionHistoryListProvider.notifier).state = transactions);
return Scaffold( return Scaffold(
endDrawer: Drawer( endDrawer: Drawer(
backgroundColor: Styles.purpleNurple, backgroundColor: Styles.purpleNurple,
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
SizedBox(height: BuildMedia(context).height * 0.15), SizedBox(height: MediaQuery.of(context).size.height * 0.15),
DropdownButton<TransactionSort>( DropdownButton<TransactionSort>(
dropdownColor: Styles.lavender, dropdownColor: Styles.lavender,
value: ref.watch(selectedTransactionSortProvider), value: ref.watch(selectedTransactionSortProvider),
@ -108,8 +104,7 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
.toList(), .toList(),
onChanged: (TransactionSort? value) { onChanged: (TransactionSort? value) {
if (value != null) { if (value != null) {
ref.read(selectedTransactionSortProvider.notifier).state = ref.read(selectedTransactionSortProvider.notifier).state = value;
value;
} }
}), }),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -137,8 +132,7 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
} }
}), }),
const SizedBox(height: 20), const SizedBox(height: 20),
if (ref.read(selectedTransactionSortProvider) == if (ref.read(selectedTransactionSortProvider) == TransactionSort.category)
TransactionSort.category)
DropdownButton<BudgetCategory>( DropdownButton<BudgetCategory>(
dropdownColor: Styles.lavender, dropdownColor: Styles.lavender,
value: ref.watch(selectedCategory), value: ref.watch(selectedCategory),
@ -154,8 +148,7 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
width: 18, width: 18,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(color: Colors.black, width: 1.5),
color: Colors.black, width: 1.5),
color: e.color, color: e.color,
), ),
), ),
@ -186,9 +179,7 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8.0, top: 16.0), padding: const EdgeInsets.only(left: 8.0, top: 16.0),
child: IconButton( child: IconButton(icon: const Icon(Icons.chevron_left), onPressed: () => Navigator.pop(context)),
icon: const Icon(Icons.chevron_left),
onPressed: () => Navigator.pop(context)),
), ),
), ),
const Center( const Center(
@ -201,14 +192,11 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0, top: 16.0), padding: const EdgeInsets.only(left: 8.0, top: 16.0),
child: IconButton( child: IconButton(icon: const Icon(Icons.chevron_left), onPressed: () => Navigator.pop(context)),
icon: const Icon(Icons.chevron_left),
onPressed: () => Navigator.pop(context)),
), ),
const Spacer(), const Spacer(),
const Padding( const Padding(
padding: padding: EdgeInsets.symmetric(vertical: 18.0, horizontal: 0.0),
EdgeInsets.symmetric(vertical: 18.0, horizontal: 0.0),
child: Text( child: Text(
'Transaction History', 'Transaction History',
style: TextStyle(fontSize: 28), style: TextStyle(fontSize: 28),
@ -245,8 +233,7 @@ class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
} }
void toggleDrawer() { void toggleDrawer() {
if (scaffoldKey.currentState != null && if (scaffoldKey.currentState != null && scaffoldKey.currentState!.isDrawerOpen) {
scaffoldKey.currentState!.isDrawerOpen) {
scaffoldKey.currentState!.openDrawer(); scaffoldKey.currentState!.openDrawer();
} else if (scaffoldKey.currentState != null) { } else if (scaffoldKey.currentState != null) {
scaffoldKey.currentState!.openEndDrawer(); scaffoldKey.currentState!.openEndDrawer();
@ -292,8 +279,7 @@ final selectedTransactionSortProvider = StateProvider<TransactionSort>(
(ref) => TransactionSort.all, (ref) => TransactionSort.all,
); );
final selectedSortDateProvider = final selectedSortDateProvider = StateProvider<SortDate>((ref) => SortDate.decending);
StateProvider<SortDate>((ref) => SortDate.decending);
final transactionHistoryListProvider = StateProvider<List<Transaction>>( final transactionHistoryListProvider = StateProvider<List<Transaction>>(
(ref) => [], (ref) => [],

View File

@ -1,7 +1,6 @@
import 'package:colorful_print/colorful_print.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:helpers/helpers/print.dart';
import 'package:rluv/global/styles.dart'; import 'package:rluv/global/styles.dart';
import 'package:rluv/models/budget.dart'; import 'package:rluv/models/budget.dart';
import 'package:rluv/models/budget_category_model.dart'; import 'package:rluv/models/budget_category_model.dart';
@ -17,12 +16,10 @@ class BudgetCategoryDialog extends ConsumerStatefulWidget {
final BudgetCategory? category; final BudgetCategory? category;
@override @override
ConsumerState<BudgetCategoryDialog> createState() => ConsumerState<BudgetCategoryDialog> createState() => _AddBudgetCategoryDialogState();
_AddBudgetCategoryDialogState();
} }
class _AddBudgetCategoryDialogState class _AddBudgetCategoryDialogState extends ConsumerState<BudgetCategoryDialog> {
extends ConsumerState<BudgetCategoryDialog> {
late final Budget? budget; late final Budget? budget;
final categoryNameController = TextEditingController(); final categoryNameController = TextEditingController();
final amountController = TextEditingController(); final amountController = TextEditingController();
@ -65,12 +62,11 @@ class _AddBudgetCategoryDialogState
return Container(); return Container();
} }
return SizedBox( return SizedBox(
width: BuildMedia(context).width * Styles.dialogScreenWidthFactor, width: MediaQuery.of(context).size.width * Styles.dialogScreenWidthFactor,
child: Stack( child: Stack(
children: [ children: [
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 18.0, horizontal: 32.0),
const EdgeInsets.symmetric(vertical: 18.0, horizontal: 32.0),
child: Form( child: Form(
key: formKey, key: formKey,
child: Column( child: Column(
@ -78,9 +74,7 @@ class _AddBudgetCategoryDialogState
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(bottom: 18.0), padding: const EdgeInsets.only(bottom: 18.0),
child: Text(widget.category == null child: Text(widget.category == null ? 'Add Category:' : 'Edit Category'),
? 'Add Category:'
: 'Edit Category'),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
@ -149,8 +143,7 @@ class _AddBudgetCategoryDialogState
], ],
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 16.0),
vertical: 6.0, horizontal: 16.0),
child: SizedBox( child: SizedBox(
height: 76, height: 76,
child: ListView.builder( child: ListView.builder(
@ -164,23 +157,21 @@ class _AddBudgetCategoryDialogState
} else { } else {
setState(() => selectedColorIndex = -1); setState(() => selectedColorIndex = -1);
} }
printBlue(selectedColorIndex); printColor(selectedColorIndex, textColor: TextColor.blue);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: AnimatedContainer( child: AnimatedContainer(
decoration: selectedColorIndex == index decoration: selectedColorIndex == index
? BoxDecoration( ? BoxDecoration(
borderRadius: borderRadius: BorderRadius.circular(15.0),
BorderRadius.circular(15.0),
border: Border.all( border: Border.all(
color: Styles.washedStone, color: Styles.washedStone,
width: 2.0, width: 2.0,
), ),
) )
: BoxDecoration( : BoxDecoration(
borderRadius: borderRadius: BorderRadius.circular(5.0),
BorderRadius.circular(5.0),
border: Border.all( border: Border.all(
color: Colors.transparent, color: Colors.transparent,
width: 2.0, width: 2.0,
@ -205,8 +196,7 @@ class _AddBudgetCategoryDialogState
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors[index], color: colors[index],
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(color: Colors.black, width: 1.5),
color: Colors.black, width: 1.5),
), ),
), ),
], ],
@ -227,8 +217,7 @@ class _AddBudgetCategoryDialogState
color: Styles.expensesRed, color: Styles.expensesRed,
text: 'DELETE', text: 'DELETE',
onPressed: () { onPressed: () {
if (formKey.currentState != null && if (formKey.currentState != null && formKey.currentState!.validate()) {
formKey.currentState!.validate()) {
removeCategory().then((success) { removeCategory().then((success) {
Navigator.pop(context); Navigator.pop(context);
if (success) { if (success) {
@ -254,8 +243,7 @@ class _AddBudgetCategoryDialogState
color: Styles.lavender, color: Styles.lavender,
text: 'SAVE', text: 'SAVE',
onPressed: () { onPressed: () {
if (formKey.currentState != null && if (formKey.currentState != null && formKey.currentState!.validate()) {
formKey.currentState!.validate()) {
submitCategory(colors[selectedColorIndex]).then((_) { submitCategory(colors[selectedColorIndex]).then((_) {
Navigator.pop(context); Navigator.pop(context);
showSnack( showSnack(
@ -282,7 +270,7 @@ class _AddBudgetCategoryDialogState
Future submitCategory(Color categoryColor) async { Future submitCategory(Color categoryColor) async {
if (formKey.currentState != null && !formKey.currentState!.validate()) { if (formKey.currentState != null && !formKey.currentState!.validate()) {
printPink('Failed validation'); printColor('Failed validation', textColor: TextColor.red);
return; return;
} }
Map<String, dynamic>? budgetData; Map<String, dynamic>? budgetData;
@ -296,58 +284,41 @@ class _AddBudgetCategoryDialogState
name: categoryNameController.text, name: categoryNameController.text,
); );
budgetData = await ref budgetData = await ref.read(apiProvider.notifier).post(path: 'budget_category', data: newBudget.toJson());
.read(apiProvider.notifier)
.post(path: 'budget_category', data: newBudget.toJson());
success = budgetData != null ? budgetData['success'] as bool : false; success = budgetData != null ? budgetData['success'] as bool : false;
if (success) { if (success) {
ref ref.read(dashboardProvider.notifier).add({'budget_categories': budgetData});
.read(dashboardProvider.notifier)
.add({'budget_categories': budgetData});
} }
} else { } else {
final newBudget = final newBudget = BudgetCategory.copyWith(category: widget.category!, data: {
BudgetCategory.copyWith(category: widget.category!, data: {
'amount': double.parse(amountController.text), 'amount': double.parse(amountController.text),
'color': categoryColor, 'color': categoryColor,
'name': categoryNameController.text 'name': categoryNameController.text
}); });
budgetData = await ref budgetData = await ref.read(apiProvider.notifier).put(path: 'budget_category', data: newBudget.toJson());
.read(apiProvider.notifier)
.put(path: 'budget_category', data: newBudget.toJson());
success = budgetData != null ? budgetData['success'] as bool : false; success = budgetData != null ? budgetData['success'] as bool : false;
if (success) { if (success) {
ref ref.read(dashboardProvider.notifier).update({'budget_categories': budgetData});
.read(dashboardProvider.notifier)
.update({'budget_categories': budgetData});
} }
} }
if (success) { if (success) {
showSnack( showSnack(
ref: ref, ref: ref,
text: widget.category == null text: widget.category == null ? 'Added budget category!' : 'Updated category!',
? 'Added budget category!'
: 'Updated category!',
type: SnackType.error); type: SnackType.error);
} else { } else {
showSnack( showSnack(
ref: ref, ref: ref,
text: widget.category == null text: widget.category == null ? 'Could not add budget category' : 'Could not update category',
? 'Could not add budget category'
: 'Could not update category',
type: SnackType.error); type: SnackType.error);
} }
} }
Future<bool> removeCategory() async { Future<bool> removeCategory() async {
final res = await ref final res = await ref.read(apiProvider.notifier).delete(path: 'budget_category', data: {'id': widget.category!.id});
.read(apiProvider.notifier)
.delete(path: 'budget_category', data: {'id': widget.category!.id});
final success = res != null ? res['success'] as bool : false; final success = res != null ? res['success'] as bool : false;
if (success) { if (success) {
ref ref.read(dashboardProvider.notifier).removeWithId('budget_categories', widget.category!.id!);
.read(dashboardProvider.notifier)
.removeWithId('budget_categories', widget.category!.id!);
} }
return success; return success;
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:rluv/global/utils.dart'; import 'package:rluv/global/utils.dart';
import '../../../global/api.dart'; import '../../../global/api.dart';
@ -18,8 +17,7 @@ class TransactionDialog extends ConsumerStatefulWidget {
final Transaction? transaction; final Transaction? transaction;
@override @override
ConsumerState<TransactionDialog> createState() => ConsumerState<TransactionDialog> createState() => _AddTransactionDialogState();
_AddTransactionDialogState();
} }
class _AddTransactionDialogState extends ConsumerState<TransactionDialog> { class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
@ -37,8 +35,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
@override @override
void initState() { void initState() {
if (widget.transaction != null) { if (widget.transaction != null) {
amountController = amountController = TextEditingController(text: widget.transaction!.amount.toString());
TextEditingController(text: widget.transaction!.amount.toString());
memoController = TextEditingController(text: widget.transaction!.memo); memoController = TextEditingController(text: widget.transaction!.memo);
transactionType = widget.transaction!.type; transactionType = widget.transaction!.type;
selectedDate = widget.transaction!.date; selectedDate = widget.transaction!.date;
@ -53,8 +50,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
} }
final u = ref.read(userProvider); final u = ref.read(userProvider);
if (u == null) { if (u == null) {
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance.addPostFrameCallback((_) => ref.read(jwtProvider.notifier).revokeToken());
(_) => ref.read(jwtProvider.notifier).revokeToken());
} else { } else {
user = u; user = u;
} }
@ -65,10 +61,9 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (user == null) return Container(); if (user == null) return Container();
final List<BudgetCategory> budgetCategories = final List<BudgetCategory> budgetCategories = ref.read(budgetCategoriesProvider);
ref.read(budgetCategoriesProvider);
return SizedBox( return SizedBox(
width: BuildMedia(context).width * Styles.dialogScreenWidthFactor, width: MediaQuery.of(context).size.width * Styles.dialogScreenWidthFactor,
child: budgetCategories.isEmpty child: budgetCategories.isEmpty
? const Padding( ? const Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
@ -91,25 +86,21 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
onTap: transactionType == TransactionType.expense onTap: transactionType == TransactionType.expense
? null ? null
: () { : () {
setState(() => transactionType = setState(() => transactionType = TransactionType.expense);
TransactionType.expense);
}, },
child: AnimatedContainer( child: AnimatedContainer(
height: 38, height: 38,
width: 80, width: 80,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8.0), topLeft: Radius.circular(8.0), topRight: Radius.circular(8.0)),
topRight: Radius.circular(8.0)), color: transactionType == TransactionType.expense
color: transactionType ==
TransactionType.expense
? Styles.lavender ? Styles.lavender
: Styles.washedStone), : Styles.washedStone),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: const Padding( child: const Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Text('Expense', child: Text('Expense', style: TextStyle(fontSize: 16)),
style: TextStyle(fontSize: 16)),
), ),
), ),
), ),
@ -117,25 +108,20 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
onTap: transactionType == TransactionType.income onTap: transactionType == TransactionType.income
? null ? null
: () { : () {
setState(() => transactionType = setState(() => transactionType = TransactionType.income);
TransactionType.income);
}, },
child: AnimatedContainer( child: AnimatedContainer(
height: 38, height: 38,
width: 80, width: 80,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8.0), topLeft: Radius.circular(8.0), topRight: Radius.circular(8.0)),
topRight: Radius.circular(8.0)),
color: color:
transactionType == TransactionType.income transactionType == TransactionType.income ? Styles.lavender : Styles.washedStone),
? Styles.lavender
: Styles.washedStone),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: const Padding( child: const Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Text('Income', child: Text('Income', style: TextStyle(fontSize: 16)),
style: TextStyle(fontSize: 16)),
), ),
), ),
), ),
@ -208,9 +194,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
width: 18, width: 18,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(color: Colors.black, width: 1.5),
color: Colors.black,
width: 1.5),
color: e.color, color: e.color,
), ),
), ),
@ -226,8 +210,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
if (kDebugMode) { if (kDebugMode) {
print('${value.name} selected'); print('${value.name} selected');
} }
setState(() => setState(() => selectedBudgetCategory = value);
selectedBudgetCategory = value);
} }
}, },
), ),
@ -247,7 +230,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
), ),
), ),
Container( Container(
width: BuildMedia(context).width * 0.65, width: MediaQuery.of(context).size.width * 0.65,
height: 100, height: 100,
decoration: Styles.boxLavenderBubble, decoration: Styles.boxLavenderBubble,
child: TextFormField( child: TextFormField(
@ -269,9 +252,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
Navigator.pop(context); Navigator.pop(context);
showSnack( showSnack(
ref: ref, ref: ref,
text: widget.transaction != null text: widget.transaction != null ? 'Transaction updated!' : 'Transaction added!',
? 'Transaction updated!'
: 'Transaction added!',
type: SnackType.success, type: SnackType.success,
); );
}), }),
@ -289,9 +270,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
data: Transaction.copyWith(widget.transaction!, { data: Transaction.copyWith(widget.transaction!, {
'memo': memoController.text.isNotEmpty ? memoController.text : null, 'memo': memoController.text.isNotEmpty ? memoController.text : null,
'amount': double.parse(amountController.text), 'amount': double.parse(amountController.text),
'budget_category_id': transactionType == TransactionType.income 'budget_category_id': transactionType == TransactionType.income ? null : selectedBudgetCategory.id,
? null
: selectedBudgetCategory.id,
}).toJson()); }).toJson());
} else { } else {
data = await ref.read(apiProvider.notifier).post( data = await ref.read(apiProvider.notifier).post(
@ -299,15 +278,11 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
data: Transaction( data: Transaction(
amount: double.parse(amountController.text), amount: double.parse(amountController.text),
createdByUserId: user!.id!, createdByUserId: user!.id!,
budgetCategoryId: transactionType == TransactionType.income budgetCategoryId: transactionType == TransactionType.income ? null : selectedBudgetCategory.id,
? null
: selectedBudgetCategory.id,
budgetId: user!.budgetId, budgetId: user!.budgetId,
date: DateTime.now(), date: DateTime.now(),
type: transactionType, type: transactionType,
memo: memoController.text.isNotEmpty memo: memoController.text.isNotEmpty ? memoController.text : null)
? memoController.text
: null)
.toJson()); .toJson());
} }
final success = data != null ? data['success'] as bool : false; final success = data != null ? data['success'] as bool : false;
@ -320,9 +295,7 @@ class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
} else { } else {
showSnack( showSnack(
ref: ref, ref: ref,
text: widget.transaction != null text: widget.transaction != null ? 'Failed to edit transaction' : 'Failed to add transaction',
? 'Failed to edit transaction'
: 'Failed to add transaction',
type: SnackType.error); type: SnackType.error);
} }
} }

View File

@ -1,7 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:helpers/helpers/print.dart';
import 'package:rluv/features/budget/widgets/add_budget_category_dialog.dart'; import 'package:rluv/features/budget/widgets/add_budget_category_dialog.dart';
import 'package:rluv/global/utils.dart'; import 'package:rluv/global/utils.dart';
import 'package:rluv/models/budget_category_model.dart'; import 'package:rluv/models/budget_category_model.dart';
@ -45,8 +44,7 @@ class _BudgetCategoryBarState extends State<BudgetCategoryBar> {
} }
}); });
final innerHeight = widget.height - widget.innerPadding * 2; final innerHeight = widget.height - widget.innerPadding * 2;
final isBright = final isBright = getBrightness(widget.budgetCategory.color) == Brightness.light;
getBrightness(widget.budgetCategory.color) == Brightness.light;
final textStyle = TextStyle( final textStyle = TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -155,7 +153,6 @@ class _BudgetCategoryBarState extends State<BudgetCategoryBar> {
if (!name.contains(' ') || name.indexOf(' ') == name.length - 1) { if (!name.contains(' ') || name.indexOf(' ') == name.length - 1) {
return name; return name;
} }
printLime('here');
final words = name.split(' '); final words = name.split(' ');
int index = 0; int index = 0;
String firstLine = words[index]; String firstLine = words[index];

View File

@ -1,14 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:helpers/helpers.dart';
import 'package:rluv/global/styles.dart'; import 'package:rluv/global/styles.dart';
import 'package:rluv/global/utils.dart'; import 'package:rluv/global/utils.dart';
class BudgetNetBar extends StatelessWidget { class BudgetNetBar extends StatelessWidget {
const BudgetNetBar( const BudgetNetBar({super.key, required this.isPositive, required this.net, required this.expected});
{super.key,
required this.isPositive,
required this.net,
required this.expected});
final bool isPositive; final bool isPositive;
final double net; final double net;
@ -16,7 +11,7 @@ class BudgetNetBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = BuildMedia(context).width; final screenWidth = MediaQuery.of(context).size.width;
return Container( return Container(
width: screenWidth * 0.85, width: screenWidth * 0.85,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -25,8 +20,7 @@ class BudgetNetBar extends StatelessWidget {
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0), padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
child: child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text( Text(
isPositive ? 'Income' : 'Expenses', isPositive ? 'Income' : 'Expenses',
style: const TextStyle( style: const TextStyle(
@ -35,9 +29,7 @@ class BudgetNetBar extends StatelessWidget {
), ),
Text( Text(
'${net.currency()} / ${expected.currency()}', '${net.currency()} / ${expected.currency()}',
style: TextStyle( style: TextStyle(fontSize: 20, color: isPositive ? Styles.incomeGreen : Styles.expensesRed),
fontSize: 20,
color: isPositive ? Styles.incomeGreen : Styles.expensesRed),
), ),
]), ]),
), ),

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:rluv/features/budget/screens/transactions_listview.dart'; import 'package:rluv/features/budget/screens/transactions_listview.dart';
import 'package:rluv/features/budget/widgets/add_transaction_dialog.dart'; import 'package:rluv/features/budget/widgets/add_transaction_dialog.dart';
@ -19,8 +18,7 @@ class TransactionListItem extends ConsumerStatefulWidget {
final int index; final int index;
@override @override
ConsumerState<TransactionListItem> createState() => ConsumerState<TransactionListItem> createState() => _TransactionListItemState();
_TransactionListItemState();
} }
class _TransactionListItemState extends ConsumerState<TransactionListItem> { class _TransactionListItemState extends ConsumerState<TransactionListItem> {
@ -54,17 +52,13 @@ class _TransactionListItemState extends ConsumerState<TransactionListItem> {
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 8.0, left: 8.0, bottom: 8.0), padding: const EdgeInsets.only(top: 8.0, left: 8.0, bottom: 8.0),
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0)),
topLeft: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0)),
child: AnimatedContainer( child: AnimatedContainer(
curve: Curves.easeOut, curve: Curves.easeOut,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
height: cardHeight, height: cardHeight,
decoration: const BoxDecoration( decoration: const BoxDecoration(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0)),
topLeft: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0)),
color: Styles.washedStone, color: Styles.washedStone,
), ),
child: Row( child: Row(
@ -74,19 +68,16 @@ class _TransactionListItemState extends ConsumerState<TransactionListItem> {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
height: cardHeight, height: cardHeight,
width: 6, width: 6,
color: transaction!.type == TransactionType.income color: transaction!.type == TransactionType.income ? Styles.incomeBlue : Styles.expensesOrange),
? Styles.incomeBlue
: Styles.expensesOrange),
SizedBox( SizedBox(
width: BuildMedia(context).width * 0.65, width: MediaQuery.of(context).size.width * 0.65,
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(DateFormat('EEE MMM d, h:mm a') Text(DateFormat('EEE MMM d, h:mm a').format(transaction!.date)),
.format(transaction!.date)),
Row( Row(
children: [ children: [
const SizedBox( const SizedBox(
@ -101,9 +92,7 @@ class _TransactionListItemState extends ConsumerState<TransactionListItem> {
), ),
], ],
Text( Text(
transaction!.type == TransactionType.income transaction!.type == TransactionType.income ? 'Income' : budgetCategory!.name,
? 'Income'
: budgetCategory!.name,
style: const TextStyle(fontSize: 20), style: const TextStyle(fontSize: 20),
), ),
], ],
@ -125,9 +114,7 @@ class _TransactionListItemState extends ConsumerState<TransactionListItem> {
transaction!.amount.currency(), transaction!.amount.currency(),
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
color: transaction!.type == TransactionType.income color: transaction!.type == TransactionType.income ? Styles.incomeGreen : Styles.expensesRed),
? Styles.incomeGreen
: Styles.expensesRed),
), ),
if (showDetails) ...[ if (showDetails) ...[
IconButton( IconButton(
@ -140,8 +127,7 @@ class _TransactionListItemState extends ConsumerState<TransactionListItem> {
builder: (context) => Dialog( builder: (context) => Dialog(
backgroundColor: Styles.dialogColor, backgroundColor: Styles.dialogColor,
shape: Styles.dialogShape, shape: Styles.dialogShape,
child: child: TransactionDialog(transaction: transaction),
TransactionDialog(transaction: transaction),
), ),
); );
}, },
@ -182,21 +168,14 @@ class _TransactionListItemState extends ConsumerState<TransactionListItem> {
} }
Future deleteTransaction() async { Future deleteTransaction() async {
final res = await ref final res = await ref.read(apiProvider.notifier).delete(path: 'transaction', data: {'id': transaction!.id});
.read(apiProvider.notifier)
.delete(path: 'transaction', data: {'id': transaction!.id});
final success = res != null ? res['success'] as bool : false; final success = res != null ? res['success'] as bool : false;
if (success) { if (success) {
ref ref.read(dashboardProvider.notifier).removeWithId('transactions', transaction!.id!);
.read(dashboardProvider.notifier)
.removeWithId('transactions', transaction!.id!);
showSnack(ref: ref, text: 'Transaction removed', type: SnackType.success); showSnack(ref: ref, text: 'Transaction removed', type: SnackType.success);
} else { } else {
showSnack( showSnack(ref: ref, text: 'Could not delete transaction', type: SnackType.error);
ref: ref,
text: 'Could not delete transaction',
type: SnackType.error);
} }
} }
} }

View File

@ -1,8 +1,7 @@
import 'package:colorful_print/colorful_print.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:helpers/helpers/print.dart';
import 'package:rluv/global/utils.dart'; import 'package:rluv/global/utils.dart';
import 'package:rluv/models/shared_note.dart'; import 'package:rluv/models/shared_note.dart';
@ -22,7 +21,7 @@ class SharedNotesScreen extends ConsumerStatefulWidget {
class _SharedNotesScreenState extends ConsumerState<SharedNotesScreen> { class _SharedNotesScreenState extends ConsumerState<SharedNotesScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screen = BuildMedia(context).size; final screen = MediaQuery.of(context).size;
final sharedNotes = ref.watch(sharedNotesProvider); final sharedNotes = ref.watch(sharedNotesProvider);
return Column( return Column(
children: [ children: [
@ -47,14 +46,10 @@ class _SharedNotesScreenState extends ConsumerState<SharedNotesScreen> {
), ),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final note = sharedNotes[index]; final note = sharedNotes[index];
final colorBrightness = note.color == null final colorBrightness = note.color == null ? Brightness.light : getBrightness(note.color!);
? Brightness.light
: getBrightness(note.color!);
final textStyle = TextStyle( final textStyle = TextStyle(
fontSize: 16.0, fontSize: 16.0,
color: colorBrightness == Brightness.light color: colorBrightness == Brightness.light ? Colors.black54 : Colors.white70,
? Colors.black54
: Colors.white70,
); );
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
@ -67,14 +62,10 @@ class _SharedNotesScreenState extends ConsumerState<SharedNotesScreen> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
children: [ children: [
FittedBox( FittedBox(child: Text(note.title, style: const TextStyle(fontSize: 20))),
child: Text(note.title,
style: const TextStyle(fontSize: 20))),
Flexible( Flexible(
child: Text( child: Text(
note.content.length > 80 note.content.length > 80 ? "${note.content.substring(0, 75)}..." : note.content,
? "${note.content.substring(0, 75)}..."
: note.content,
style: textStyle, style: textStyle,
), ),
), ),
@ -93,9 +84,7 @@ class _SharedNotesScreenState extends ConsumerState<SharedNotesScreen> {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
bottom:
MediaQuery.of(context).viewInsets.bottom),
child: NoteBottomSheet( child: NoteBottomSheet(
index: index, index: index,
), ),
@ -121,8 +110,7 @@ class _SharedNotesScreenState extends ConsumerState<SharedNotesScreen> {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
bottom: MediaQuery.of(context).viewInsets.bottom),
child: const NoteBottomSheet( child: const NoteBottomSheet(
index: null, index: null,
), ),
@ -182,19 +170,14 @@ class _NoteBottomSheetState extends ConsumerState<NoteBottomSheet> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screen = BuildMedia(context).size; final screen = MediaQuery.of(context).size;
return Container( return Container(
width: screen.width, width: screen.width,
decoration: BoxDecoration( decoration: BoxDecoration(
color: newNote.color ?? Styles.washedStone, color: newNote.color ?? Styles.washedStone,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
boxShadow: const [ boxShadow: const [
BoxShadow( BoxShadow(color: Colors.black26, spreadRadius: 5.0, blurRadius: 2.0, offset: Offset(0, -2))
color: Colors.black26,
spreadRadius: 5.0,
blurRadius: 2.0,
offset: Offset(0, -2))
]), ]),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
@ -218,8 +201,7 @@ class _NoteBottomSheetState extends ConsumerState<NoteBottomSheet> {
// decoration: Styles.inputLavenderBubble(), // decoration: Styles.inputLavenderBubble(),
), ),
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 28.0, horizontal: 36.0),
const EdgeInsets.symmetric(vertical: 28.0, horizontal: 36.0),
child: UiButton( child: UiButton(
onPressed: !noteChanged(oldNote, newNote) onPressed: !noteChanged(oldNote, newNote)
? null ? null
@ -229,41 +211,26 @@ class _NoteBottomSheetState extends ConsumerState<NoteBottomSheet> {
try { try {
Response? res; Response? res;
if (widget.index == null) { if (widget.index == null) {
res = await ref res = await ref.read(apiProvider).post('shared_note', data: newNote.toJson());
.read(apiProvider)
.post('shared_note', data: newNote.toJson());
} else { } else {
res = await ref res = await ref.read(apiProvider).put('shared_note', data: newNote.toJson());
.read(apiProvider)
.put('shared_note', data: newNote.toJson());
} }
if (res.data != null && res.data['success']) { if (res.data != null && res.data['success']) {
if (widget.index == null) { if (widget.index == null) {
ref ref.read(dashboardProvider.notifier).add({'shared_notes': res.data});
.read(dashboardProvider.notifier)
.add({'shared_notes': res.data});
} else { } else {
ref ref.read(dashboardProvider.notifier).update({'shared_notes': res.data});
.read(dashboardProvider.notifier)
.update({'shared_notes': res.data});
} }
showSnack( showSnack(ref: ref, text: 'Added note', type: SnackType.success);
ref: ref,
text: 'Added note',
type: SnackType.success);
} else { } else {
showSnack( showSnack(
ref: ref, ref: ref,
text: res.data['message'] ?? text: res.data['message'] ?? 'Unexpected error occurred',
'Unexpected error occurred',
type: SnackType.error); type: SnackType.error);
} }
} catch (err) { } catch (err) {
showSnack( showSnack(ref: ref, text: 'Unexpected error occurred', type: SnackType.error);
ref: ref, printColor(err, textColor: TextColor.red);
text: 'Unexpected error occurred',
type: SnackType.error);
printRed(err);
} }
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
Navigator.pop(context); Navigator.pop(context);

View File

@ -1,9 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'package:colorful_print/colorful_print.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/print.dart';
import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:rluv/global/store.dart'; import 'package:rluv/global/store.dart';
@ -11,7 +11,7 @@ import '../models/token.dart';
final tokenProvider = StateProvider<Token?>((ref) { final tokenProvider = StateProvider<Token?>((ref) {
final jwt = ref.watch(jwtProvider); final jwt = ref.watch(jwtProvider);
printLime('Current token: $jwt'); printColor('Current token: $jwt', textColor: TextColor.green);
if (jwt == null) return null; if (jwt == null) return null;
try { try {
return Token.fromJson(JwtDecoder.decode(jwt)); return Token.fromJson(JwtDecoder.decode(jwt));
@ -37,12 +37,12 @@ class _JwtNotifier extends StateNotifier<String?> {
void setToken(String jwt) { void setToken(String jwt) {
state = jwt; state = jwt;
printCyan('Loaded jwt into client: $jwt'); printColor('Loaded jwt into client: $jwt', textColor: TextColor.cyan);
ref.read(prefsProvider)?.setString('jwt', jwt); ref.read(prefsProvider)?.setString('jwt', jwt);
} }
void revokeToken() { void revokeToken() {
printCyan('jwt token revoked'); printColor('jwt token revoked', textColor: TextColor.cyan);
state = null; state = null;
ref.read(prefsProvider)?.remove('jwt'); ref.read(prefsProvider)?.remove('jwt');
} }
@ -58,8 +58,7 @@ class _ApiNotifier extends StateNotifier<Dio> {
// dio.options.baseUrl = "http://localhost:8081/"; // dio.options.baseUrl = "http://localhost:8081/";
dio.options.baseUrl = "https://rluv.fosscat.com/"; dio.options.baseUrl = "https://rluv.fosscat.com/";
dio.interceptors.addAll([ dio.interceptors.addAll([
InterceptorsWrapper(onRequest: InterceptorsWrapper(onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
(RequestOptions options, RequestInterceptorHandler handler) {
final jwt = ref.read(jwtProvider); final jwt = ref.read(jwtProvider);
if (jwt != null) { if (jwt != null) {
options.headers['token'] = jwt; options.headers['token'] = jwt;
@ -69,12 +68,10 @@ class _ApiNotifier extends StateNotifier<Dio> {
if (response.statusCode != null) { if (response.statusCode != null) {
if (response.statusCode == 200) { if (response.statusCode == 200) {
try { try {
if ((response.data as Map<String, dynamic>) if ((response.data as Map<String, dynamic>).containsKey('success')) {
.containsKey('success')) {
if (!response.data['success']) return handler.next(response); if (!response.data['success']) return handler.next(response);
} }
if ((response.data as Map<String, dynamic>) if ((response.data as Map<String, dynamic>).containsKey('token')) {
.containsKey('token')) {
final jwt = response.data['token']; final jwt = response.data['token'];
if (jwt != null) { if (jwt != null) {
ref.read(jwtProvider.notifier).setToken(jwt); ref.read(jwtProvider.notifier).setToken(jwt);
@ -83,7 +80,7 @@ class _ApiNotifier extends StateNotifier<Dio> {
} }
} }
} catch (err) { } catch (err) {
printRed('Error in interceptor for token: $err'); printColor('Error in interceptor for token: $err', textColor: TextColor.red);
return handler.next(response); return handler.next(response);
} }
} }
@ -115,8 +112,7 @@ class _ApiNotifier extends StateNotifier<Dio> {
} }
} }
Future<Map<String, dynamic>?> put( Future<Map<String, dynamic>?> put({required String path, Object? data}) async {
{required String path, Object? data}) async {
try { try {
final res = await dio.put(path, data: data); final res = await dio.put(path, data: data);
@ -129,8 +125,7 @@ class _ApiNotifier extends StateNotifier<Dio> {
} }
} }
Future<Map<String, dynamic>?> post( Future<Map<String, dynamic>?> post({required String path, Object? data}) async {
{required String path, Object? data}) async {
try { try {
final res = await dio.post(path, data: data); final res = await dio.post(path, data: data);
@ -143,8 +138,7 @@ class _ApiNotifier extends StateNotifier<Dio> {
} }
} }
Future<Map<String, dynamic>?> delete( Future<Map<String, dynamic>?> delete({required String path, Object? data}) async {
{required String path, Object? data}) async {
try { try {
final res = await dio.delete(path, data: data); final res = await dio.delete(path, data: data);
@ -162,8 +156,7 @@ class _LoggingInterceptor extends Interceptor {
_LoggingInterceptor(); _LoggingInterceptor();
@override @override
Future onRequest( Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
RequestOptions options, RequestInterceptorHandler handler) async {
logPrint('///*** REQUEST ***\\\\\\'); logPrint('///*** REQUEST ***\\\\\\');
printKV('URI', options.uri); printKV('URI', options.uri);
printKV('METHOD', options.method); printKV('METHOD', options.method);
@ -177,7 +170,7 @@ class _LoggingInterceptor extends Interceptor {
@override @override
void onError(DioException err, ErrorInterceptorHandler handler) { void onError(DioException err, ErrorInterceptorHandler handler) {
printRed('///*** ERROR RESPONSE ***\\\\\\'); printColor('///*** ERROR RESPONSE ***\\\\\\', textColor: TextColor.red);
logPrint('URI: ${err.requestOptions.uri}'); logPrint('URI: ${err.requestOptions.uri}');
if (err.response != null) { if (err.response != null) {
logPrint('STATUS CODE: ${err.response?.statusCode?.toString()}'); logPrint('STATUS CODE: ${err.response?.statusCode?.toString()}');
@ -192,8 +185,7 @@ class _LoggingInterceptor extends Interceptor {
} }
@override @override
Future onResponse( Future onResponse(Response response, ResponseInterceptorHandler handler) async {
Response response, ResponseInterceptorHandler handler) async {
logPrint('///*** RESPONSE ***\\\\\\'); logPrint('///*** RESPONSE ***\\\\\\');
printKV('URI', response.requestOptions.uri); printKV('URI', response.requestOptions.uri);
printKV('STATUS CODE', response.statusCode ?? ''); printKV('STATUS CODE', response.statusCode ?? '');
@ -206,7 +198,7 @@ class _LoggingInterceptor extends Interceptor {
void printKV(String key, Object v) { void printKV(String key, Object v) {
if (kDebugMode) { if (kDebugMode) {
printOrange('$key: $v'); printColor('$key: $v', textColor: TextColor.orange);
} }
} }
@ -215,21 +207,21 @@ class _LoggingInterceptor extends Interceptor {
final data = (s as Map<String, dynamic>?); final data = (s as Map<String, dynamic>?);
if (kDebugMode) { if (kDebugMode) {
if (data == null) { if (data == null) {
printAmber({}); printColor({}, textColor: TextColor.yellow);
return; return;
} }
JsonEncoder encoder = const JsonEncoder.withIndent(' '); JsonEncoder encoder = const JsonEncoder.withIndent(' ');
String prettyprint = encoder.convert(s); String prettyprint = encoder.convert(s);
printAmber(prettyprint); printColor(prettyprint, textColor: TextColor.yellow);
} }
} catch (_) { } catch (_) {
printAmber(s); printColor(s, textColor: TextColor.yellow);
} }
} }
void logPrint(String s) { void logPrint(String s) {
if (kDebugMode) { if (kDebugMode) {
printOrange(s); printColor(s, textColor: TextColor.yellow);
} }
} }
} }

View File

@ -1,8 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:colorful_print/colorful_print.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/print.dart';
import 'package:rluv/global/api.dart'; import 'package:rluv/global/api.dart';
import 'package:rluv/models/budget.dart'; import 'package:rluv/models/budget.dart';
import 'package:rluv/models/budget_category_model.dart'; import 'package:rluv/models/budget_category_model.dart';
@ -13,8 +13,7 @@ import '../models/shared_note.dart';
import '../models/transaction_model.dart'; import '../models/transaction_model.dart';
import '../models/user.dart'; import '../models/user.dart';
StateProvider<SharedPreferences?> prefsProvider = StateProvider<SharedPreferences?> prefsProvider = StateProvider<SharedPreferences?>((ref) => null);
StateProvider<SharedPreferences?>((ref) => null);
// final StateProvider<Token?> tokenProvider = StateProvider<Token?>((ref) { // final StateProvider<Token?> tokenProvider = StateProvider<Token?>((ref) {
// // final tokenStr = prefs.getString('jwt'); // // final tokenStr = prefs.getString('jwt');
@ -42,8 +41,7 @@ final Provider<FamilyModel?> familyProvider = Provider<FamilyModel?>(
}, },
); );
final Provider<List<BudgetCategory>> budgetCategoriesProvider = final Provider<List<BudgetCategory>> budgetCategoriesProvider = Provider<List<BudgetCategory>>((ref) {
Provider<List<BudgetCategory>>((ref) {
final dash = ref.watch(dashboardProvider); final dash = ref.watch(dashboardProvider);
if (dash == null) return []; if (dash == null) return [];
final categoriesData = dash['budget_categories'] as List<dynamic>; final categoriesData = dash['budget_categories'] as List<dynamic>;
@ -55,7 +53,7 @@ final Provider<List<BudgetCategory>> budgetCategoriesProvider =
final prefs = ref.read(prefsProvider); final prefs = ref.read(prefsProvider);
final budgetJson = jsonEncode({'budget_categories': categoriesData}); final budgetJson = jsonEncode({'budget_categories': categoriesData});
printBlue('updated prefs stored categories'); printColor('updated prefs stored categories', textColor: TextColor.blue);
prefs?.setString('budget_categories', budgetJson); prefs?.setString('budget_categories', budgetJson);
return categories; return categories;
@ -71,8 +69,7 @@ final Provider<Budget?> budgetProvider = Provider<Budget?>(
}, },
); );
final Provider<List<Transaction>> transactionsProvider = final Provider<List<Transaction>> transactionsProvider = Provider<List<Transaction>>((ref) {
Provider<List<Transaction>>((ref) {
final dash = ref.watch(dashboardProvider); final dash = ref.watch(dashboardProvider);
if (dash == null) return []; if (dash == null) return [];
final transactions = dash['transactions'] as List<dynamic>; final transactions = dash['transactions'] as List<dynamic>;
@ -83,8 +80,7 @@ final Provider<List<Transaction>> transactionsProvider =
.toList(); .toList();
}); });
final Provider<List<SharedNote>> sharedNotesProvider = final Provider<List<SharedNote>> sharedNotesProvider = Provider<List<SharedNote>>(
Provider<List<SharedNote>>(
(ref) { (ref) {
final dash = ref.watch(dashboardProvider); final dash = ref.watch(dashboardProvider);
if (dash == null) return []; if (dash == null) return [];
@ -97,8 +93,7 @@ final Provider<List<SharedNote>> sharedNotesProvider =
}, },
); );
final dashboardProvider = final dashboardProvider = StateNotifierProvider<DashBoardStateNotifier, Map<String, dynamic>?>(
StateNotifierProvider<DashBoardStateNotifier, Map<String, dynamic>?>(
(ref) { (ref) {
return DashBoardStateNotifier(ref); return DashBoardStateNotifier(ref);
}, },
@ -119,10 +114,10 @@ class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
); );
final token = ref.read(tokenProvider); final token = ref.read(tokenProvider);
if (token?.familyId == null) { if (token?.familyId == null) {
printPink('No token, cannot fetch dashboard'); printColor('No token, cannot fetch dashboard', textColor: TextColor.red);
return; return;
} }
printAmber('Fetching dashboard'); printColor('Fetching dashboard', textColor: TextColor.yellow);
state = await ref.read(apiProvider.notifier).get("dashboard"); state = await ref.read(apiProvider.notifier).get("dashboard");
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance.addPostFrameCallback(
(_) => ref.read(loadingStateProvider.notifier).state = false, (_) => ref.read(loadingStateProvider.notifier).state = false,
@ -131,7 +126,7 @@ class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
void update(Map<String, dynamic> data) { void update(Map<String, dynamic> data) {
if (state == null) { if (state == null) {
printPink('Cant update data, state is null'); printColor('Cant update data, state is null', textColor: TextColor.red);
return; return;
} }
if (data.keys.length != 1 || data.values.length != 1) { if (data.keys.length != 1 || data.values.length != 1) {
@ -149,9 +144,7 @@ class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
) )
.toList(); .toList();
subStateListObj.removeWhere( subStateListObj.removeWhere(
(element) => (element) => element['id'] == (data.values.first as Map<String, dynamic>)['id'],
element['id'] ==
(data.values.first as Map<String, dynamic>)['id'],
); );
subStateListObj.add(data.values.first); subStateListObj.add(data.values.first);
@ -167,7 +160,7 @@ class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
void add(Map<String, dynamic> data) { void add(Map<String, dynamic> data) {
if (state == null) { if (state == null) {
printPink('Cant add data, state is null'); printColor('Cant add data, state is null', textColor: TextColor.red);
return; return;
} }
if (data.keys.length != 1 || data.values.length != 1) { if (data.keys.length != 1 || data.values.length != 1) {
@ -192,7 +185,7 @@ class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
void removeWithId(String stateKey, int id) { void removeWithId(String stateKey, int id) {
if (state == null) { if (state == null) {
printPink('Cant remove data, state is null'); printColor('Cant remove data, state is null', textColor: TextColor.red);
return; return;
} }
switch (stateKey) { switch (stateKey) {
@ -201,8 +194,7 @@ class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
case 'shared_noted': case 'shared_noted':
final subStateList = state![stateKey] as List<dynamic>; final subStateList = state![stateKey] as List<dynamic>;
final newState = state; final newState = state;
subStateList subStateList.removeWhere((e) => (e as Map<String, dynamic>)['id'] == id);
.removeWhere((e) => (e as Map<String, dynamic>)['id'] == id);
newState![stateKey] = subStateList; newState![stateKey] = subStateList;
state = {...newState}; state = {...newState};
// printBlue(state); // printBlue(state);

View File

@ -1,9 +1,9 @@
import 'dart:math'; import 'dart:math';
import 'package:colorful_print/colorful_print.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/print.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:rluv/global/styles.dart'; import 'package:rluv/global/styles.dart';
import 'package:rluv/main.dart'; import 'package:rluv/main.dart';
@ -20,20 +20,16 @@ bool boolFromJson(int value) => value == 1;
int boolToJson(bool hide) => hide ? 1 : 0; int boolToJson(bool hide) => hide ? 1 : 0;
String colorToJson(Color color) => String colorToJson(Color color) => color.toString().split('(0x')[1].split(')')[0];
color.toString().split('(0x')[1].split(')')[0];
Color colorFromJson(String hex) => Color(int.parse(hex, radix: 16)); Color colorFromJson(String hex) => Color(int.parse(hex, radix: 16));
String? optionalColorToJson(Color? optionalColor) => optionalColor == null String? optionalColorToJson(Color? optionalColor) =>
? null optionalColor == null ? null : optionalColor.toString().split('(0x')[1].split(')')[0];
: optionalColor.toString().split('(0x')[1].split(')')[0];
Color? optionalColorFromJson(String? hex) => Color? optionalColorFromJson(String? hex) => hex == null ? null : Color(int.parse(hex, radix: 16));
hex == null ? null : Color(int.parse(hex, radix: 16));
Brightness getBrightness(Color color) => Brightness getBrightness(Color color) => ThemeData.estimateBrightnessForColor(color);
ThemeData.estimateBrightnessForColor(color);
List<Color> generateColorList() { List<Color> generateColorList() {
List<Color> colors = []; List<Color> colors = [];
@ -60,7 +56,7 @@ extension MonetaryExtension on double {
return "\$${pieces.first}.00"; return "\$${pieces.first}.00";
} else { } else {
if (pieces.length > 2) { if (pieces.length > 2) {
printBlue(pieces); printColor(pieces, textColor: TextColor.blue);
} }
return '\$${pieces[0]}.${pieces[1].padRight(2, "0")}'; return '\$${pieces[0]}.${pieces[1].padRight(2, "0")}';
} }
@ -83,7 +79,7 @@ void showSnack(
Duration duration = const Duration(seconds: 2)}) { Duration duration = const Duration(seconds: 2)}) {
final messengerKey = ref.read(scaffoldMessengerKeyProvider); final messengerKey = ref.read(scaffoldMessengerKeyProvider);
if (messengerKey.currentState == null) { if (messengerKey.currentState == null) {
printPink('Cannot show snackbar, state == null'); printColor('Cannot show snackbar, state == null', textColor: TextColor.red);
return; return;
} }
final color = type == SnackType.info final color = type == SnackType.info
@ -99,8 +95,7 @@ void showSnack(
elevation: 8, elevation: 8,
backgroundColor: Styles.deepPurpleNurple, backgroundColor: Styles.deepPurpleNurple,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
), ),
content: Padding( content: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
@ -108,9 +103,7 @@ void showSnack(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(right: 14.0), padding: const EdgeInsets.only(right: 14.0),
child: Icon( child: Icon(type == SnackType.success ? Icons.check_circle : Icons.info, color: color),
type == SnackType.success ? Icons.check_circle : Icons.info,
color: color),
), ),
Text(text, style: textStyle), Text(text, style: textStyle),
], ],
@ -120,8 +113,7 @@ void showSnack(
} }
bool isEmailValid(String email) { bool isEmailValid(String email) {
final RegExp regex = final RegExp regex = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$");
RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$");
return regex.hasMatch(email); return regex.hasMatch(email);
} }

View File

@ -1,7 +1,6 @@
import 'package:animated_splash_screen/animated_splash_screen.dart'; import 'package:animated_splash_screen/animated_splash_screen.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/misc_build/build_media.dart';
import 'package:rluv/features/account/account_create_screen.dart'; import 'package:rluv/features/account/account_create_screen.dart';
import 'package:rluv/features/budget/screens/budget_overview.dart'; import 'package:rluv/features/budget/screens/budget_overview.dart';
import 'package:rluv/features/notes/screens/notes_screen.dart'; import 'package:rluv/features/notes/screens/notes_screen.dart';
@ -74,8 +73,7 @@ class _HomeState extends ConsumerState<Home> {
} }
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance.addPostFrameCallback(
(_) { (_) {
ref.read(currentHomePageProvider.notifier).state = ref.read(currentHomePageProvider.notifier).state = const BudgetOverviewScreen(initialData: {});
const BudgetOverviewScreen(initialData: {});
}, },
); );
super.initState(); super.initState();
@ -92,8 +90,7 @@ class _HomeState extends ConsumerState<Home> {
); );
}); });
} }
if (ref.watch(currentHomePageProvider).toString() == if (ref.watch(currentHomePageProvider).toString() == "BudgetOverviewScreen") {
"BudgetOverviewScreen") {
initData = {}; initData = {};
} }
return Scaffold( return Scaffold(
@ -104,15 +101,13 @@ class _HomeState extends ConsumerState<Home> {
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
SizedBox(height: BuildMedia(context).height * 0.15), SizedBox(height: MediaQuery.of(context).size.height * 0.15),
UiButton( UiButton(
text: 'Budget', text: 'Budget',
color: drawerColors[4], color: drawerColors[4],
onPressed: () { onPressed: () {
if (ref.read(currentHomePageProvider).toString() != if (ref.read(currentHomePageProvider).toString() != "BudgetOverviewScreen") {
"BudgetOverviewScreen") { ref.read(currentHomePageProvider.notifier).state = BudgetOverviewScreen(initialData: initData);
ref.read(currentHomePageProvider.notifier).state =
BudgetOverviewScreen(initialData: initData);
} }
toggleDrawer(); toggleDrawer();
}, },
@ -121,10 +116,8 @@ class _HomeState extends ConsumerState<Home> {
text: 'Notes', text: 'Notes',
color: drawerColors[5], color: drawerColors[5],
onPressed: () { onPressed: () {
if (ref.read(currentHomePageProvider).toString() != if (ref.read(currentHomePageProvider).toString() != "SharedNotesScreen") {
"SharedNotesScreen") { ref.read(currentHomePageProvider.notifier).state = SharedNotesScreen(initialData: initData);
ref.read(currentHomePageProvider.notifier).state =
SharedNotesScreen(initialData: initData);
} }
toggleDrawer(); toggleDrawer();
}, },
@ -134,10 +127,8 @@ class _HomeState extends ConsumerState<Home> {
text: 'Settings', text: 'Settings',
color: drawerColors[0], color: drawerColors[0],
onPressed: () { onPressed: () {
if (ref.read(currentHomePageProvider).toString() != if (ref.read(currentHomePageProvider).toString() != "SettingsScreen") {
"SettingsScreen") { ref.read(currentHomePageProvider.notifier).state = const SettingsScreen();
ref.read(currentHomePageProvider.notifier).state =
const SettingsScreen();
} }
toggleDrawer(); toggleDrawer();
}, },
@ -190,5 +181,4 @@ final appBarTitleProvider = Provider<String>(
); );
final scaffoldMessengerKeyProvider = final scaffoldMessengerKeyProvider =
Provider<GlobalKey<ScaffoldMessengerState>>( Provider<GlobalKey<ScaffoldMessengerState>>((ref) => GlobalKey<ScaffoldMessengerState>());
(ref) => GlobalKey<ScaffoldMessengerState>());

27
lib/models/budget.g.dart Normal file
View File

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'budget.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Budget _$BudgetFromJson(Map<String, dynamic> json) => Budget(
id: json['id'] as int?,
familyId: json['family_id'] as int,
name: json['name'] as String,
expectedIncome: (json['expected_income'] as num?)?.toDouble(),
createdAt: dateFromJson(json['created_at'] as int),
updatedAt: dateFromJson(json['updated_at'] as int),
hide: json['hide'] == null ? false : boolFromJson(json['hide'] as int),
);
Map<String, dynamic> _$BudgetToJson(Budget instance) => <String, dynamic>{
'id': instance.id,
'family_id': instance.familyId,
'expected_income': instance.expectedIncome,
'name': instance.name,
'hide': boolToJson(instance.hide),
'created_at': dateToJson(instance.createdAt),
'updated_at': dateToJson(instance.updatedAt),
};

View File

@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'budget_category_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BudgetCategory _$BudgetCategoryFromJson(Map<String, dynamic> json) =>
BudgetCategory(
id: json['id'] as int?,
budgetId: json['budget_id'] as int,
name: json['name'] as String,
color: colorFromJson(json['color'] as String),
createdAt: dateFromJson(json['created_at'] as int),
updatedAt: dateFromJson(json['updated_at'] as int),
amount: (json['amount'] as num).toDouble(),
hide: json['hide'] == null ? false : boolFromJson(json['hide'] as int),
);
Map<String, dynamic> _$BudgetCategoryToJson(BudgetCategory instance) =>
<String, dynamic>{
'budget_id': instance.budgetId,
'id': instance.id,
'name': instance.name,
'amount': instance.amount,
'hide': boolToJson(instance.hide),
'color': colorToJson(instance.color),
'created_at': dateToJson(instance.createdAt),
'updated_at': dateToJson(instance.updatedAt),
};

View File

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'family_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
FamilyModel _$FamilyModelFromJson(Map<String, dynamic> json) => FamilyModel(
id: json['id'] as int,
code: json['code'] as String?,
createdAt: dateFromJson(json['created_at'] as int),
updatedAt: dateFromJson(json['updated_at'] as int),
hide: json['hide'] == null ? false : boolFromJson(json['hide'] as int),
);
Map<String, dynamic> _$FamilyModelToJson(FamilyModel instance) =>
<String, dynamic>{
'id': instance.id,
'code': instance.code,
'hide': boolToJson(instance.hide),
'created_at': dateToJson(instance.createdAt),
'updated_at': dateToJson(instance.updatedAt),
};

View File

@ -0,0 +1,38 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shared_note.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SharedNote _$SharedNoteFromJson(Map<String, dynamic> json) => SharedNote(
id: json['id'] as int?,
familyId: json['family_id'] as int,
createdByUserId: json['created_by_user_id'] as int,
content: json['content'] as String,
title: json['title'] as String,
color: optionalColorFromJson(json['color'] as String?),
createdAt: dateFromJson(json['created_at'] as int),
updatedAt: dateFromJson(json['updated_at'] as int),
tagIds: _tagIdsFromJson(json['tag_ids'] as String),
isMarkdown: json['is_markdown'] == null
? false
: boolFromJson(json['is_markdown'] as int),
hide: json['hide'] == null ? false : boolFromJson(json['hide'] as int),
);
Map<String, dynamic> _$SharedNoteToJson(SharedNote instance) =>
<String, dynamic>{
'id': instance.id,
'family_id': instance.familyId,
'created_by_user_id': instance.createdByUserId,
'content': instance.content,
'title': instance.title,
'tag_ids': _tagIdsToJson(instance.tagIds),
'color': optionalColorToJson(instance.color),
'is_markdown': boolToJson(instance.isMarkdown),
'hide': boolToJson(instance.hide),
'created_at': dateToJson(instance.createdAt),
'updated_at': dateToJson(instance.updatedAt),
};

33
lib/models/tag.g.dart Normal file
View File

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tag.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Tag _$TagFromJson(Map<String, dynamic> json) => Tag(
id: json['id'] as int?,
familyId: json['family_id'] as int,
createdByUserId: json['created_by_user_id'] as int,
name: json['name'] as String,
type: $enumDecode(_$TagTypeEnumMap, json['type']),
createdAt: dateFromJson(json['created_at'] as int),
updatedAt: dateFromJson(json['updated_at'] as int),
hide: json['hide'] == null ? false : boolFromJson(json['hide'] as int),
);
Map<String, dynamic> _$TagToJson(Tag instance) => <String, dynamic>{
'id': instance.id,
'family_id': instance.familyId,
'created_by_user_id': instance.createdByUserId,
'name': instance.name,
'type': _$TagTypeEnumMap[instance.type]!,
'hide': boolToJson(instance.hide),
'created_at': dateToJson(instance.createdAt),
'updated_at': dateToJson(instance.updatedAt),
};
const _$TagTypeEnumMap = {
TagType.note: 'note',
};

21
lib/models/token.g.dart Normal file
View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'token.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Token _$TokenFromJson(Map<String, dynamic> json) => Token(
userId: json['user_id'] as int,
familyId: json['family_id'] as int,
generatedAt: dateFromJson(json['generated_at'] as int),
expiresAt: dateFromJson(json['expires_at'] as int),
);
Map<String, dynamic> _$TokenToJson(Token instance) => <String, dynamic>{
'family_id': instance.familyId,
'user_id': instance.userId,
'generated_at': dateToJson(instance.generatedAt),
'expires_at': dateToJson(instance.expiresAt),
};

View File

@ -0,0 +1,41 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'transaction_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Transaction _$TransactionFromJson(Map<String, dynamic> json) => Transaction(
id: json['id'] as int?,
amount: (json['amount'] as num).toDouble(),
type: $enumDecode(_$TransactionTypeEnumMap, json['type']),
budgetId: json['budget_id'] as int,
budgetCategoryId: json['budget_category_id'] as int?,
createdByUserId: json['created_by_user_id'] as int,
date: dateFromJson(json['date'] as int),
memo: json['memo'] as String?,
createdAt: dateFromJson(json['created_at'] as int),
updatedAt: dateFromJson(json['updated_at'] as int),
hide: json['hide'] == null ? false : boolFromJson(json['hide'] as int),
);
Map<String, dynamic> _$TransactionToJson(Transaction instance) =>
<String, dynamic>{
'id': instance.id,
'budget_category_id': instance.budgetCategoryId,
'budget_id': instance.budgetId,
'created_by_user_id': instance.createdByUserId,
'amount': instance.amount,
'memo': instance.memo,
'type': _$TransactionTypeEnumMap[instance.type]!,
'hide': boolToJson(instance.hide),
'date': dateToJson(instance.date),
'created_at': dateToJson(instance.createdAt),
'updated_at': dateToJson(instance.updatedAt),
};
const _$TransactionTypeEnumMap = {
TransactionType.income: 'income',
TransactionType.expense: 'expense',
};

33
lib/models/user.g.dart Normal file
View File

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
User _$UserFromJson(Map<String, dynamic> json) => User(
id: json['id'] as int?,
name: json['name'] as String,
familyId: json['family_id'] as int,
budgetId: json['budget_id'] as int,
email: json['email'] as String?,
username: json['username'] as String?,
createdAt: dateFromJson(json['created_at'] as int),
updatedAt: dateFromJson(json['updated_at'] as int),
lastActivityAt: dateFromJson(json['last_activity_at'] as int),
hide: json['hide'] == null ? false : boolFromJson(json['hide'] as int),
);
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'id': instance.id,
'family_id': instance.familyId,
'budget_id': instance.budgetId,
'name': instance.name,
'username': instance.username,
'email': instance.email,
'hide': boolToJson(instance.hide),
'created_at': dateToJson(instance.createdAt),
'updated_at': dateToJson(instance.updatedAt),
'last_activity_at': dateToJson(instance.lastActivityAt),
};

View File

@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
set(BINARY_NAME "rluv") set(BINARY_NAME "rluv")
# The unique GTK application identifier for this application. See: # The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID # https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.rluv") set(APPLICATION_ID "com.fosscat.rluv")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.

View File

@ -0,0 +1,20 @@
package_rename_config:
android:
app_name: rluv
package_name: com.fosscat.rluv
override_old_package: com.example.rluv
lang: kotlin
ios:
app_name: rluv
bundle_name: fosscatrluv
package_name: com.fosscat.rluv
web:
app_name: rluv
description: Package to change project configurations.
linux:
app_name: rluv
package_name: com.fosscat.rluv
exe_name: rluv

View File

@ -0,0 +1,12 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=rluv
Comment=Budget App
Categories=Utility;
Icon=com.fosscat.rluv
Exec=rluv
Terminal=false
StartupWMClass=rluv

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Generator for metainfo & .desktop files:
https://www.freedesktop.org/software/appstream/metainfocreator/#/
-->
<component type="desktop-application">
<id>com.fosscat.rluv</id>
<name>rluv</name>
<summary>A budget app made with love</summary>
<developer_name>Nate Anderson</developer_name>
<url type="homepage">https://git.fosscat.com/n8r/rluv_client</url>
<metadata_license></metadata_license>
<project_license></project_license>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</supports>
<description>
<p>A budgeting app for my needs. Maybe your too?</p>
</description>
<launchable type="desktop-id">com.fosscat.rluv.desktop</launchable>
<screenshots>
<!-- <screenshot type="default"> -->
<!-- <image>https://raw.githubusercontent.com/Merrit/flutter_flatpak_example/main/screenshots/screenshot.png</image> -->
<!-- </screenshot> -->
</screenshots>
<content_rating type="oars-1.1" />
<releases>
<release version="0.0.1" date="2024-06-02" />
</releases>
</component>

View File

@ -161,6 +161,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
colorful_print:
dependency: "direct main"
description:
name: colorful_print
sha256: "2c9784a0d5e6dcd480a0d4ab67b7263e19d31644a30a8bc7f67ceb6db89549c7"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -177,14 +185,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
cupertino_icons: csslib:
dependency: "direct main" dependency: transitive
description: description:
name: cupertino_icons name: csslib
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.0"
custom_refresh_indicator: custom_refresh_indicator:
dependency: "direct main" dependency: "direct main"
description: description:
@ -304,15 +312,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.1"
helpers:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: e9486562cb1ec5ac31fdd91ff573e99d51b2d57f
url: "https://github.com/mitch2na/helpers.git"
source: git
version: "1.2.0"
highlight: highlight:
dependency: transitive dependency: transitive
description: description:
@ -321,6 +320,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.0" version: "0.7.0"
html:
dependency: transitive
description:
name: html
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev"
source: hosted
version: "0.15.4"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -405,26 +412,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.0" version: "10.0.4"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.3"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -433,6 +440,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
logger:
dependency: transitive
description:
name: logger
sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4
url: "https://pub.dev"
source: hosted
version: "2.3.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -477,10 +492,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.12.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -497,6 +512,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
package_rename:
dependency: "direct dev"
description:
name: package_rename
sha256: "5066d021830f7a984f66a3d6912b0b95efb1d91f14322ba26ceacce2a62fd312"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
page_transition: page_transition:
dependency: transitive dependency: transitive
description: description:
@ -762,10 +785,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.0"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -882,10 +905,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "14.2.1"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -936,4 +959,4 @@ packages:
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.18.0-18.0.pre.54"

View File

@ -1,47 +1,18 @@
name: rluv name: rluv
description: A new Flutter project. description: A budget app for lovers
# The following line prevents the package from being accidentally published to publish_to: 'none'
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.19.6 <3.0.0' sdk: '>=2.19.6 <3.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dio: ^5.2.1+1 dio: ^5.2.1+1
flutter_riverpod: ^2.1.3 flutter_riverpod: ^2.1.3
helpers:
git:
url: https://github.com/mitch2na/helpers.git
ref: main
uuid: ^3.0.7 uuid: ^3.0.7
colorful_print: ^0.1.2
json_annotation: ^4.8.0 json_annotation: ^4.8.0
shared_preferences: ^2.1.2 shared_preferences: ^2.1.2
intl: ^0.18.1 intl: ^0.18.1
@ -53,52 +24,19 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.3.3 build_runner: ^2.3.3
json_serializable: ^6.6.0 json_serializable: ^6.6.0
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
icons_launcher: ^2.1.3 icons_launcher: ^2.1.3
package_rename: ^1.6.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter: flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets: assets:
- assets/ - assets/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
icons_launcher: icons_launcher:
image_path: "assets/app_icon.png" image_path: "assets/app_icon.png"
platforms: platforms:

View File

@ -1,6 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html><html><head>
<html>
<head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.
@ -18,7 +16,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project."> <meta name="description" content="Package to change project configurations.">
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
@ -27,7 +25,7 @@
<link rel="apple-touch-icon" href="icons/Icon-192.png"> <link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png">
<title>rluv</title> <title>rluv</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
@ -37,7 +35,7 @@
var serviceWorkerVersion = null; var serviceWorkerVersion = null;
</script> </script>
<!-- This script adds the flutter initialization JS code --> <!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script> <script src="flutter.js" defer=""></script>
</head> </head>
<body> <body>
<script> <script>
@ -55,5 +53,7 @@
}); });
}); });
</script> </script>
</body>
</html>
</body></html>

View File

@ -5,7 +5,7 @@
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#0175C2",
"theme_color": "#0175C2", "theme_color": "#0175C2",
"description": "A new Flutter project.", "description": "Package to change project configurations.",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"prefer_related_applications": false, "prefer_related_applications": false,
"icons": [ "icons": [