Added working edit transaction
This commit is contained in:
+122
-31
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user