Compare commits

..

6 commits

11 changed files with 285 additions and 55 deletions

View file

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:label="ocbo_esign_mobile" android:name="${applicationName}" android:icon="@mipmap/launcher_icon"> <application android:label="OCBO eSign" android:name="${applicationName}" android:icon="@mipmap/launcher_icon">
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user

BIN
assets/ocbologo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>ocbo_esign_mobile</string> <string>OCBO eSign</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View file

@ -1,7 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:intl/intl.dart';
import 'package:ocbo_esign_mobile/blocs/application/functions/bloc_getapplication.dart'; import 'package:ocbo_esign_mobile/blocs/application/functions/bloc_getapplication.dart';
import 'package:ocbo_esign_mobile/blocs/qr/functions/bloc_getqr.dart';
import 'package:ocbo_esign_mobile/functions/get_api.dart';
import 'package:ocbo_esign_mobile/widgets/box_widget.dart'; import 'package:ocbo_esign_mobile/widgets/box_widget.dart';
import 'package:ocbo_esign_mobile/widgets/loading_widget.dart';
import 'package:ocbo_esign_mobile/widgets/text_widget.dart'; import 'package:ocbo_esign_mobile/widgets/text_widget.dart';
class ApplicationInfoPage extends StatefulWidget { class ApplicationInfoPage extends StatefulWidget {
@ -12,13 +16,51 @@ class ApplicationInfoPage extends StatefulWidget {
} }
class _ApplicationInfoPageState extends State<ApplicationInfoPage> { class _ApplicationInfoPageState extends State<ApplicationInfoPage> {
// final NumberFormat formatter = NumberFormat('#,###.##');
final NumberFormat formatter = NumberFormat.currency(symbol: '', decimalDigits: 2);
late String _applicationNo = ''; late String _applicationNo = '';
late String _name = '';
late String _monthApproved = '';
late String _dayApproved = '';
late String _yearApproved = '';
late String _monthPrinted = '';
late String _dayPrinted = '';
late String _yearPrinted = '';
late String _amount = '';
late bool _isLoading = false;
void _getInfo() async { void _getInfo() async {
final applicationBloc = await blocGetApplication(context); setState(() {
_isLoading = true;
});
late String applicationBloc = '';
late String qrBloc = '';
if (context.mounted) {
applicationBloc = await blocGetApplication(context);
// ignore: use_build_context_synchronously
qrBloc = await blocGetQr(context);
}
final id = await getApi('get-esignid-byname', qrBloc, null);
final responseApproval = await getApi('get-infoapproval-electrical', applicationBloc, id["result"].toString());
final responsePrinted = await getApi('get-infoprinted-electrical', applicationBloc, id["result"].toString());
final responseAmount = await getApi('get-totalamount-electrical', applicationBloc, null);
final formattedAmount = formatter.format(double.parse(responseAmount["result"].toString()));
setState(() { setState(() {
_applicationNo = applicationBloc; _applicationNo = applicationBloc;
_name = responseApproval["result"].toString();
_monthApproved = responseApproval["result2"].toString();
_dayApproved = responseApproval["result3"].toString();
_yearApproved = responseApproval["result4"].toString();
_monthPrinted = responsePrinted["result"].toString();
_dayPrinted = responsePrinted["result2"].toString();
_yearPrinted = responsePrinted["result3"].toString();
_amount = formattedAmount;
_isLoading = false;
}); });
} }
@ -30,36 +72,124 @@ class _ApplicationInfoPageState extends State<ApplicationInfoPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Scaffold(
alignment: Alignment.center, resizeToAvoidBottomInset: false,
height: MediaQuery.of(context).size.height, body: Container(
decoration: const BoxDecoration( padding: EdgeInsets.only(left: 16, right: 16),
gradient: LinearGradient( alignment: Alignment.center,
begin: Alignment.topCenter, height: MediaQuery.of(context).size.height,
end: Alignment.bottomCenter, decoration: const BoxDecoration(
colors: [ gradient: LinearGradient(
Color.fromRGBO(37, 25, 44, 1), begin: Alignment.topCenter,
Color.fromRGBO(22, 33, 44, 1), end: Alignment.bottomCenter,
Color.fromRGBO(22, 33, 44, 1), colors: [
Color.fromRGBO(22, 33, 44, 1), Color.fromRGBO(37, 25, 44, 1),
Color.fromRGBO(25, 46, 41, 1), Color.fromRGBO(22, 33, 44, 1),
Color.fromRGBO(22, 33, 44, 1),
Color.fromRGBO(22, 33, 44, 1),
Color.fromRGBO(25, 46, 41, 1),
],
),
),
child: Column(
children: [
const Gap(88),
BoxWidget(
circular: 16,
padding: EdgeInsets.all(8),
content: _isLoading
? Center(child: LoadingWidget(text: 'Gathering Data, please wait'))
: Center(child: TextWidget(text: _applicationNo, bold: true, size: 30)),
),
const Gap(16),
BoxWidget(
circular: 16,
padding: EdgeInsets.all(8),
content: Center(
child: Column(
children: [
const TextWidget(
text: 'Name of Owner',
bold: false,
size: 8,
color: Color.fromRGBO(255, 255, 255, 0.8),
),
const Gap(4),
TextWidget(text: _name, bold: false, size: 16),
],
),
),
),
const Gap(8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BoxWidget(
circular: 16,
width: 140,
padding: EdgeInsets.all(0),
content: Center(
child: Column(
children: [
const TextWidget(
text: 'Approved Date',
bold: false,
size: 8,
color: Color.fromRGBO(255, 255, 255, 0.8),
),
const Gap(4),
TextWidget(text: _monthApproved, bold: false, size: 16),
TextWidget(text: _dayApproved, bold: true, size: 40),
TextWidget(text: _yearApproved, bold: false, size: 16),
],
),
),
),
BoxWidget(
circular: 16,
width: 140,
padding: EdgeInsets.all(0),
content: Center(
child: Column(
children: [
const TextWidget(
text: 'Printed Date',
bold: false,
size: 8,
color: Color.fromRGBO(255, 255, 255, 0.8),
),
const Gap(4),
TextWidget(text: _monthPrinted, bold: false, size: 16),
TextWidget(text: _dayPrinted, bold: true, size: 40),
TextWidget(text: _yearPrinted, bold: false, size: 16),
],
),
),
),
],
),
const Gap(8),
BoxWidget(
circular: 16,
padding: EdgeInsets.all(8),
content: Center(
child: Column(
children: [
const TextWidget(
text: 'Total Amount',
bold: false,
size: 8,
color: Color.fromRGBO(255, 255, 255, 0.8),
),
const Gap(4),
TextWidget(text: _amount, bold: true, size: 32),
],
),
),
),
], ],
), ),
), ),
child: Column(
children: [
const Gap(88),
BoxWidget(
circular: 16,
content: Center(child: TextWidget(text: _applicationNo, bold: true)),
),
const Gap(16),
BoxWidget(
circular: 16,
content: Center(child: TextWidget(text: _applicationNo, bold: true)),
),
],
),
); );
} }
} }

View file

@ -46,18 +46,13 @@ class IndexPage extends StatelessWidget {
const Gap(88), const Gap(88),
const ImageWidget(imagePath: 'assets/esign-mobile.webp', size: 160, measureByHeight: false), const ImageWidget(imagePath: 'assets/esign-mobile.webp', size: 160, measureByHeight: false),
const Gap(20), const Gap(20),
const TextWidget( const TextWidget(text: "OCBO e-Sign", color: Color.fromRGBO(244, 243, 243, 1), bold: true, size: 34),
text: "OCBO e-Sign",
color: Color.fromARGB(255, 244, 243, 243),
bold: true,
size: 34,
),
Padding( Padding(
padding: EdgeInsets.only(right: (screenWidth / 2) - 100), padding: EdgeInsets.only(right: (screenWidth / 2) - 100),
child: const Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
TextWidget(text: "Mobile", color: Color.fromARGB(255, 244, 243, 243), bold: false, size: 16), TextWidget(text: "Mobile", color: Color.fromRGBO(244, 243, 243, 1), bold: false, size: 16),
], ],
), ),
), ),
@ -85,15 +80,36 @@ class IndexPage extends StatelessWidget {
const MaxGap(516), const MaxGap(516),
const Opacity( const Opacity(
opacity: 0.7, opacity: 0.7,
child: ImageWidget(imagePath: 'assets/pat-alcala.webp', size: 74, measureByHeight: false), child: Column(
), children: [
const Gap(4), ImageWidget(imagePath: 'assets/ocbologo.webp', size: 26, measureByHeight: false),
const TextWidget( Gap(8),
text: "Developed By: Pat Alcala", TextWidget(text: '© 2026 Office of the City Building Official', size: 10),
color: Color.fromRGBO(244, 243, 243, 0.7), Gap(8),
bold: false, Row(
size: 10, mainAxisAlignment: MainAxisAlignment.center,
children: [
TextWidget(
text: "Developed By:",
color: Color.fromRGBO(244, 243, 243, 0.7),
bold: false,
size: 10,
),
Gap(4),
ImageWidget(imagePath: 'assets/pat-alcala.webp', size: 60, measureByHeight: false),
Gap(4),
TextWidget(
text: "Pat Alcala",
color: Color.fromRGBO(244, 243, 243, 0.7),
bold: false,
size: 10,
),
],
),
],
),
), ),
const Gap(24), const Gap(24),
], ],
), ),

View file

@ -7,6 +7,7 @@ import 'package:ocbo_esign_mobile/functions/get_api.dart';
import 'package:ocbo_esign_mobile/widgets/box_widget.dart'; import 'package:ocbo_esign_mobile/widgets/box_widget.dart';
import 'package:ocbo_esign_mobile/widgets/image_widget.dart'; import 'package:ocbo_esign_mobile/widgets/image_widget.dart';
import 'package:ocbo_esign_mobile/widgets/input_widget.dart'; import 'package:ocbo_esign_mobile/widgets/input_widget.dart';
import 'package:ocbo_esign_mobile/widgets/loading_widget.dart';
import 'package:ocbo_esign_mobile/widgets/text_widget.dart'; import 'package:ocbo_esign_mobile/widgets/text_widget.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -19,7 +20,7 @@ class ValidateDetailPage extends StatefulWidget {
class _ValidateDetailPageState extends State<ValidateDetailPage> { class _ValidateDetailPageState extends State<ValidateDetailPage> {
final _searchController = TextEditingController(); final _searchController = TextEditingController();
final NumberFormat formatter = NumberFormat('#,###.##'); final NumberFormat formatter = NumberFormat('#,###');
final dateFormatter = DateFormat('yyyy-MM-dd'); final dateFormatter = DateFormat('yyyy-MM-dd');
// final Color greenColor = const Color(0xFF4CCE51); // final Color greenColor = const Color(0xFF4CCE51);
late bool isLoading = false; late bool isLoading = false;
@ -129,7 +130,7 @@ class _ValidateDetailPageState extends State<ValidateDetailPage> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
isLoading isLoading
? TextWidget(text: 'Loading Data, please wait', size: 20, bold: false) ? LoadingWidget(text: 'Loading Data, please wait')
: Column( : Column(
children: [ children: [
TextWidget(text: formatter.format(_total), size: 50, bold: true), TextWidget(text: formatter.format(_total), size: 50, bold: true),
@ -169,7 +170,7 @@ class _ValidateDetailPageState extends State<ValidateDetailPage> {
const Gap(4), const Gap(4),
TextWidget(text: 'Time: ${_timeList[index]}', size: 10, opacity: 1), TextWidget(text: 'Time: ${_timeList[index]}', size: 10, opacity: 1),
const Gap(4), const Gap(4),
TextWidget(text: 'Tap to show details', size: 9, opacity: 0.6), const TextWidget(text: 'Tap to show details', size: 9, opacity: 0.6),
], ],
), ),
], ],

View file

@ -5,6 +5,7 @@ import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:ocbo_esign_mobile/blocs/qr/functions/bloc_setqr.dart'; import 'package:ocbo_esign_mobile/blocs/qr/functions/bloc_setqr.dart';
import 'package:ocbo_esign_mobile/functions/get_api.dart'; import 'package:ocbo_esign_mobile/functions/get_api.dart';
import 'package:ocbo_esign_mobile/widgets/button_widget.dart'; import 'package:ocbo_esign_mobile/widgets/button_widget.dart';
import 'package:ocbo_esign_mobile/widgets/loading_widget.dart';
import 'package:ocbo_esign_mobile/widgets/text_widget.dart'; import 'package:ocbo_esign_mobile/widgets/text_widget.dart';
import 'package:vibration/vibration.dart'; import 'package:vibration/vibration.dart';
@ -26,13 +27,18 @@ class BarcodeScannerScreen extends StatefulWidget {
class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> { class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
final Color _redColorShade = const Color.fromRGBO(86, 38, 38, 0.2); final Color _redColorShade = const Color.fromRGBO(86, 38, 38, 0.2);
final Color _redColor = const Color.fromRGBO(235, 88, 115, 0.91); final Color _redColor = const Color.fromRGBO(235, 88, 115, 0.8);
final Color _greenColorShade = const Color.fromARGB(51, 41, 115, 43); final Color _greenColorShade = const Color.fromRGBO(41, 115, 43, 0.2);
final Color _greenColor = const Color.fromRGBO(76, 206, 81, 0.91); final Color _greenColor = const Color.fromRGBO(76, 206, 81, 0.8);
late String _qrResult = ''; late String _qrResult = '';
late bool _scanning = false;
void readQr(String value) async { void readQr(String value) async {
setState(() {
_scanning = true;
});
if (value.contains('OCBO e-Sign')) { if (value.contains('OCBO e-Sign')) {
final qr = value.substring(35); final qr = value.substring(35);
final response = await getApi('check-qr', qr, null); final response = await getApi('check-qr', qr, null);
@ -40,15 +46,18 @@ class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
if (result != '') { if (result != '') {
setState(() { setState(() {
_scanning = false;
_qrResult = result; _qrResult = result;
}); });
} else { } else {
setState(() { setState(() {
_scanning = false;
_qrResult = 'non-exist'; _qrResult = 'non-exist';
}); });
} }
} else { } else {
setState(() { setState(() {
_scanning = false;
_qrResult = 'invalid'; _qrResult = 'invalid';
}); });
@ -61,6 +70,10 @@ class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
} }
void tryQR() async { void tryQR() async {
setState(() {
_scanning = true;
});
final value = "Use OCBO e-Sign Validator - scanid=0918d59e"; final value = "Use OCBO e-Sign Validator - scanid=0918d59e";
if (value.contains('OCBO e-Sign')) { if (value.contains('OCBO e-Sign')) {
@ -70,10 +83,12 @@ class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
if (result != null) { if (result != null) {
setState(() { setState(() {
_scanning = false;
_qrResult = result; _qrResult = result;
}); });
} else { } else {
setState(() { setState(() {
_scanning = false;
_qrResult = 'non-exist'; _qrResult = 'non-exist';
}); });
@ -85,6 +100,7 @@ class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
} }
} else { } else {
setState(() { setState(() {
_scanning = false;
_qrResult = 'invalid'; _qrResult = 'invalid';
}); });
@ -181,6 +197,13 @@ class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
), ),
const Gap(24), const Gap(24),
// ButtonWidget(text: 'Try API', disabled: false, onPressed: tryQR), // ButtonWidget(text: 'Try API', disabled: false, onPressed: tryQR),
if (_scanning)
Column(
children: [
const Gap(16),
const LoadingWidget(text: 'Reading QR'),
],
),
if (_qrResult.isNotEmpty) if (_qrResult.isNotEmpty)
Column( Column(
children: [ children: [
@ -214,7 +237,20 @@ class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
), ),
), ),
const Gap(16), const Gap(16),
TextWidget(text: 'QR Detected is not OCBO e-Sign', bold: true, size: 20, color: _redColor), Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: _redColor, width: 2),
color: _redColorShade,
borderRadius: BorderRadius.circular(16),
),
child: TextWidget(
text: 'Not OCBO e-Sign QR detected',
bold: true,
size: 18,
color: _redColor,
),
),
], ],
) )
else if (_qrResult == 'non-exist') else if (_qrResult == 'non-exist')

View file

@ -4,20 +4,29 @@ class BoxWidget extends StatelessWidget {
final Widget content; final Widget content;
final double? circular; final double? circular;
final CrossAxisAlignment? alignment; final CrossAxisAlignment? alignment;
final double? width;
final EdgeInsets? padding;
// final double? titleSize; // final double? titleSize;
const BoxWidget({super.key, required this.content, this.circular = 8, this.alignment = CrossAxisAlignment.start}); const BoxWidget({
super.key,
required this.content,
this.circular = 8,
this.alignment = CrossAxisAlignment.start,
this.width,
this.padding,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: EdgeInsets.all(16), padding: padding ?? EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(circular ?? 8), borderRadius: BorderRadius.circular(circular ?? 8),
color: Color.fromRGBO(16, 22, 28, 0.584), color: Color.fromRGBO(16, 22, 28, 0.584),
border: Border.all(color: const Color.fromRGBO(41, 60, 78, 0.914)), border: Border.all(color: const Color.fromRGBO(41, 60, 78, 0.914)),
), ),
width: MediaQuery.of(context).size.width - 30, width: width ?? MediaQuery.of(context).size.width - 30,
child: Column( child: Column(
crossAxisAlignment: alignment ?? CrossAxisAlignment.start, crossAxisAlignment: alignment ?? CrossAxisAlignment.start,
children: [Padding(padding: const EdgeInsets.all(16), child: content)], children: [Padding(padding: const EdgeInsets.all(16), child: content)],

View file

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:ocbo_esign_mobile/widgets/text_widget.dart';
class LoadingWidget extends StatelessWidget {
final String? text;
const LoadingWidget({super.key, this.text});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
LoadingAnimationWidget.discreteCircle(
color: const Color.fromRGBO(164, 168, 229, 0.8),
secondRingColor: const Color.fromRGBO(141, 145, 210, 0.8),
thirdRingColor: const Color.fromRGBO(117, 123, 204, 0.8),
size: 60,
),
const Gap(16),
TextWidget(text: text ?? '', size: 16, bold: true),
],
),
);
}
}

View file

@ -376,6 +376,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "6.1.0"
loading_animation_widget:
dependency: "direct main"
description:
name: loading_animation_widget
sha256: "9fe23381f3096e902f39e87e487648ff7f74925e86234353fa885bb9f6c98004"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:

View file

@ -23,6 +23,7 @@ dependencies:
flutter_bloc: ^9.1.1 flutter_bloc: ^9.1.1
vibration: ^3.1.4 vibration: ^3.1.4
intl: ^0.20.2 intl: ^0.20.2
loading_animation_widget: ^1.3.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -48,3 +49,4 @@ flutter:
- assets/esign.webp - assets/esign.webp
- assets/esign-mobile.webp - assets/esign-mobile.webp
- assets/esign-check.webp - assets/esign-check.webp
- assets/ocbologo.webp