progress on home page
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
; BASE_URL=http://192.168.100.8:6950/api
|
||||
BASE_URL=http://192.168.1.164:6950/api
|
||||
BASE_URL=http://192.168.100.8:6950/api
|
||||
; BASE_URL=http://192.168.1.164:6950/api
|
||||
BIN
assets/card-background.png
Normal file
BIN
assets/card-background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
BIN
assets/econet.png
Normal file
BIN
assets/econet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/netone.png
Normal file
BIN
assets/netone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/zesa.png
Normal file
BIN
assets/zesa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
@@ -15,8 +15,8 @@ class Http {
|
||||
dio.options.receiveTimeout = const Duration(seconds: 60);
|
||||
dio.interceptors.add(LogInterceptor(
|
||||
request: true,
|
||||
responseBody: true,
|
||||
requestBody: true,
|
||||
responseBody: false,
|
||||
requestBody: false,
|
||||
error: true,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'providers/transaction_provider.dart';
|
||||
import 'screens/home/home_screen.dart';
|
||||
import 'screens/make_payment_screen.dart';
|
||||
import 'screens/pay/pay_screen.dart';
|
||||
import 'screens/history_screen.dart';
|
||||
import 'screens/profile_screen.dart';
|
||||
import 'theme/app_theme.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:qpay/screens/pay/pay_controller.dart';
|
||||
|
||||
const String testEnv = "assets/.env";
|
||||
const String liveEnv = "assets/.env";
|
||||
|
||||
void main() async {
|
||||
await dotenv.load(fileName: testEnv);
|
||||
runApp(const MyApp());
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => PayController(),
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@@ -39,7 +44,7 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
GoRoute(
|
||||
path: '/make-payment',
|
||||
builder: (context, state) => const MakePaymentScreen(),
|
||||
builder: (context, state) => const PayScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/history',
|
||||
|
||||
@@ -10,6 +10,10 @@ part 'home_controller.g.dart';
|
||||
class HomeScreenModel {
|
||||
List<dynamic> transactions = [];
|
||||
List<Category> categories = [];
|
||||
List<BillProvider> providers = [];
|
||||
List<BillProvider> filterableProviders = [];
|
||||
BillProvider? selectedProvider;
|
||||
dynamic selectedCategory;
|
||||
dynamic account = {};
|
||||
dynamic profile = {};
|
||||
bool isLoading = false;
|
||||
@@ -30,13 +34,64 @@ abstract class Category with _$Category {
|
||||
_$CategoryFromJson(json);
|
||||
}
|
||||
|
||||
var logger = Logger(printer: PrettyPrinter());
|
||||
@freezed
|
||||
abstract class BillProvider with _$BillProvider {
|
||||
const factory BillProvider({
|
||||
required String clientId,
|
||||
required String name,
|
||||
required String description,
|
||||
required bool requiresAccount,
|
||||
required bool requiresAmount,
|
||||
required bool requiresAmountFromMerchant,
|
||||
required bool requiresPhone,
|
||||
required bool requiresReversal,
|
||||
required String? additionalDataString,
|
||||
required String processorType,
|
||||
required String uid,
|
||||
required String image,
|
||||
required String label,
|
||||
required String category,
|
||||
}) = _BillProvider;
|
||||
|
||||
factory BillProvider.fromJson(Map<String, dynamic> json) =>
|
||||
_$BillProviderFromJson(json);
|
||||
}
|
||||
|
||||
class HomeController extends ChangeNotifier {
|
||||
|
||||
final HomeScreenModel model = HomeScreenModel();
|
||||
final Http http = Http();
|
||||
|
||||
Future<void> getProviders() async {
|
||||
model.providers = getFakeProviders();
|
||||
try {
|
||||
model.isLoading = true;
|
||||
|
||||
List<dynamic> response = await http.get('/providers');
|
||||
model.providers = response.map((e) => BillProvider.fromJson(e)).toList();
|
||||
model.filterableProviders = model.providers;
|
||||
|
||||
if (model.providers.isNotEmpty) {
|
||||
updateSelectedProvider(model.providers[0]);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
model.errorMessage = e.toString();
|
||||
model.providers = [];
|
||||
model.filterableProviders = [];
|
||||
}
|
||||
|
||||
model.isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void updateSelectedProvider(BillProvider provider) {
|
||||
model.selectedProvider = provider;
|
||||
|
||||
// getProducts(provider.uid);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getCategories() async {
|
||||
// activate skeletonizer
|
||||
model.categories = getFakeCategoryData();
|
||||
@@ -45,15 +100,57 @@ class HomeController extends ChangeNotifier {
|
||||
model.isLoading = true;
|
||||
List<dynamic> response = await http.get('/categories');
|
||||
model.categories = response.map((e) => Category.fromJson(e)).toList();
|
||||
} on DioException catch (e) {
|
||||
logger.e(e);
|
||||
model.errorMessage = e.type.name;
|
||||
model.categories = [];
|
||||
}
|
||||
|
||||
if (model.categories.isNotEmpty) {
|
||||
updateSelectedCategory(model.categories[0]);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
logger.e(e);
|
||||
model.errorMessage = e.toString();
|
||||
model.categories = [];
|
||||
} finally {
|
||||
model.isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateSelectedCategory(Category? category) async {
|
||||
if (category == null) {
|
||||
model.filterableProviders = model.providers;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
model.selectedCategory = category;
|
||||
|
||||
model.filterableProviders =
|
||||
model.providers
|
||||
.where((element) => element.category == category.label)
|
||||
.toList();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<BillProvider> getFakeProviders() {
|
||||
return [
|
||||
BillProvider(
|
||||
clientId: '1',
|
||||
name: 'ZESA',
|
||||
description: 'Pay your electricity bills easily',
|
||||
requiresAccount: false,
|
||||
requiresAmount: false,
|
||||
requiresAmountFromMerchant: false,
|
||||
requiresPhone: false,
|
||||
requiresReversal: false,
|
||||
additionalDataString: '',
|
||||
processorType: '',
|
||||
uid: '',
|
||||
image: 'econet.png',
|
||||
label: '',
|
||||
category: '',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// required by skeletonizer
|
||||
List<Category> getFakeCategoryData() {
|
||||
|
||||
@@ -152,6 +152,178 @@ as String,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$BillProvider {
|
||||
|
||||
String get clientId; String get name; String get description; bool get requiresAccount; bool get requiresAmount; bool get requiresAmountFromMerchant; bool get requiresPhone; bool get requiresReversal; String? get additionalDataString; String get processorType; String get uid; String get image; String get label; String get category;
|
||||
/// Create a copy of BillProvider
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$BillProviderCopyWith<BillProvider> get copyWith => _$BillProviderCopyWithImpl<BillProvider>(this as BillProvider, _$identity);
|
||||
|
||||
/// Serializes this BillProvider to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BillProvider&&(identical(other.clientId, clientId) || other.clientId == clientId)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.requiresAccount, requiresAccount) || other.requiresAccount == requiresAccount)&&(identical(other.requiresAmount, requiresAmount) || other.requiresAmount == requiresAmount)&&(identical(other.requiresAmountFromMerchant, requiresAmountFromMerchant) || other.requiresAmountFromMerchant == requiresAmountFromMerchant)&&(identical(other.requiresPhone, requiresPhone) || other.requiresPhone == requiresPhone)&&(identical(other.requiresReversal, requiresReversal) || other.requiresReversal == requiresReversal)&&(identical(other.additionalDataString, additionalDataString) || other.additionalDataString == additionalDataString)&&(identical(other.processorType, processorType) || other.processorType == processorType)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.image, image) || other.image == image)&&(identical(other.label, label) || other.label == label)&&(identical(other.category, category) || other.category == category));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,clientId,name,description,requiresAccount,requiresAmount,requiresAmountFromMerchant,requiresPhone,requiresReversal,additionalDataString,processorType,uid,image,label,category);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BillProvider(clientId: $clientId, name: $name, description: $description, requiresAccount: $requiresAccount, requiresAmount: $requiresAmount, requiresAmountFromMerchant: $requiresAmountFromMerchant, requiresPhone: $requiresPhone, requiresReversal: $requiresReversal, additionalDataString: $additionalDataString, processorType: $processorType, uid: $uid, image: $image, label: $label, category: $category)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $BillProviderCopyWith<$Res> {
|
||||
factory $BillProviderCopyWith(BillProvider value, $Res Function(BillProvider) _then) = _$BillProviderCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String clientId, String name, String description, bool requiresAccount, bool requiresAmount, bool requiresAmountFromMerchant, bool requiresPhone, bool requiresReversal, String? additionalDataString, String processorType, String uid, String image, String label, String category
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$BillProviderCopyWithImpl<$Res>
|
||||
implements $BillProviderCopyWith<$Res> {
|
||||
_$BillProviderCopyWithImpl(this._self, this._then);
|
||||
|
||||
final BillProvider _self;
|
||||
final $Res Function(BillProvider) _then;
|
||||
|
||||
/// Create a copy of BillProvider
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? clientId = null,Object? name = null,Object? description = null,Object? requiresAccount = null,Object? requiresAmount = null,Object? requiresAmountFromMerchant = null,Object? requiresPhone = null,Object? requiresReversal = null,Object? additionalDataString = freezed,Object? processorType = null,Object? uid = null,Object? image = null,Object? label = null,Object? category = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
clientId: null == clientId ? _self.clientId : clientId // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,requiresAccount: null == requiresAccount ? _self.requiresAccount : requiresAccount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresAmount: null == requiresAmount ? _self.requiresAmount : requiresAmount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresAmountFromMerchant: null == requiresAmountFromMerchant ? _self.requiresAmountFromMerchant : requiresAmountFromMerchant // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresPhone: null == requiresPhone ? _self.requiresPhone : requiresPhone // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresReversal: null == requiresReversal ? _self.requiresReversal : requiresReversal // ignore: cast_nullable_to_non_nullable
|
||||
as bool,additionalDataString: freezed == additionalDataString ? _self.additionalDataString : additionalDataString // ignore: cast_nullable_to_non_nullable
|
||||
as String?,processorType: null == processorType ? _self.processorType : processorType // ignore: cast_nullable_to_non_nullable
|
||||
as String,uid: null == uid ? _self.uid : uid // ignore: cast_nullable_to_non_nullable
|
||||
as String,image: null == image ? _self.image : image // ignore: cast_nullable_to_non_nullable
|
||||
as String,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,category: null == category ? _self.category : category // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _BillProvider implements BillProvider {
|
||||
const _BillProvider({required this.clientId, required this.name, required this.description, required this.requiresAccount, required this.requiresAmount, required this.requiresAmountFromMerchant, required this.requiresPhone, required this.requiresReversal, required this.additionalDataString, required this.processorType, required this.uid, required this.image, required this.label, required this.category});
|
||||
factory _BillProvider.fromJson(Map<String, dynamic> json) => _$BillProviderFromJson(json);
|
||||
|
||||
@override final String clientId;
|
||||
@override final String name;
|
||||
@override final String description;
|
||||
@override final bool requiresAccount;
|
||||
@override final bool requiresAmount;
|
||||
@override final bool requiresAmountFromMerchant;
|
||||
@override final bool requiresPhone;
|
||||
@override final bool requiresReversal;
|
||||
@override final String? additionalDataString;
|
||||
@override final String processorType;
|
||||
@override final String uid;
|
||||
@override final String image;
|
||||
@override final String label;
|
||||
@override final String category;
|
||||
|
||||
/// Create a copy of BillProvider
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$BillProviderCopyWith<_BillProvider> get copyWith => __$BillProviderCopyWithImpl<_BillProvider>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$BillProviderToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BillProvider&&(identical(other.clientId, clientId) || other.clientId == clientId)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.requiresAccount, requiresAccount) || other.requiresAccount == requiresAccount)&&(identical(other.requiresAmount, requiresAmount) || other.requiresAmount == requiresAmount)&&(identical(other.requiresAmountFromMerchant, requiresAmountFromMerchant) || other.requiresAmountFromMerchant == requiresAmountFromMerchant)&&(identical(other.requiresPhone, requiresPhone) || other.requiresPhone == requiresPhone)&&(identical(other.requiresReversal, requiresReversal) || other.requiresReversal == requiresReversal)&&(identical(other.additionalDataString, additionalDataString) || other.additionalDataString == additionalDataString)&&(identical(other.processorType, processorType) || other.processorType == processorType)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.image, image) || other.image == image)&&(identical(other.label, label) || other.label == label)&&(identical(other.category, category) || other.category == category));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,clientId,name,description,requiresAccount,requiresAmount,requiresAmountFromMerchant,requiresPhone,requiresReversal,additionalDataString,processorType,uid,image,label,category);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BillProvider(clientId: $clientId, name: $name, description: $description, requiresAccount: $requiresAccount, requiresAmount: $requiresAmount, requiresAmountFromMerchant: $requiresAmountFromMerchant, requiresPhone: $requiresPhone, requiresReversal: $requiresReversal, additionalDataString: $additionalDataString, processorType: $processorType, uid: $uid, image: $image, label: $label, category: $category)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$BillProviderCopyWith<$Res> implements $BillProviderCopyWith<$Res> {
|
||||
factory _$BillProviderCopyWith(_BillProvider value, $Res Function(_BillProvider) _then) = __$BillProviderCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String clientId, String name, String description, bool requiresAccount, bool requiresAmount, bool requiresAmountFromMerchant, bool requiresPhone, bool requiresReversal, String? additionalDataString, String processorType, String uid, String image, String label, String category
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$BillProviderCopyWithImpl<$Res>
|
||||
implements _$BillProviderCopyWith<$Res> {
|
||||
__$BillProviderCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _BillProvider _self;
|
||||
final $Res Function(_BillProvider) _then;
|
||||
|
||||
/// Create a copy of BillProvider
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? clientId = null,Object? name = null,Object? description = null,Object? requiresAccount = null,Object? requiresAmount = null,Object? requiresAmountFromMerchant = null,Object? requiresPhone = null,Object? requiresReversal = null,Object? additionalDataString = freezed,Object? processorType = null,Object? uid = null,Object? image = null,Object? label = null,Object? category = null,}) {
|
||||
return _then(_BillProvider(
|
||||
clientId: null == clientId ? _self.clientId : clientId // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,requiresAccount: null == requiresAccount ? _self.requiresAccount : requiresAccount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresAmount: null == requiresAmount ? _self.requiresAmount : requiresAmount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresAmountFromMerchant: null == requiresAmountFromMerchant ? _self.requiresAmountFromMerchant : requiresAmountFromMerchant // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresPhone: null == requiresPhone ? _self.requiresPhone : requiresPhone // ignore: cast_nullable_to_non_nullable
|
||||
as bool,requiresReversal: null == requiresReversal ? _self.requiresReversal : requiresReversal // ignore: cast_nullable_to_non_nullable
|
||||
as bool,additionalDataString: freezed == additionalDataString ? _self.additionalDataString : additionalDataString // ignore: cast_nullable_to_non_nullable
|
||||
as String?,processorType: null == processorType ? _self.processorType : processorType // ignore: cast_nullable_to_non_nullable
|
||||
as String,uid: null == uid ? _self.uid : uid // ignore: cast_nullable_to_non_nullable
|
||||
as String,image: null == image ? _self.image : image // ignore: cast_nullable_to_non_nullable
|
||||
as String,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,category: null == category ? _self.category : category // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
||||
@@ -19,3 +19,39 @@ Map<String, dynamic> _$CategoryToJson(_Category instance) => <String, dynamic>{
|
||||
'image': instance.image,
|
||||
'label': instance.label,
|
||||
};
|
||||
|
||||
_BillProvider _$BillProviderFromJson(Map<String, dynamic> json) =>
|
||||
_BillProvider(
|
||||
clientId: json['clientId'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
requiresAccount: json['requiresAccount'] as bool,
|
||||
requiresAmount: json['requiresAmount'] as bool,
|
||||
requiresAmountFromMerchant: json['requiresAmountFromMerchant'] as bool,
|
||||
requiresPhone: json['requiresPhone'] as bool,
|
||||
requiresReversal: json['requiresReversal'] as bool,
|
||||
additionalDataString: json['additionalDataString'] as String?,
|
||||
processorType: json['processorType'] as String,
|
||||
uid: json['uid'] as String,
|
||||
image: json['image'] as String,
|
||||
label: json['label'] as String,
|
||||
category: json['category'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$BillProviderToJson(_BillProvider instance) =>
|
||||
<String, dynamic>{
|
||||
'clientId': instance.clientId,
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'requiresAccount': instance.requiresAccount,
|
||||
'requiresAmount': instance.requiresAmount,
|
||||
'requiresAmountFromMerchant': instance.requiresAmountFromMerchant,
|
||||
'requiresPhone': instance.requiresPhone,
|
||||
'requiresReversal': instance.requiresReversal,
|
||||
'additionalDataString': instance.additionalDataString,
|
||||
'processorType': instance.processorType,
|
||||
'uid': instance.uid,
|
||||
'image': instance.image,
|
||||
'label': instance.label,
|
||||
'category': instance.category,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qpay/screens/home/home_controller.dart';
|
||||
import 'package:qpay/screens/pay/pay_controller.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'dart:async';
|
||||
|
||||
@@ -14,15 +16,15 @@ class HomeScreen extends StatefulWidget {
|
||||
class _HomeScreenState extends State<HomeScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late final Animation<AlignmentGeometry> _alignAnimation;
|
||||
late Animation<AlignmentGeometry> _alignAnimation;
|
||||
final List<Animation<Offset>> _animations = [];
|
||||
final List<Animation<Offset>> _alignListAnimations = [];
|
||||
|
||||
late final bool _enabled = true;
|
||||
late bool _loading = true;
|
||||
Timer? _loadingTimer;
|
||||
|
||||
bool get _isLoggedIn => true;
|
||||
String _selectedFilter = 'All';
|
||||
|
||||
final HomeController homeController = HomeController();
|
||||
|
||||
@@ -31,6 +33,8 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
super.initState();
|
||||
|
||||
homeController.getCategories();
|
||||
homeController.getProviders();
|
||||
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
vsync: this,
|
||||
@@ -75,6 +79,35 @@ 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(
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showErrorSnackBar(String? message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -84,14 +117,6 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
);
|
||||
}
|
||||
|
||||
int _getCategoryIndex(String name) {
|
||||
return homeController.model.categories.indexWhere((e) => e.name == name);
|
||||
}
|
||||
|
||||
bool _isCategoryOdd(String name) {
|
||||
return _getCategoryIndex(name) % 2 != 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_loadingTimer?.cancel();
|
||||
@@ -101,7 +126,6 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final VoidCallback? onPressed = _enabled ? () {} : null;
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: CustomScrollView(
|
||||
@@ -119,7 +143,15 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
child: ListenableBuilder(
|
||||
listenable: homeController,
|
||||
builder: (context, child) {
|
||||
if (homeController.model.errorMessage != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_showErrorSnackBar(homeController.model.errorMessage);
|
||||
});
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_isLoggedIn)
|
||||
@@ -145,77 +177,16 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
ListenableBuilder(
|
||||
listenable: homeController,
|
||||
builder: (context, child) {
|
||||
if (homeController.model.errorMessage != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_showErrorSnackBar(
|
||||
homeController.model.errorMessage,
|
||||
);
|
||||
});
|
||||
}
|
||||
return Column(
|
||||
_buildFilterChips(homeController),
|
||||
SizedBox(height: 10),
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
...homeController.model.categories.map(
|
||||
(e) => Skeletonizer(
|
||||
enabled: homeController.model.isLoading,
|
||||
child: SlideTransition(
|
||||
position: _animations[0],
|
||||
child: FilledButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
WidgetStateProperty.all(
|
||||
_isCategoryOdd(e.name)
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary
|
||||
.withValues(alpha: 0.9)
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withValues(alpha: 0.9),
|
||||
),
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(
|
||||
vertical: 17.5,
|
||||
horizontal: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.electric_bolt,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
e.name,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
...homeController.model.filterableProviders.map(
|
||||
(e) => _buildProviderItem(e, context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
homeController.model.categories.isNotEmpty
|
||||
? const SizedBox(height: 30)
|
||||
: SizedBox(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -233,7 +204,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
child: const Text(
|
||||
'View All',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
@@ -253,23 +224,29 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withValues(alpha: 0.1),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.electric_bolt,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'Electricity Bill',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'Paid USD 50.00',
|
||||
style: TextStyle(fontWeight: FontWeight.normal),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
trailing: Text(
|
||||
'-USD 50.00',
|
||||
@@ -286,23 +263,29 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withValues(alpha: 0.1),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.phone_android,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'Airtime Purchase',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'Purchased USD 10.00',
|
||||
style: TextStyle(fontWeight: FontWeight.normal),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
trailing: Text(
|
||||
'-USD 10.00',
|
||||
@@ -317,6 +300,8 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -325,4 +310,104 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Column _buildProviderItem(BillProvider e, BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Skeletonizer(
|
||||
enabled: homeController.model.isLoading,
|
||||
child: SlideTransition(
|
||||
position: _alignListAnimations[1],
|
||||
child: Container(
|
||||
decoration: getBoxDecoration(e.clientId),
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
onTap: () {
|
||||
homeController.updateSelectedProvider(e);
|
||||
context.go("/make-payment");
|
||||
},
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Image(image: AssetImage("assets/${e.image}")),
|
||||
),
|
||||
title: Text(
|
||||
e.name,
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(
|
||||
e.description,
|
||||
style: TextStyle(fontWeight: FontWeight.normal, fontSize: 10),
|
||||
),
|
||||
trailing: Icon(
|
||||
Icons.chevron_right,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.secondary.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterChips(HomeController controller) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
_buildFilterChip('All', controller),
|
||||
...controller.model.categories.map((category) {
|
||||
return _buildFilterChip(category.name, controller, category);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterChip(
|
||||
String label,
|
||||
HomeController controller, [
|
||||
Category? category,
|
||||
]) {
|
||||
final isSelected = _selectedFilter == label;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: FilterChip(
|
||||
selected: isSelected,
|
||||
label: Text(label),
|
||||
onSelected: (selected) {
|
||||
setState(() {
|
||||
_selectedFilter = label;
|
||||
});
|
||||
if (category != null) {
|
||||
controller.updateSelectedCategory(category);
|
||||
} else {
|
||||
controller.updateSelectedCategory(null);
|
||||
}
|
||||
},
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
selectedColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
||||
checkmarkColor: Theme.of(context).colorScheme.primary,
|
||||
labelStyle: TextStyle(
|
||||
color:
|
||||
isSelected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
58
lib/screens/pay/pay_controller.dart
Normal file
58
lib/screens/pay/pay_controller.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:qpay/http/http.dart';
|
||||
import 'package:qpay/screens/home/home_controller.dart' as hc;
|
||||
|
||||
part 'pay_controller.freezed.dart';
|
||||
part 'pay_controller.g.dart';
|
||||
|
||||
class PayScreenModel {
|
||||
List<BillProduct> products = [];
|
||||
late BillProduct selectedProduct;
|
||||
dynamic formData = {};
|
||||
bool isLoading = false;
|
||||
String? errorMessage;
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class BillProduct with _$BillProduct {
|
||||
const factory BillProduct({
|
||||
required String uid,
|
||||
required String name,
|
||||
required String description,
|
||||
required String displayName,
|
||||
required double defaultAmount,
|
||||
required bool requiresAmount,
|
||||
}) = _BillProduct;
|
||||
|
||||
factory BillProduct.fromJson(Map<String, dynamic> json) =>
|
||||
_$BillProductFromJson(json);
|
||||
}
|
||||
|
||||
class PayController extends ChangeNotifier {
|
||||
final PayScreenModel model = PayScreenModel();
|
||||
final Http http = Http();
|
||||
|
||||
void updateSelectedProduct(BillProduct product) {
|
||||
model.selectedProduct = product;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getProducts(String providerId) async {
|
||||
try {
|
||||
model.isLoading = true;
|
||||
|
||||
List<dynamic> response = await http.get(
|
||||
'/providers/$providerId/products',
|
||||
);
|
||||
model.products = response.map((e) => BillProduct.fromJson(e)).toList();
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
model.errorMessage = e.toString();
|
||||
}
|
||||
|
||||
model.isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
163
lib/screens/pay/pay_controller.freezed.dart
Normal file
163
lib/screens/pay/pay_controller.freezed.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
// dart format width=80
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'pay_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$BillProduct {
|
||||
|
||||
String get uid; String get name; String get description; String get displayName; double get defaultAmount; bool get requiresAmount;
|
||||
/// Create a copy of BillProduct
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$BillProductCopyWith<BillProduct> get copyWith => _$BillProductCopyWithImpl<BillProduct>(this as BillProduct, _$identity);
|
||||
|
||||
/// Serializes this BillProduct to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BillProduct&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.defaultAmount, defaultAmount) || other.defaultAmount == defaultAmount)&&(identical(other.requiresAmount, requiresAmount) || other.requiresAmount == requiresAmount));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BillProduct(uid: $uid, name: $name, description: $description, displayName: $displayName, defaultAmount: $defaultAmount, requiresAmount: $requiresAmount)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $BillProductCopyWith<$Res> {
|
||||
factory $BillProductCopyWith(BillProduct value, $Res Function(BillProduct) _then) = _$BillProductCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String uid, String name, String description, String displayName, double defaultAmount, bool requiresAmount
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$BillProductCopyWithImpl<$Res>
|
||||
implements $BillProductCopyWith<$Res> {
|
||||
_$BillProductCopyWithImpl(this._self, this._then);
|
||||
|
||||
final BillProduct _self;
|
||||
final $Res Function(BillProduct) _then;
|
||||
|
||||
/// Create a copy of BillProduct
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? uid = null,Object? name = null,Object? description = null,Object? displayName = null,Object? defaultAmount = null,Object? requiresAmount = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
uid: null == uid ? _self.uid : uid // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||
as String,defaultAmount: null == defaultAmount ? _self.defaultAmount : defaultAmount // ignore: cast_nullable_to_non_nullable
|
||||
as double,requiresAmount: null == requiresAmount ? _self.requiresAmount : requiresAmount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _BillProduct implements BillProduct {
|
||||
const _BillProduct({required this.uid, required this.name, required this.description, required this.displayName, required this.defaultAmount, required this.requiresAmount});
|
||||
factory _BillProduct.fromJson(Map<String, dynamic> json) => _$BillProductFromJson(json);
|
||||
|
||||
@override final String uid;
|
||||
@override final String name;
|
||||
@override final String description;
|
||||
@override final String displayName;
|
||||
@override final double defaultAmount;
|
||||
@override final bool requiresAmount;
|
||||
|
||||
/// Create a copy of BillProduct
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$BillProductCopyWith<_BillProduct> get copyWith => __$BillProductCopyWithImpl<_BillProduct>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$BillProductToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BillProduct&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.defaultAmount, defaultAmount) || other.defaultAmount == defaultAmount)&&(identical(other.requiresAmount, requiresAmount) || other.requiresAmount == requiresAmount));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BillProduct(uid: $uid, name: $name, description: $description, displayName: $displayName, defaultAmount: $defaultAmount, requiresAmount: $requiresAmount)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$BillProductCopyWith<$Res> implements $BillProductCopyWith<$Res> {
|
||||
factory _$BillProductCopyWith(_BillProduct value, $Res Function(_BillProduct) _then) = __$BillProductCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String uid, String name, String description, String displayName, double defaultAmount, bool requiresAmount
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$BillProductCopyWithImpl<$Res>
|
||||
implements _$BillProductCopyWith<$Res> {
|
||||
__$BillProductCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _BillProduct _self;
|
||||
final $Res Function(_BillProduct) _then;
|
||||
|
||||
/// Create a copy of BillProduct
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? uid = null,Object? name = null,Object? description = null,Object? displayName = null,Object? defaultAmount = null,Object? requiresAmount = null,}) {
|
||||
return _then(_BillProduct(
|
||||
uid: null == uid ? _self.uid : uid // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||
as String,defaultAmount: null == defaultAmount ? _self.defaultAmount : defaultAmount // ignore: cast_nullable_to_non_nullable
|
||||
as double,requiresAmount: null == requiresAmount ? _self.requiresAmount : requiresAmount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
26
lib/screens/pay/pay_controller.g.dart
Normal file
26
lib/screens/pay/pay_controller.g.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'pay_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_BillProduct _$BillProductFromJson(Map<String, dynamic> json) => _BillProduct(
|
||||
uid: json['uid'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
displayName: json['displayName'] as String,
|
||||
defaultAmount: (json['defaultAmount'] as num).toDouble(),
|
||||
requiresAmount: json['requiresAmount'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$BillProductToJson(_BillProduct instance) =>
|
||||
<String, dynamic>{
|
||||
'uid': instance.uid,
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'displayName': instance.displayName,
|
||||
'defaultAmount': instance.defaultAmount,
|
||||
'requiresAmount': instance.requiresAmount,
|
||||
};
|
||||
@@ -1,14 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qpay/screens/pay/pay_controller.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class MakePaymentScreen extends StatefulWidget {
|
||||
const MakePaymentScreen({super.key});
|
||||
class PayScreen extends StatefulWidget {
|
||||
const PayScreen({super.key});
|
||||
|
||||
@override
|
||||
State<MakePaymentScreen> createState() => _MakePaymentScreenState();
|
||||
State<PayScreen> createState() => _PayScreenState();
|
||||
}
|
||||
|
||||
class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
@@ -16,11 +18,12 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _amountController = TextEditingController();
|
||||
final _descriptionController = TextEditingController();
|
||||
String _selectedBillType = 'Electricity';
|
||||
String _selectedBillType = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
@@ -54,13 +57,32 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _showErrorSnackBar(String? message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message.toString()),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Make Payment'),
|
||||
title: const Text(
|
||||
'Make Payment',
|
||||
style: TextStyle(color: Colors.black87),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
),
|
||||
body: Consumer<PayController>(
|
||||
builder: (context, controller, child) {
|
||||
if (controller.model.errorMessage != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_showErrorSnackBar(controller.model.errorMessage);
|
||||
});
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: FadeTransition(
|
||||
@@ -72,7 +94,8 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildBillTypeSelector(),
|
||||
SizedBox(height: 20),
|
||||
_buildBillProductSelector(controller),
|
||||
const SizedBox(height: 24),
|
||||
_buildAmountField(),
|
||||
const SizedBox(height: 24),
|
||||
@@ -85,20 +108,19 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBillTypeSelector() {
|
||||
Widget _buildBillProductSelector(PayController controller) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Select Bill Type',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
'Select Provider Product',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
@@ -112,21 +134,16 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: _selectedBillType,
|
||||
isExpanded: true,
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
items: [
|
||||
'Electricity',
|
||||
'Water',
|
||||
'Internet',
|
||||
'Mobile',
|
||||
'Gas',
|
||||
].map((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
...controller.model.products.map(
|
||||
(e) => DropdownMenuItem<String>(
|
||||
value: e.uid,
|
||||
child: Text(e.displayName),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (String? newValue) {
|
||||
if (newValue != null) {
|
||||
setState(() {
|
||||
@@ -147,10 +164,7 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
children: [
|
||||
const Text(
|
||||
'Amount',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
@@ -180,10 +194,7 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
children: [
|
||||
const Text(
|
||||
'Description',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
@@ -218,10 +229,7 @@ class _MakePaymentScreenState extends State<MakePaymentScreen>
|
||||
),
|
||||
child: const Text(
|
||||
'Pay Now',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user