Added working edit transaction

This commit is contained in:
Nathan Anderson
2023-07-22 21:29:32 -06:00
parent eba628bb4c
commit 18aad2b3d5
28 changed files with 1959 additions and 430 deletions
+122 -31
View File
@@ -1,4 +1,7 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:helpers/helpers/print.dart';
class Api {
@@ -10,38 +13,40 @@ class Api {
_instance.initDone = true;
_instance.dio = Dio();
_instance.dio.options.baseUrl = "http://localhost:8081/";
// _instance.dio.options.baseUrl = "https://rluv.fosscat.com/";
_instance.dio.interceptors.add(
InterceptorsWrapper(
onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
// Do something before request is sent.
// If you want to resolve the request with custom data,
// you can resolve a `Response` using `handler.resolve(response)`.
// If you want to reject the request with a error message,
// you can reject with a `DioException` using `handler.reject(dioError)`.
return handler.next(options);
},
onResponse: (Response response, ResponseInterceptorHandler handler) {
if (response.statusCode != null &&
response.statusCode! < 500 &&
response.statusCode! >= 400) {
return handler.reject(DioException.badResponse(
requestOptions: RequestOptions(),
response: response,
statusCode: response.statusCode!));
}
// Do something with response data.
// If you want to reject the request with a error message,
// you can reject a `DioException` object using `handler.reject(dioError)`.
return handler.next(response);
},
onError: (DioException e, ErrorInterceptorHandler handler) {
printPink(e);
// Do something with response error.
// If you want to resolve the request with some custom data,
// you can resolve a `Response` object using `handler.resolve(response)`.
return handler.next(e);
},
),
LoggingInterceptor(),
// InterceptorsWrapper(
// onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
// // Do something before request is sent.
// // If you want to resolve the request with custom data,
// // you can resolve a `Response` using `handler.resolve(response)`.
// // If you want to reject the request with a error message,
// // you can reject with a `DioException` using `handler.reject(dioError)`.
// return handler.next(options);
// },
// onResponse: (Response response, ResponseInterceptorHandler handler) {
// if (response.statusCode != null &&
// response.statusCode! < 500 &&
// response.statusCode! >= 400) {
// return handler.reject(DioException.badResponse(
// requestOptions: RequestOptions(),
// response: response,
// statusCode: response.statusCode!));
// }
// // Do something with response data.
// // If you want to reject the request with a error message,
// // you can reject a `DioException` object using `handler.reject(dioError)`.
// return handler.next(response);
// },
// onError: (DioException e, ErrorInterceptorHandler handler) {
// printPink(e);
// // Do something with response error.
// // If you want to resolve the request with some custom data,
// // you can resolve a `Response` object using `handler.resolve(response)`.
// return handler.next(e);
// },
// ),
);
return _instance;
}
@@ -79,6 +84,21 @@ class Api {
}
}
Future<Map<String, dynamic>?> post(
{required String path, Object? data}) async {
try {
final res = await dio.post(path, data: data);
if (res.data != null) {
return res.data as Map<String, dynamic>;
}
return null;
} catch (err) {
printRed('Error in put: $err');
return null;
}
}
Future<Map<String, dynamic>?> delete(
{required String path, Object? data}) async {
try {
@@ -94,3 +114,74 @@ class Api {
}
}
}
class LoggingInterceptor extends Interceptor {
LoggingInterceptor();
@override
Future onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
logPrint('///*** REQUEST ***\\\\\\');
printKV('URI', options.uri);
printKV('METHOD', options.method);
logPrint('HEADERS:');
options.headers.forEach((key, v) => printKV(' - $key', v));
logPrint('BODY:');
printJson(options.data);
return handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
logPrint('///*** ERROR RESPONSE ***\\\\\\');
logPrint('URI: ${err.requestOptions.uri}');
if (err.response != null) {
logPrint('STATUS CODE: ${err.response?.statusCode?.toString()}');
}
logPrint('$err');
if (err.response != null) {
printKV('REDIRECT', err.response?.realUri ?? '');
logPrint('BODY:');
printJson(err.response?.data);
}
return handler.next(err);
}
@override
Future onResponse(
Response response, ResponseInterceptorHandler handler) async {
logPrint('///*** RESPONSE ***\\\\\\');
printKV('URI', response.requestOptions.uri);
printKV('STATUS CODE', response.statusCode ?? '');
// printKV('REDIRECT', response.isRedirect);
logPrint('BODY:');
printJson(response.data);
return handler.next(response);
}
void printKV(String key, Object v) {
if (kDebugMode) {
printOrange('$key: $v');
}
}
void printJson(Map<String, dynamic>? s) {
if (kDebugMode) {
if (s == null) {
printAmber({});
return;
}
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
String prettyprint = encoder.convert(s);
printAmber(prettyprint);
}
}
void logPrint(String s) {
if (kDebugMode) {
printOrange(s);
}
}
}
+121 -17
View File
@@ -1,43 +1,70 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/print.dart';
import 'package:rluv/global/api.dart';
import 'package:rluv/models/budget.dart';
import 'package:rluv/models/budget_category_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/family_model.dart';
import '../models/shared_note.dart';
import '../models/transaction_model.dart';
import '../models/user.dart';
class Store {
static final Store _instance = Store._internal();
bool _initDone = false;
SharedPreferences? prefs;
factory Store() {
if (_instance._initDone) {
return _instance;
}
_instance._initDone = true;
_instance.dashboardProvider = FutureProvider<Map<String, dynamic>?>(
(ref) async {
final family = await ref.watch(_instance.familyProvider.future);
return Api().get("dashboard/${family.id}");
SharedPreferences.getInstance().then(
(value) => _instance.prefs = value,
);
_instance.dashboardProvider =
StateNotifierProvider<DashBoardStateNotifier, Map<String, dynamic>?>(
(ref) {
final family = ref.watch(_instance.familyProvider).valueOrNull;
return DashBoardStateNotifier(family);
},
);
_instance.budgetCategoriesProvider =
FutureProvider<List<BudgetCategory>>((ref) async {
final dash = await ref.watch(_instance.dashboardProvider.future);
printAmber(dash);
// _instance.dashboardProvider = FutureProvider<Map<String, dynamic>?>(
// (ref) async {
// final family = await ref.watch(_instance.familyProvider.future);
// return Api().get("dashboard/${family.id}");
// },
// );
_instance.budgetProvider = Provider<Budget?>(
(ref) {
final dash = ref.watch(_instance.dashboardProvider);
if (dash == null) return null;
final budgetData = dash['budget'] as Map<String, dynamic>?;
if (budgetData == null) return null;
return Budget.fromJson(budgetData);
},
);
_instance.budgetCategoriesProvider = Provider<List<BudgetCategory>>((ref) {
final dash = ref.watch(_instance.dashboardProvider);
if (dash == null) return [];
final categories = dash['budget_categories'] as List<dynamic>;
return categories
final categoriesData = dash['budget_categories'] as List<dynamic>;
final categories = categoriesData
.map(
(e) => BudgetCategory.fromJson(e as Map<String, dynamic>),
)
.toList();
if (_instance.prefs != null) {
final budgetJson = jsonEncode({'budget_categories': categoriesData});
printBlue('updated prefs stored categories');
_instance.prefs!.setString('budget_categories', budgetJson);
}
return categories;
});
_instance.transactionsProvider =
FutureProvider<List<Transaction>>((ref) async {
final dash = await ref.watch(_instance.dashboardProvider.future);
_instance.transactionsProvider = Provider<List<Transaction>>((ref) {
final dash = ref.watch(_instance.dashboardProvider);
if (dash == null) return [];
final transactions = dash['transactions'] as List<dynamic>;
return transactions
@@ -72,10 +99,87 @@ class Store {
createdAt: DateTime.now(),
updatedAt: DateTime.now()));
late final FutureProvider<List<BudgetCategory>> budgetCategoriesProvider;
late final FutureProvider<Budget> budgetProvider;
late final FutureProvider<List<Transaction>> transactionsProvider;
late final FutureProvider<Map<String, dynamic>?> dashboardProvider;
late final Provider<List<BudgetCategory>> budgetCategoriesProvider;
late final Provider<Budget?> budgetProvider;
late final Provider<List<Transaction>> transactionsProvider;
late final Provider<List<SharedNote>> sharedNotesProvider;
// late final FutureProvider<Map<String, dynamic>?> dashboardProvider;
late final StateNotifierProvider<DashBoardStateNotifier,
Map<String, dynamic>?> dashboardProvider;
void fetchDashboard() {}
}
class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
DashBoardStateNotifier(FamilyModel? family) : super(null) {
fetchDashboard(family);
}
Future fetchDashboard(FamilyModel? family) async {
if (family == null) {
printPink('Unable to get dashboard');
return;
}
printAmber('Fetching dashboard');
state = await Api().get("dashboard/${family.id}");
}
void update(Map<String, dynamic> data) {
if (state == null) {
printPink('Cant update data, state is null');
return;
}
if (data.keys.length != 1 || data.values.length != 1) {
throw Exception('Only one key/val used in update');
}
final key = data.keys.first;
switch (key) {
case 'transactions':
case 'budget_categories':
final subStateList = state![key] as List<dynamic>;
final subStateListObj = subStateList
.map(
(e) => e as Map<String, dynamic>,
)
.toList();
subStateListObj.removeWhere(
(element) =>
element['id'] ==
(data.values.first as Map<String, dynamic>)['id'],
);
subStateListObj.add(data.values.first);
final newState = state;
newState![key] = subStateListObj;
state = {...newState};
// printBlue(state);
break;
default:
break;
}
}
void add(Map<String, dynamic> data) {
if (state == null) {
printPink('Cant add data, state is null');
return;
}
if (data.keys.length != 1 || data.values.length != 1) {
throw Exception('Only one key/val used in add');
}
final key = data.keys.first;
switch (key) {
case 'transactions':
case 'budget_categories':
final subStateList = state![key] as List<dynamic>;
final newState = state;
subStateList.add(data.values.first);
newState![key] = subStateList;
state = {...newState};
// printBlue(state);
break;
default:
break;
}
}
}
+55 -3
View File
@@ -1,4 +1,4 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class Styles {
// Theme Colors
@@ -9,7 +9,7 @@ class Styles {
static const Color seaweedGreen = Color(0xFF86BA90);
static const Color emptyBarGrey = Color(0xFFC8C8C8);
static const Color lavender = Color(0xFFB8B8FF);
static const Color sand = Color(0xFFD9D9D9);
static const Color washedStone = Color(0xFFD9D9D9);
// Income Colors
static const Color incomeBlue = Color(0xFFB8B8FF);
@@ -17,5 +17,57 @@ class Styles {
// Expenses Colors
static const Color expensesOrange = Color(0xFFFA824C);
static const Color expensesRed = Color(0xFF9E0000);
static const Color expensesRed = Color(0xFFC73E1D);
static const List<Color> curatedColors = [
Color(0xFF6D326D),
Color(0xFFE58F65),
Color(0xFFD3E298),
Color(0xFFFD96A9),
Color(0xFF4F7CAC),
Color(0xFFE4B4C2),
Color(0xFFFFB8D1),
Color(0xFFDDFDFE),
Color(0xFFD7BE82),
Color(0xFFD4C5C7),
Color(0xFF8EB8E5),
Color(0xFF9893DA),
Color(0xFF99AA38),
Color(0xFFA7E2E3),
Color(0xFFF7B2BD),
Color(0xFFFEDC97),
Color(0xFFC28CAE),
Color(0xFFF1BF98),
Color(0xFFD1BCE3),
Color(0xFFBDC667),
Color(0xFFFFB563)
];
// Widget Styles
static BoxDecoration boxLavenderBubble = BoxDecoration(
color: Styles.lavender,
borderRadius: BorderRadius.circular(20.0),
);
static InputDecoration inputLavenderBubble({String? labelText}) =>
InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
labelText: labelText,
focusColor: Styles.blushingPink,
hoverColor: Styles.blushingPink,
fillColor: Styles.lavender,
border: const OutlineInputBorder(
borderSide: BorderSide.none,
),
);
static const Color dialogColor = Styles.purpleNurple;
static RoundedRectangleBorder dialogShape = RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
);
// Sizing Styles
static const dialogScreenWidthFactor = 0.75;
}
+58 -3
View File
@@ -1,5 +1,8 @@
import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:helpers/helpers/print.dart';
import 'package:intl/intl.dart';
String formatDate(DateTime time) {
@@ -8,10 +11,62 @@ String formatDate(DateTime time) {
DateTime dateFromJson(int value) => DateTime.fromMillisecondsSinceEpoch(value);
int dateToJson(DateTime? value) =>
value == null ? 0 : value.millisecondsSinceEpoch;
int? dateToJson(DateTime? value) => value?.millisecondsSinceEpoch;
bool boolFromJson(int value) => value == 1;
int boolToJson(bool hide) => hide ? 1 : 0;
String colorToJson(Color color) =>
color.toString().split('(0x')[1].split(')')[0];
Color colorFromJson(String hex) => Color(int.parse(hex, radix: 16));
String? optionalColorToJson(Color? optionalColor) => optionalColor == null
? null
: optionalColor.toString().split('(0x')[1].split(')')[0];
Color? optionalColorFromJson(String? hex) =>
hex == null ? null : Color(int.parse(hex, radix: 16));
Brightness getBrightness(Color color) =>
ThemeData.estimateBrightnessForColor(color);
List<Color> generateColorList() {
List<Color> colors = [];
for (int i = 100; i <= 255; i += 30) {
// Red value variations
for (int j = 100; j <= 255; j += 30) {
// Green value variations
for (int k = 100; k <= 255; k += 30) {
// Blue value variations
final alpha = Random().nextInt(256) + 50 % 255;
colors.add(Color.fromARGB(alpha, i, j, k));
}
}
}
colors.shuffle();
return colors;
}
extension MonetaryExtension on double {
String currency() {
final numStr = toStringAsFixed(2);
final pieces = numStr.split(".");
if (pieces.length == 1) {
return "\$${pieces.first}.00";
} else {
if (pieces.length > 2) {
printBlue(pieces);
}
return '\$${pieces[0]}.${pieces[1].padRight(2, "0")}';
}
}
}
void setDevicePortraitOrientation() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class BudgetColorCircle extends StatelessWidget {
const BudgetColorCircle(
{super.key, required this.color, this.height = 18, this.width = 18});
final double height;
final double width;
final Color color;
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: height,
width: width,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
),
Container(
height: height,
width: width,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.black, width: 1.5),
color: color,
),
),
],
);
}
}
+54
View File
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:rluv/global/styles.dart';
class CuteDrawerButton extends StatelessWidget {
const CuteDrawerButton(
{super.key,
required this.text,
required this.color,
required this.onPressed});
final String text;
final Function onPressed;
final Color color;
final double borderRadius = 12.0;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
borderRadius: BorderRadius.circular(borderRadius),
onTap: () => onPressed(),
child: Container(
decoration: BoxDecoration(
color: color,
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 2.0,
spreadRadius: 2.0,
offset: Offset(2.5, 2.5),
)
],
borderRadius: BorderRadius.circular(borderRadius),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
text,
style: const TextStyle(
fontSize: 14,
color: Styles.washedStone,
),
),
),
],
),
),
),
);
}
}