ui improvements

This commit is contained in:
2025-09-01 20:17:08 +02:00
parent daf4450f7a
commit 6e2f91d2c0
30 changed files with 1822 additions and 224 deletions

8
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
]
}

View File

@@ -1,6 +1,7 @@
# BASE_URL=https://peakapi.qantra.co.zw/api
BASE_URL=http://192.168.100.8:6950/api
# BASE_URL=http://192.168.1.164:6950/api
# BASE_URL=http://192.168.120.160:6950/api
# BASE_URL=http://10.10.2.92:6950/api
# BASE_URL=http://172.20.5.105:6950/api
; BASE_URL=https://peakapi.qantra.co.zw/api
BASE_URL=http://192.168.100.8:6950/api
; BASE_URL=http://192.168.1.164:6950/api
; BASE_URL=http://192.168.120.160:6950/api
; BASE_URL=http://10.10.2.92:6950/api
; BASE_URL=http://172.20.5.105:6950/api
; BASE_URL=http://10.10.3.92:6950/api

BIN
assets/apple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/google.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/landing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
assets/password.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
assets/united-states.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
assets/verify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
assets/zimbabwe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -3,6 +3,11 @@ import 'package:provider/provider.dart';
import 'package:go_router/go_router.dart';
import 'package:qpay/screens/confirm/confirm_screen.dart';
import 'package:qpay/screens/gateway/gateway_screen.dart';
import 'package:qpay/screens/onboarding/landing/landing_screen.dart';
import 'package:qpay/screens/onboarding/login/login_screen.dart';
import 'package:qpay/screens/onboarding/password/password_screen.dart';
import 'package:qpay/screens/onboarding/register/register_screen.dart';
import 'package:qpay/screens/onboarding/verify/verify_screen.dart';
import 'package:qpay/screens/pay/pay_screen.dart';
import 'package:qpay/screens/receipt/receipt_screen.dart';
import 'package:qpay/screens/recipient/recipients_screen.dart';
@@ -65,6 +70,11 @@ class _MyAppState extends State<MyApp> {
routes: [
ShellRoute(
builder: (context, state, child) {
// If the current location is an onboarding route, don't show the navbar
final location = GoRouterState.of(context).uri.toString();
if (location.startsWith('/onboarding/')) {
return child;
}
return ScaffoldWithNavBar(child: child);
},
routes: [
@@ -102,6 +112,26 @@ class _MyAppState extends State<MyApp> {
path: '/profile',
builder: (context, state) => const ProfileScreen(),
),
GoRoute(
path: '/onboarding/landing',
builder: (context, state) => const LandingScreen(),
),
GoRoute(
path: '/onboarding/register',
builder: (context, state) => const RegisterScreen(),
),
GoRoute(
path: '/onboarding/verify',
builder: (context, state) => const VerifyScreen(),
),
GoRoute(
path: '/onboarding/password',
builder: (context, state) => const PasswordScreen(),
),
GoRoute(
path: '/onboarding/login',
builder: (context, state) => const LoginScreen(),
),
],
),
],

View File

@@ -62,11 +62,12 @@ class ConfirmController extends ChangeNotifier {
.formData = transactionController.model.formData.copyWith(
type: "REQUEST",
trace: transactionController.model.confirmationData['trace'],
id: transactionController.model.confirmationData['id'],
authType: transactionController.model.selectedPaymentProcessor.authType,
);
dynamic response = await http.post(
'/transaction',
'/public/transaction',
transactionController.model.formData,
);
logger.i(response.toString());
@@ -107,12 +108,11 @@ class ConfirmController extends ChangeNotifier {
Future<void> pollTransaction(String uid) async {
while (!model.isCancelled) {
// Check again after delay in case it was cancelled during the delay
if (model.isCancelled) break;
try {
dynamic response = await http.get('/transaction/poll/$uid');
dynamic response = await http.get('/public/transaction/poll/$uid');
logger.i(response.toString());
if (response['status'] == 'SUCCESS') {

View File

@@ -200,6 +200,13 @@ class _ConfirmScreenState extends State<ConfirmScreen>
],
),
const SizedBox(height: 10),
if (controller
.transactionController
.model
.confirmationData["charge"] !=
0)
Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
@@ -222,6 +229,15 @@ class _ConfirmScreenState extends State<ConfirmScreen>
],
),
const SizedBox(height: 10),
],
),
if (controller
.transactionController
.model
.confirmationData["gatewayCharge"] !=
0)
Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
@@ -244,6 +260,15 @@ class _ConfirmScreenState extends State<ConfirmScreen>
],
),
const SizedBox(height: 10),
],
),
if (controller
.transactionController
.model
.confirmationData["tax"] !=
0)
Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
@@ -266,6 +291,8 @@ class _ConfirmScreenState extends State<ConfirmScreen>
],
),
const SizedBox(height: 10),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
@@ -287,11 +314,28 @@ class _ConfirmScreenState extends State<ConfirmScreen>
),
],
),
const SizedBox(height: 5),
Text(
"NB: Your payment provider may charge additional fees.",
style: TextStyle(
fontSize: 11,
color: Colors.red,
),
),
],
),
),
const SizedBox(height: 20),
_buildPaymentProcessorButton(),
const SizedBox(height: 20),
Center(
child: OutlinedButton(
child: Text('Cancel'),
onPressed: () {
context.go('/');
},
),
),
],
),
),

View File

@@ -58,7 +58,7 @@ class GatewayController extends ChangeNotifier {
if (model.isCancelled) break;
try {
dynamic response = await http.get('/transaction/poll/$uid');
dynamic response = await http.get('/public/transaction/poll/$uid');
logger.i(response.toString());
if (response['status'] == 'SUCCESS') {
@@ -85,7 +85,7 @@ class GatewayController extends ChangeNotifier {
}
try {
dynamic response = await http.get('/transaction/poll/$uid');
dynamic response = await http.get('/public/transaction/poll/$uid');
logger.i(response.toString());
if (response['status'] == 'SUCCESS') {

View File

@@ -37,7 +37,7 @@ class HistoryController extends ChangeNotifier {
try {
params['type'] = 'REQUEST';
Map<String, dynamic> response = await http.get(
'/transaction?${buildQueryParameters(params)}',
'/public/transaction?${buildQueryParameters(params)}',
);
if (params['page'] == '0') {

View File

@@ -11,6 +11,7 @@ class HomeScreenModel {
List<Category> categories = [];
List<BillProvider> providers = [];
List<BillProvider> filterableProviders = [];
List<Account> accounts = [];
BillProvider? selectedProvider;
dynamic selectedCategory;
dynamic account = {};
@@ -20,6 +21,26 @@ class HomeScreenModel {
String? errorMessage;
}
class Account {
late String name;
late String description;
late String image;
late String currency;
late String balance;
late String availableBalance;
late String accountNumber;
late String accountType;
late String accountHolderName;
Account({
required this.name,
required this.description,
required this.image,
required this.currency,
required this.balance,
});
}
@freezed
abstract class Category with _$Category {
const factory Category({
@@ -76,12 +97,17 @@ class HomeController extends ChangeNotifier {
});
}
Future<void> getAccounts() async {
model.accounts = getFakeAccounts();
notifyListeners();
}
Future<void> getProviders() async {
try {
model.isLoading = true;
notifyListeners();
List<dynamic> response = await http.get('/providers');
List<dynamic> response = await http.get('/public/providers');
model.providers = response.map((e) => BillProvider.fromJson(e)).toList();
model.filterableProviders = model.providers;
@@ -116,7 +142,7 @@ class HomeController extends ChangeNotifier {
try {
Map<String, dynamic> response = await http.get(
'/transaction?sort=createdAt,desc&size=3&page=0&userId=$userId&type=REQUEST',
'/public/transaction?sort=createdAt,desc&size=3&page=0&userId=$userId&type=REQUEST',
);
PageableModel pageableModel = PageableModel.fromJson(response);
@@ -146,7 +172,7 @@ class HomeController extends ChangeNotifier {
// Simulate fetching categories
try {
List<dynamic> response = await http.get('/categories');
List<dynamic> response = await http.get('/public/categories');
model.categories = response.map((e) => Category.fromJson(e)).toList();
if (model.categories.isNotEmpty) {
@@ -246,4 +272,23 @@ class HomeController extends ChangeNotifier {
),
];
}
List<Account> getFakeAccounts() {
return [
Account(
name: 'Vusumuzi Khoza',
description: 'USD Wallet',
image: 'united-states.png',
currency: 'USD',
balance: '4.02',
),
Account(
name: 'Vusumuzi Khoza',
description: 'ZWG Wallet',
image: 'zimbabwe.png',
currency: 'ZWL',
balance: '36.00',
),
];
}
}

View File

@@ -80,6 +80,7 @@ class _HomeScreenState extends State<HomeScreen>
Future<void> setupDataAndTransitions() async {
homeController = HomeController(context);
await homeController.getAccounts();
await homeController.getCategories();
await homeController.getProviders();
@@ -108,20 +109,7 @@ class _HomeScreenState extends State<HomeScreen>
}
getBoxDecoration(String? clientId) {
// BillProvider? provider = homeController.model.selectedProvider;
//
// if (provider != null && clientId != provider.clientId) {
// return BoxDecoration(
// border: Border.all(color: Colors.black87.withAlpha(20)),
// borderRadius: BorderRadius.circular(12),
// );
// }
return BoxDecoration(
// border: Border.all(
// color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.7),
// width: 3,
// ),
border: Border.all(color: Colors.black87.withAlpha(20)),
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
@@ -161,12 +149,45 @@ class _HomeScreenState extends State<HomeScreen>
surfaceTintColor: Colors.transparent,
floating: true,
title: Image(image: AssetImage('assets/peak.png'), width: 85),
actions: [
leading: IconButton(
icon: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
border: Border.all(color: Colors.black87.withAlpha(20)),
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.1),
Theme.of(
context,
).colorScheme.tertiary.withValues(alpha: 0.05),
],
stops: const [0.3, 0.7],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
),
),
child: Center(
child: Text(
'VK',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
),
onPressed: () {},
),
// actions: [
// IconButton(
// icon: const Icon(Icons.notifications_outlined),
// onPressed: () {},
// ),
],
// ],
),
SliverToBoxAdapter(
child: Padding(
@@ -177,29 +198,8 @@ class _HomeScreenState extends State<HomeScreen>
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_isLoggedIn)
AlignTransition(
alignment: _alignAnimation,
child: Skeletonizer(
enabled: _loading,
child: Column(
children: [
Text(
"Vusumuzi Khoza",
style: TextStyle(fontSize: 15),
),
Text(
"USD 4.02",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
],
),
),
),
if (!_isLoggedIn) _buildLogin(homeController),
if (_isLoggedIn) _buildAccounts(homeController),
_buildFilterChips(homeController),
SizedBox(height: 10),
Column(
@@ -209,7 +209,7 @@ class _HomeScreenState extends State<HomeScreen>
),
],
),
const SizedBox(height: 20),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
@@ -273,6 +273,174 @@ class _HomeScreenState extends State<HomeScreen>
);
}
Widget _buildLogin(HomeController controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'Register or Login for more',
style: TextStyle(fontSize: 16),
),
InkWell(
onTap: () {
_showFeaturesPopup(context);
},
borderRadius: BorderRadius.circular(5),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2),
child: Text(
'features',
style: TextStyle(
fontSize: 16,
decoration: TextDecoration.underline,
decorationColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.primary,
),
),
),
),
],
),
InkWell(
onTap: () {
context.push('/onboarding/landing');
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
),
child: Text('Get Started', style: TextStyle(fontSize: 12)),
),
),
],
),
SizedBox(height: 10),
Divider(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.2),
),
],
);
}
Widget _buildAccounts(HomeController controller) {
return Column(
children: [
Row(
children: [
...controller.model.accounts.map(
(e) => Row(
children: [
Skeletonizer(
enabled: controller.model.isLoading,
child: SlideTransition(
position: _alignListAnimations[0],
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () {
// context.push("/wallet");
},
child: Container(
width: 150,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
decoration: BoxDecoration(
border: Border.all(
color: Colors.black87.withAlpha(20),
),
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
colors: [
Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.1),
Theme.of(
context,
).colorScheme.tertiary.withValues(alpha: 0.05),
],
stops: const [0.3, 0.7],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/${e.image}', width: 20),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
e.currency,
style: TextStyle(
fontSize: 22,
color: Colors.black,
fontWeight: FontWeight.w100,
),
),
SizedBox(width: 5),
Text(
e.balance,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 22,
color: Colors.black,
),
),
],
),
SizedBox(height: 5),
InkWell(
onTap: () {
// context.push("/withdraw");
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary
.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'Add Money',
style: TextStyle(fontSize: 12),
),
),
),
],
),
),
),
),
),
SizedBox(width: 10),
],
),
),
],
),
SizedBox(height: 10),
],
);
}
Widget _buildTransactionItem(Map<String, dynamic> e, BuildContext context) {
return SlideTransition(
position: _alignListAnimations[0],
@@ -463,4 +631,197 @@ class _HomeScreenState extends State<HomeScreen>
),
);
}
void _showFeaturesPopup(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 0,
backgroundColor: Colors.transparent,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header with icon and title
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Image(image: AssetImage("assets/peak.png"), width: 45),
),
const SizedBox(height: 16),
Text(
'Unlock Additional Features',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Register or login to access these great features',
style: TextStyle(
fontSize: 14,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// Features list
_buildFeatureItem(
Icons.account_balance_wallet,
'Dual Currency Wallets',
'USD and ZWG wallets for prepayments that can be utilized later',
),
const SizedBox(height: 16),
_buildFeatureItem(
Icons.history,
'Transaction History',
'All transactions are saved for reference from any device',
),
const SizedBox(height: 16),
_buildFeatureItem(
Icons.people,
'Recipient Management',
'Save and manage recipient details for future use',
),
const SizedBox(height: 16),
_buildFeatureItem(
Icons.schedule,
'Smart Payments',
'Schedule payments and split bills with friends',
),
const SizedBox(height: 24),
// Action buttons
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Maybe Later',
style: TextStyle(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
fontSize: 16,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
context.push('/onboarding/landing');
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
backgroundColor:
Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Get Started',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
],
),
),
);
},
);
}
Widget _buildFeatureItem(IconData icon, String title, String description) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 8),
Text(
description,
style: TextStyle(
fontSize: 14,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.7),
height: 1.4,
),
),
],
),
),
],
);
}
}

View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:qpay/screens/onboarding/login/login_controller.dart';
class LandingScreen extends StatefulWidget {
const LandingScreen({super.key});
@override
State<LandingScreen> createState() => _LandingScreenState();
}
class _LandingScreenState extends State<LandingScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 75),
Text(
'Welcome to Peak',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
SizedBox(height: 5),
Text(
'The fastest way to pay for local goods and services',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
Image.asset('assets/landing.png', width: 300),
Image.asset('assets/peak.png', width: 100),
SizedBox(height: 5),
Text(
'Register or login to get started',
style: TextStyle(fontSize: 16),
),
SizedBox(height: 10),
],
),
),
),
bottomNavigationBar: SizedBox(
height: 130,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
context.go('/onboarding/register');
},
child: Text('Register', style: TextStyle(fontSize: 16)),
),
),
SizedBox(width: 16),
Expanded(
child: TextButton(
onPressed: () {
context.go('/onboarding/login');
},
child: Text('Login', style: TextStyle(fontSize: 16)),
),
),
],
),
),
SizedBox(height: 5),
TextButton(
onPressed: () {
context.go('/');
},
child: Text('Continue as guest?', style: TextStyle(fontSize: 14)),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,80 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:qpay/http/http.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginScreenModel {
String username = "";
String password = "";
String status = "PENDING";
bool isLoading = false;
String? errorMessage;
}
class LoginController extends ChangeNotifier {
final Http http = Http();
final model = LoginScreenModel();
late SharedPreferences prefs;
BuildContext context;
LoginController(this.context);
void _showErrorSnackBar(String? message) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message.toString()),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.deepOrange,
),
);
});
}
Future<LoginScreenModel> login(String username, String password) async {
try {
model.isLoading = true;
notifyListeners();
prefs = await SharedPreferences.getInstance();
dynamic response = await http.post('/auth/login', {
'username': username,
'password': password,
});
logger.i(response.toString());
if (response.containsKey('token')) {
model.status = 'SUCCESS';
prefs.setString('token', response['token']);
prefs.setString('username', username);
prefs.setString('email', response['email']);
prefs.setString('firstName', response['firstName']);
prefs.setString('lastName', response['lastName']);
prefs.setString('phone', response['phone']);
return model;
} else {
model.errorMessage =
response['errorMessage'] ??
"Problem with the transaction. Please try again or contact support";
_showErrorSnackBar(model.errorMessage);
}
} on DioException catch (e, s) {
logger.e(s);
_showErrorSnackBar("Network error. Please try again or contact support");
} catch (e, s) {
logger.e(s);
_showErrorSnackBar(
"Problem processing that transaction. Please try again or contact support",
);
}
model.isLoading = false;
notifyListeners();
return model;
}
}

View File

@@ -0,0 +1,231 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:qpay/screens/onboarding/login/login_controller.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
late LoginController _loginController;
@override
void initState() {
super.initState();
_loginController = LoginController(context);
}
@override
void dispose() {
_loginController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 75),
Text(
'Login',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
SizedBox(height: 5),
Text(
'Welcome back to Peak',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
SizedBox(height: 40),
// Username Field
TextFormField(
controller: _usernameController,
decoration: InputDecoration(
labelText: 'Phone Number',
prefixIcon: Icon(Icons.person_outline),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a phone number';
}
return null;
},
),
SizedBox(height: 20),
// Password Field
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: Icon(Icons.visibility_outlined),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a password';
}
return null;
},
),
SizedBox(height: 20),
// Forgot Password Link
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
// TODO: Add forgot password logic
},
child: Text(
'Forgot Password?',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 14,
),
),
),
),
SizedBox(height: 40),
// Divider with "or" text
Row(
children: [
Expanded(child: Divider(color: Colors.grey.shade300)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
'or continue with',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
),
),
Expanded(child: Divider(color: Colors.grey.shade300)),
],
),
SizedBox(height: 30),
// Social Login Buttons
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
// TODO: Add Google login logic
},
icon: Image.asset(
'assets/google.png',
width: 20,
height: 20,
),
label: Text('Google', style: TextStyle(fontSize: 16)),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
SizedBox(width: 16),
Expanded(
child: OutlinedButton.icon(
onPressed: () {
// TODO: Add Apple login logic
},
icon: Image.asset(
'assets/apple.png',
width: 20,
height: 20,
),
label: Text('Apple', style: TextStyle(fontSize: 16)),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
SizedBox(height: 40),
],
),
),
),
),
bottomNavigationBar: SizedBox(
height: 130,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () async {
// TODO: Add login logic
if (_formKey.currentState!.validate()) {
LoginScreenModel model = await _loginController.login(
_usernameController.text,
_passwordController.text,
);
if (model.status == 'SUCCESS') {
context.go('/');
}
}
},
child: Text('Login', style: TextStyle(fontSize: 16)),
),
),
SizedBox(width: 16),
Expanded(
child: TextButton(
onPressed: () {
context.go('/onboarding/register');
},
child: Text(
'Create Account',
style: TextStyle(fontSize: 16),
),
),
),
],
),
),
SizedBox(height: 5),
TextButton(
onPressed: () {
context.go('/');
},
child: Text('Continue as guest?', style: TextStyle(fontSize: 14)),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,255 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class PasswordScreen extends StatefulWidget {
const PasswordScreen({super.key});
@override
State<PasswordScreen> createState() => _PasswordScreenState();
}
class _PasswordScreenState extends State<PasswordScreen> {
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _confirmPasswordController =
TextEditingController();
final FocusNode _passwordFocusNode = FocusNode();
final FocusNode _confirmPasswordFocusNode = FocusNode();
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
@override
void dispose() {
_passwordController.dispose();
_confirmPasswordController.dispose();
_passwordFocusNode.dispose();
_confirmPasswordFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 75),
Text(
'Set New Password',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
SizedBox(height: 5),
Text(
'Create a strong password to secure your account',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
Image.asset('assets/password.png', width: 300),
// Password Input
TextField(
controller: _passwordController,
focusNode: _passwordFocusNode,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: 'New Password',
hintText: 'Enter your new password',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey.shade600,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
),
SizedBox(height: 20),
// Confirm Password Input
TextField(
controller: _confirmPasswordController,
focusNode: _confirmPasswordFocusNode,
obscureText: _obscureConfirmPassword,
decoration: InputDecoration(
labelText: 'Confirm Password',
hintText: 'Confirm your new password',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey.shade600,
),
onPressed: () {
setState(() {
_obscureConfirmPassword = !_obscureConfirmPassword;
});
},
),
),
),
SizedBox(height: 20),
// Password Requirements
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Password Requirements:',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: Colors.grey.shade700,
),
),
SizedBox(height: 8),
_buildRequirement('At least 8 characters', true),
_buildRequirement('Contains uppercase letter', true),
_buildRequirement('Contains lowercase letter', true),
_buildRequirement('Contains number', true),
_buildRequirement('Contains special character', false),
],
),
),
SizedBox(height: 40),
],
),
),
),
bottomNavigationBar: SizedBox(
height: 130,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
// TODO: Add password update logic
String password = _passwordController.text;
String confirmPassword =
_confirmPasswordController.text;
print('Password: $password');
print('Confirm Password: $confirmPassword');
context.go('/onboarding/login');
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Update Password',
style: TextStyle(fontSize: 16),
),
),
),
SizedBox(width: 16),
Expanded(
child: OutlinedButton(
onPressed: () {
context.go('/onboarding/login');
},
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Back to Login',
style: TextStyle(fontSize: 16),
),
),
),
],
),
),
SizedBox(height: 20),
TextButton(
onPressed: () {
context.go('/');
},
child: Text('Continue as guest?', style: TextStyle(fontSize: 14)),
),
],
),
),
);
}
Widget _buildRequirement(String text, bool isMet) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
Icon(
isMet ? Icons.check_circle : Icons.circle_outlined,
size: 16,
color: isMet ? Colors.green : Colors.grey.shade600,
),
SizedBox(width: 8),
Text(
text,
style: TextStyle(
fontSize: 12,
color: isMet ? Colors.grey.shade600 : Colors.grey.shade400,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,194 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class RegisterScreen extends StatefulWidget {
const RegisterScreen({super.key});
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 75),
Text(
'Registration',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
SizedBox(height: 5),
Text(
'Join Peak and start making fast payments',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
SizedBox(height: 40),
// First Name Field
TextField(
decoration: InputDecoration(
labelText: 'First Name',
prefixIcon: Icon(Icons.person_outline),
),
),
SizedBox(height: 20),
// Last Name Field
TextField(
decoration: InputDecoration(
labelText: 'Last Name',
prefixIcon: Icon(Icons.person_outline),
),
),
SizedBox(height: 20),
// Phone Field
TextField(
decoration: InputDecoration(
labelText: 'Phone Number',
prefixIcon: Icon(Icons.phone_outlined),
),
keyboardType: TextInputType.phone,
),
SizedBox(height: 20),
// Email Field
TextField(
decoration: InputDecoration(
labelText: 'Email Address',
prefixIcon: Icon(Icons.email_outlined),
),
keyboardType: TextInputType.emailAddress,
),
SizedBox(height: 20),
// Password Field
TextField(
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: Icon(Icons.visibility_outlined),
),
obscureText: true,
),
SizedBox(height: 40),
// Divider with "or" text
Row(
children: [
Expanded(child: Divider(color: Colors.grey.shade300)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
'or continue with',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
),
),
Expanded(child: Divider(color: Colors.grey.shade300)),
],
),
SizedBox(height: 30),
// Social Login Buttons
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
// TODO: Add Google login logic
},
icon: Image.asset(
'assets/google.png',
width: 20,
height: 20,
),
label: Text('Google', style: TextStyle(fontSize: 16)),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
SizedBox(width: 16),
Expanded(
child: OutlinedButton.icon(
onPressed: () {
// TODO: Add Apple login logic
},
icon: Image.asset(
'assets/apple.png',
width: 20,
height: 20,
),
label: Text('Apple', style: TextStyle(fontSize: 16)),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
SizedBox(height: 40),
],
),
),
),
bottomNavigationBar: SizedBox(
height: 130,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
context.go('/onboarding/verify');
},
child: Text('Submit', style: TextStyle(fontSize: 16)),
),
),
SizedBox(width: 16),
Expanded(
child: TextButton(
onPressed: () {
context.go('/onboarding/login');
},
child: Text(
'Go to Login',
style: TextStyle(fontSize: 16),
),
),
),
],
),
),
SizedBox(height: 5),
TextButton(
onPressed: () {
context.go('/');
},
child: Text('Continue as guest?', style: TextStyle(fontSize: 14)),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class VerifyScreen extends StatefulWidget {
const VerifyScreen({super.key});
@override
State<VerifyScreen> createState() => _VerifyScreenState();
}
class _VerifyScreenState extends State<VerifyScreen> {
final List<TextEditingController> _codeControllers = List.generate(
6,
(index) => TextEditingController(),
);
final List<FocusNode> _focusNodes = List.generate(6, (index) => FocusNode());
@override
void dispose() {
for (var controller in _codeControllers) {
controller.dispose();
}
for (var node in _focusNodes) {
node.dispose();
}
super.dispose();
}
void _onCodeChanged(String value, int index) {
if (value.length == 1 && index < 5) {
_focusNodes[index + 1].requestFocus();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 75),
Text(
'Verify Your Account',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
SizedBox(height: 5),
Text(
'Enter the 6-digit code sent to your phone/email',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
Image.asset('assets/verify.png', width: 300),
// Verification Code Input
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(
6,
(index) => SizedBox(
width: 50,
child: TextField(
controller: _codeControllers[index],
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
decoration: InputDecoration(
counterText: '',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2,
),
),
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
onChanged: (value) => _onCodeChanged(value, index),
),
),
),
),
SizedBox(height: 30),
// Resend Code Section
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Didn't receive the code? ",
style: TextStyle(color: Colors.grey.shade600, fontSize: 16),
),
TextButton(
onPressed: () {
// TODO: Add resend code logic
},
child: Text(
'Resend Code',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
],
),
SizedBox(height: 20),
// Timer for resend (optional)
Text(
'Resend available in 2:30',
style: TextStyle(color: Colors.grey.shade500, fontSize: 14),
),
SizedBox(height: 40),
],
),
),
),
bottomNavigationBar: SizedBox(
height: 130,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
context.go('/onboarding/password');
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Verify Code',
style: TextStyle(fontSize: 16),
),
),
),
SizedBox(width: 16),
Expanded(
child: OutlinedButton(
onPressed: () {
context.go('/onboarding/login');
},
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Back to Login',
style: TextStyle(fontSize: 16),
),
),
),
],
),
),
SizedBox(height: 20),
TextButton(
onPressed: () {
context.go('/');
},
child: Text('Continue as guest?', style: TextStyle(fontSize: 14)),
),
],
),
),
);
}
}

View File

@@ -112,7 +112,7 @@ class PayController extends ChangeNotifier {
);
dynamic response = await http.post(
'/transaction',
'/public/transaction',
transactionController.model.formData,
);
logger.i(response.toString());
@@ -148,7 +148,7 @@ class PayController extends ChangeNotifier {
model.isLoading = true;
List<dynamic> response = await http.get(
'/providers/$providerId/products',
'/public/providers/$providerId/products',
);
model.products = response.map((e) => BillProduct.fromJson(e)).toList();
} catch (e) {
@@ -168,7 +168,7 @@ class PayController extends ChangeNotifier {
model.paymentProcessors = getFakePaymentProcessors();
notifyListeners();
List<dynamic> response = await http.get('/payment-processors');
List<dynamic> response = await http.get('/public/payment-processors');
model.paymentProcessors =
response.map((e) => PaymentProcessor.fromJson(e)).toList();

View File

@@ -50,7 +50,7 @@ class ReceiptController extends ChangeNotifier {
// fetch provider & update tran controller
String providerId = model.receiptData?['billClientId'] ?? '';
Map<String, dynamic> provider = await http.get(
'/providers/client/$providerId',
'/public/providers/client/$providerId',
);
transactionController.model.selectedProvider = hc.BillProvider.fromJson(
provider,
@@ -72,7 +72,7 @@ class ReceiptController extends ChangeNotifier {
if (productUid.isNotEmpty) {
Map<String, dynamic> product = await http.get(
'/providers/$providerUid/products/$productUid',
'/public/providers/$providerUid/products/$productUid',
);
transactionController.model.selectedProduct = pc.BillProduct.fromJson(
product,
@@ -96,7 +96,7 @@ class ReceiptController extends ChangeNotifier {
Future<void> getReceiptData(String id) async {
setLoading(true);
try {
Map<String, dynamic> response = await http.get('/transaction/$id');
Map<String, dynamic> response = await http.get('/public/transaction/$id');
model.receiptData = response;
} catch (e) {
_showErrorSnackBar(

View File

@@ -212,7 +212,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
Text(
receiptController
.model
.receiptData?["totalAmount"]
.receiptData?["amount"]
.toStringAsFixed(2) ??
"",
style: TextStyle(fontSize: 26),
@@ -447,6 +447,37 @@ class _ReceiptScreenState extends State<ReceiptScreen>
),
),
const SizedBox(height: 10),
Skeletonizer(
enabled:
receiptController
.model
.isLoading,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(
"Debit Reference",
style: TextStyle(
fontSize: 16,
fontWeight:
FontWeight.bold,
),
),
Text(
transactionController
.model
.receiptData?["debitRef"] ??
"",
style: TextStyle(
fontSize: 16,
),
),
],
),
),
const SizedBox(height: 10),
Skeletonizer(
enabled:
receiptController
@@ -613,6 +644,12 @@ class _ReceiptScreenState extends State<ReceiptScreen>
],
),
const SizedBox(height: 10),
if (transactionController
.model
.receiptData?["charge"] !=
0)
Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
@@ -623,7 +660,8 @@ class _ReceiptScreenState extends State<ReceiptScreen>
style: TextStyle(
fontSize: 16,
fontWeight:
FontWeight.bold,
FontWeight
.bold,
),
),
Text(
@@ -640,7 +678,17 @@ class _ReceiptScreenState extends State<ReceiptScreen>
),
],
),
const SizedBox(height: 10),
const SizedBox(
height: 10,
),
],
),
if (transactionController
.model
.receiptData?["gatewayCharge"] !=
0)
Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
@@ -651,7 +699,8 @@ class _ReceiptScreenState extends State<ReceiptScreen>
style: TextStyle(
fontSize: 16,
fontWeight:
FontWeight.bold,
FontWeight
.bold,
),
),
Text(
@@ -668,7 +717,17 @@ class _ReceiptScreenState extends State<ReceiptScreen>
),
],
),
const SizedBox(height: 10),
const SizedBox(
height: 10,
),
],
),
if (transactionController
.model
.receiptData?["tax"] !=
0)
Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
@@ -679,7 +738,8 @@ class _ReceiptScreenState extends State<ReceiptScreen>
style: TextStyle(
fontSize: 16,
fontWeight:
FontWeight.bold,
FontWeight
.bold,
),
),
Text(
@@ -696,7 +756,11 @@ class _ReceiptScreenState extends State<ReceiptScreen>
),
],
),
const SizedBox(height: 10),
const SizedBox(
height: 10,
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment

View File

@@ -67,7 +67,7 @@ class RecipientsController extends ChangeNotifier {
try {
List<dynamic> response = await http.get(
'/recipients/search?${buildQueryParameters(params ?? {})}',
'/public/recipients/search?${buildQueryParameters(params ?? {})}',
);
model.recipients.clear();
model.recipients.addAll(response.map((e) => Recipient.fromJson(e)));

View File

@@ -15,6 +15,7 @@ class TransactionModel {
BillProvider? selectedProvider;
Map<String, dynamic>? receiptData;
FormData formData = FormData(
id: '',
type: '',
billClientId: '',
debitRef: '',
@@ -49,6 +50,7 @@ abstract class FormData with _$FormData {
const factory FormData({
required String type, // CONFIRM, REQUEST, REVERSE
required String billClientId,
required String id,
required String debitRef,
required String debitCurrency,
required String amount,

View File

@@ -17,7 +17,7 @@ T _$identity<T>(T value) => value;
mixin _$FormData {
String get type;// CONFIRM, REQUEST, REVERSE
String get billClientId; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount;
String get billClientId; String get id; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount;
/// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -30,16 +30,16 @@ $FormDataCopyWith<FormData> get copyWith => _$FormDataCopyWithImpl<FormData>(thi
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount));
return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.id, id) || other.id == id)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]);
int get hashCode => Object.hashAll([runtimeType,type,billClientId,id,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]);
@override
String toString() {
return 'FormData(type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)';
return 'FormData(type: $type, billClientId: $billClientId, id: $id, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)';
}
@@ -50,7 +50,7 @@ abstract mixin class $FormDataCopyWith<$Res> {
factory $FormDataCopyWith(FormData value, $Res Function(FormData) _then) = _$FormDataCopyWithImpl;
@useResult
$Res call({
String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount
String type, String billClientId, String id, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount
});
@@ -67,10 +67,11 @@ class _$FormDataCopyWithImpl<$Res>
/// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? billClientId = null,Object? id = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) {
return _then(_self.copyWith(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,billClientId: null == billClientId ? _self.billClientId : billClientId // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,debitRef: null == debitRef ? _self.debitRef : debitRef // ignore: cast_nullable_to_non_nullable
as String,debitCurrency: null == debitCurrency ? _self.debitCurrency : debitCurrency // ignore: cast_nullable_to_non_nullable
as String,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable
@@ -107,12 +108,13 @@ as String?,
@JsonSerializable()
class _FormData implements FormData {
const _FormData({required this.type, required this.billClientId, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, this.debitPhone, this.debitAccount, this.productUid, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount});
const _FormData({required this.type, required this.billClientId, required this.id, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, this.debitPhone, this.debitAccount, this.productUid, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount});
factory _FormData.fromJson(Map<String, dynamic> json) => _$FormDataFromJson(json);
@override final String type;
// CONFIRM, REQUEST, REVERSE
@override final String billClientId;
@override final String id;
@override final String debitRef;
@override final String debitCurrency;
@override final String amount;
@@ -152,16 +154,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.id, id) || other.id == id)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]);
int get hashCode => Object.hashAll([runtimeType,type,billClientId,id,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]);
@override
String toString() {
return 'FormData(type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)';
return 'FormData(type: $type, billClientId: $billClientId, id: $id, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)';
}
@@ -172,7 +174,7 @@ abstract mixin class _$FormDataCopyWith<$Res> implements $FormDataCopyWith<$Res>
factory _$FormDataCopyWith(_FormData value, $Res Function(_FormData) _then) = __$FormDataCopyWithImpl;
@override @useResult
$Res call({
String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount
String type, String billClientId, String id, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount
});
@@ -189,10 +191,11 @@ class __$FormDataCopyWithImpl<$Res>
/// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? billClientId = null,Object? id = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) {
return _then(_FormData(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,billClientId: null == billClientId ? _self.billClientId : billClientId // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,debitRef: null == debitRef ? _self.debitRef : debitRef // ignore: cast_nullable_to_non_nullable
as String,debitCurrency: null == debitCurrency ? _self.debitCurrency : debitCurrency // ignore: cast_nullable_to_non_nullable
as String,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable

View File

@@ -9,6 +9,7 @@ part of 'transaction_controller.dart';
_FormData _$FormDataFromJson(Map<String, dynamic> json) => _FormData(
type: json['type'] as String,
billClientId: json['billClientId'] as String,
id: json['id'] as String,
debitRef: json['debitRef'] as String,
debitCurrency: json['debitCurrency'] as String,
amount: json['amount'] as String,
@@ -39,6 +40,7 @@ _FormData _$FormDataFromJson(Map<String, dynamic> json) => _FormData(
Map<String, dynamic> _$FormDataToJson(_FormData instance) => <String, dynamic>{
'type': instance.type,
'billClientId': instance.billClientId,
'id': instance.id,
'debitRef': instance.debitRef,
'debitCurrency': instance.debitCurrency,
'amount': instance.amount,

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class AppTheme {
static const _primaryColor = Color(0xFFD4AF37); // Gold
static const _primaryColor = Color(0xFF9D6711); // #9D6711
static const _secondaryColor = Color(0xFF8B0000); // Dark Red
static const _tertiaryColor = Color(0xFF000000); // Black
static const _backgroundColor = Color(0xFFFFFBFE);
@@ -56,19 +56,19 @@ class AppTheme {
fillColor: _surfaceColor,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
borderSide: BorderSide(color: Colors.grey.shade300, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
borderSide: BorderSide(color: Colors.grey.shade300, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _primaryColor),
borderSide: const BorderSide(color: _primaryColor, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _errorColor),
borderSide: const BorderSide(color: _errorColor, width: 1),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
),