Compare commits

5 Commits

31 changed files with 3785 additions and 8 deletions

View File

@@ -1,5 +1,46 @@
import 'dart:convert';
import 'package:jiffy/jiffy.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'constant.dart';
String formatDateJiffy(String serverDate) {
return Jiffy.parse(serverDate).format(pattern: 'dd-MM-yyyy');
}
}
Future<bool> isTokenExpired() async {
final prefs = await SharedPreferences.getInstance();
final bearerString = prefs.getString(Constant.bearerName);
if (bearerString == null || bearerString == "null") {
print("BEARER STRING NULL");
return true;
}
final xmodel = jsonDecode(bearerString);
if (xmodel == null) return true;
final String? expireDateStr = xmodel['expire_date'];
print("EXPIRE DATE $expireDateStr");
if (expireDateStr == null || expireDateStr.isEmpty) {
print("Token EXPIRED (Tidak Ada Tanggal Expire)");
return true; // Anggap expired jika tidak ada data
}
try {
DateTime expiredDate = DateTime.parse(expireDateStr);
// DateTime batasExpired = expiredDate.subtract(Duration(minutes: 3));
DateTime batasExpired = expiredDate.subtract(Duration(minutes: 10));
DateTime now = DateTime.now();
final bool expired = now.isAfter(batasExpired);
print(expired ? "Token EXPIRED (Perlu Refresh)" : "Token MASIH AKTIF");
return expired;
} catch (e) {
print("Token EXPIRED (Format Salah)");
return true; // Anggap expired jika terjadi error parsing
}
}

View File

@@ -4,6 +4,11 @@ class Constant {
// static double designHeight = 1024;
// static double designWidth = 1440;
// prosesAksi
static String getRiwayat = "getRiwayat";
static String postUploadFoto = "postUploadFoto";
static String postEditScan = "postEditScan";
// base url
static String baseURL = "http://devone.aplikasi.web.id/";

View File

@@ -1,4 +1,9 @@
import 'package:flutter/material.dart';
import 'package:scanktpflutter/screen/no-login-scan/no_login_scan_screen.dart';
import '../screen/no-login-home/no_login_home_screen.dart';
import '../screen/no-login-scan/no_login_edit_scan_screen.dart';
import '../screen/no-login-splash/no_login_splash_screen.dart';
import '../screen/no-login/no_login_screen.dart';
import '../screen/home/home_screen.dart';
import '../screen/login/login_screen.dart';
@@ -12,6 +17,12 @@ const homeRoute = "/homeRoute";
const scanRoute = "/scanRoute";
const editScanRoute = "/editScanRoute";
const noLoginSplashRoute = "/noLoginSplashRoute";
const noLoginRoute = "/noLoginRoute";
const noLoginHomeRoute = "/noLoginHomeRoute";
const noLoginScanRoute = "/noLoginScanRoute";
const noLoginEditScanRoute = "/noLoginEditScanRoute";
class AppRoute {
static Route<dynamic> generateRoute(RouteSettings settings) {
// splash screen
@@ -25,6 +36,16 @@ class AppRoute {
});
}
if (settings.name == noLoginSplashRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)),
child: NoLoginSplashScreen(),
);
});
}
// login screen
if (settings.name == loginRoute) {
return MaterialPageRoute(builder: (context) {
@@ -36,6 +57,16 @@ class AppRoute {
});
}
if (settings.name == noLoginRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)),
child: NoLoginScreen(),
);
});
}
// home screen
if (settings.name == homeRoute) {
return MaterialPageRoute(builder: (context) {
@@ -47,6 +78,16 @@ class AppRoute {
});
}
if (settings.name == noLoginHomeRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)),
child: NoLoginHomeScreen(),
);
});
}
// scan screen
if (settings.name == scanRoute) {
return MaterialPageRoute(builder: (context) {
@@ -58,6 +99,16 @@ class AppRoute {
});
}
if (settings.name == noLoginScanRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)),
child: NoLoginScanScreen(),
);
});
}
// edit screen
if (settings.name == editScanRoute) {
return MaterialPageRoute(builder: (context) {
@@ -69,12 +120,22 @@ class AppRoute {
});
}
if (settings.name == noLoginEditScanRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)),
child: NoLoginEditScanScreen(),
);
});
}
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
padding: const EdgeInsets.all(0),
textScaler: TextScaler.linear(1.0)),
child: SplashScreen(),
child: NoLoginSplashScreen(),
);
});
}

View File

@@ -31,7 +31,7 @@ class MyApp extends StatelessWidget {
},
),
debugShowCheckedModeBanner: false,
initialRoute: splashRoute,
initialRoute: noLoginRoute,
onGenerateRoute: AppRoute.generateRoute,
);
}

View File

@@ -0,0 +1,90 @@
class NoLoginAuthModel {
final String token;
final String host;
final String client_id;
final String expire_date;
final bool isLogin;
NoLoginAuthModel({
required this.host,
required this.token,
required this.client_id,
required this.expire_date,
required this.isLogin,
});
Map<String, dynamic> toJson() {
return {
'host':host,
'token': token,
'client_id' :client_id,
'expire_date':expire_date,
'isLogin':isLogin,
};
}
}
// old
class UserModel {
final String userId;
final String username;
final String groupDashboard;
final String defaultSampleStationId;
final String staffName;
final String isCourier;
final String timeAutoLogout;
final String ip;
final String agent;
final String version;
final String lastLogin;
final int satelliteId;
UserModel({
required this.userId,
required this.username,
required this.groupDashboard,
required this.defaultSampleStationId,
required this.staffName,
required this.isCourier,
required this.timeAutoLogout,
required this.ip,
required this.agent,
required this.version,
required this.lastLogin,
required this.satelliteId,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
userId: json['M_UserID'] ?? '',
username: json['M_UserUsername'] ?? '',
groupDashboard: json['M_UserGroupDashboard'] ?? '',
defaultSampleStationId: json['M_UserDefaultT_SampleStationID'] ?? '',
staffName: json['M_StaffName'] ?? '',
isCourier: json['is_courier'] ?? '',
timeAutoLogout: json['time_autologout'] ?? '',
ip: json['ip'] ?? '',
agent: json['agent'] ?? '',
version: json['version'] ?? '',
lastLogin: json['last-login'] ?? '',
satelliteId: json['M_SatelliteID'] ?? 0,
);
}
Map<String, dynamic> toJson() {
return {
'M_UserID': userId,
'M_UserUsername': username,
'M_UserGroupDashboard': groupDashboard,
'M_UserDefaultT_SampleStationID': defaultSampleStationId,
'M_StaffName': staffName,
'is_courier': isCourier,
'time_autologout': timeAutoLogout,
'ip': ip,
'agent': agent,
'version': version,
'last-login': lastLogin,
'M_SatelliteID': satelliteId,
};
}
}

View File

@@ -0,0 +1,49 @@
class NoLoginEditPersonKtpModel {
final String personID;
final String personNIK;
final String personName;
final String personDob;
final String personSex;
final String personUrl;
final String personQrCode;
final String m_sexname;
NoLoginEditPersonKtpModel({
required this.personID,
required this.personNIK,
required this.personName,
required this.personDob,
required this.personSex,
required this.personUrl,
required this.personQrCode,
required this.m_sexname,
});
// Convert JSON to Model
factory NoLoginEditPersonKtpModel.fromJson(Map<String, dynamic> json) {
return NoLoginEditPersonKtpModel(
personID: json['Person_ID'] ?? "",
personNIK: json['Person_NIK'] ?? "",
personName: json['Person_Name'] ?? "",
personDob: json['Person_Dob'] ?? DateTime.now(),
personSex: json['Person_Sex'] ?? "",
personUrl: json['Person_Url'] ?? "",
personQrCode: json['Person_QrCode'] ?? "",
m_sexname: json['m_sexname'] ?? "",
);
}
// Convert Model to JSON
Map<String, dynamic> toJson() {
return {
'Person_ID':personID,
'Person_NIK': personNIK,
'Person_Name': personName,
'Person_Dob': personDob,
'Person_Sex': personSex,
'Person_Url': personUrl,
'Person_QrCode': personQrCode,
'm_sexname':m_sexname,
};
}
}

View File

@@ -0,0 +1,49 @@
class NoLoginPersonKtpModel {
final String personID;
final String personNIK;
final String personName;
final String personDob;
final String personSex;
final String personUrl;
final String personQrCode;
final String m_sexname;
NoLoginPersonKtpModel({
required this.personID,
required this.personNIK,
required this.personName,
required this.personDob,
required this.personSex,
required this.personUrl,
required this.personQrCode,
required this.m_sexname,
});
// Convert JSON to Model
factory NoLoginPersonKtpModel.fromJson(Map<String, dynamic> json) {
return NoLoginPersonKtpModel(
personID: json['Person_ID'] ?? "",
personNIK: json['Person_NIK'] ?? "",
personName: json['Person_Name'] ?? "",
personDob: json['Person_Dob'] ?? DateTime.now(),
personSex: json['Person_Sex'] ?? "",
personUrl: json['Person_Url'] ?? "",
personQrCode: json['Person_QrCode'] ?? "",
m_sexname: json['m_sexname'] ?? "",
);
}
// Convert Model to JSON
Map<String, dynamic> toJson() {
return {
'Person_ID':personID,
'Person_NIK': personNIK,
'Person_Name': personName,
'Person_Dob': personDob,
'Person_Sex': personSex,
'Person_Url': personUrl,
'Person_QrCode': personQrCode,
'm_sexname':m_sexname,
};
}
}

View File

@@ -0,0 +1,31 @@
class NoLoginSexModel {
final String M_SexID;
final String M_SexCode;
final String m_sexname;
final String M_SexNameLang;
NoLoginSexModel({
required this.M_SexID,
required this.M_SexCode,
required this.m_sexname,
required this.M_SexNameLang,
});
factory NoLoginSexModel.fromJson(Map<String, dynamic> json) {
return NoLoginSexModel(
M_SexID: json['M_SexID'],
M_SexCode: json['M_SexCode'],
m_sexname: json['m_sexname'],
M_SexNameLang: json['M_SexNameLang'],
);
}
Map<String, dynamic> toJson() {
return {
'M_SexID': M_SexID,
'M_SexCode': M_SexCode,
'm_sexname': m_sexname,
'M_SexNameLang': M_SexNameLang,
};
}
}

View File

@@ -0,0 +1,49 @@
class NoLoginSuksesPersonKtpModel {
final String personID;
final String personNIK;
final String personName;
final String personDob;
final String personSex;
final String personUrl;
final String personQrCode;
final String m_sexname;
NoLoginSuksesPersonKtpModel({
required this.personID,
required this.personNIK,
required this.personName,
required this.personDob,
required this.personSex,
required this.personUrl,
required this.personQrCode,
required this.m_sexname,
});
// Convert JSON to Model
factory NoLoginSuksesPersonKtpModel.fromJson(Map<String, dynamic> json) {
return NoLoginSuksesPersonKtpModel(
personID: json['Person_ID'] ?? "",
personNIK: json['Person_NIK'] ?? "",
personName: json['Person_Name'] ?? "",
personDob: json['Person_Dob'] ?? DateTime.now(),
personSex: json['Person_Sex'] ?? "",
personUrl: json['Person_Url'] ?? "",
personQrCode: json['Person_QrCode'] ?? "",
m_sexname: json['m_sexname'] ?? "",
);
}
// Convert Model to JSON
Map<String, dynamic> toJson() {
return {
'Person_ID':personID,
'Person_NIK': personNIK,
'Person_Name': personName,
'Person_Dob': personDob,
'Person_Sex': personSex,
'Person_Url': personUrl,
'Person_QrCode': personQrCode,
'm_sexname':m_sexname,
};
}
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:scanktpflutter/model/no-login/no_login_edit_person_ktp_model.dart';
import 'package:scanktpflutter/model/no-login/no_login_person_ktp_model.dart';
import 'package:scanktpflutter/model/no-login/no_login_sex_model.dart';
import '../../app/constant.dart';
import '../../model/edit_person_model.dart';
// list scan
final listScanRwt = StateProvider<List<NoLoginPersonKtpModel>>(
(ref) => List.empty(
growable: true,
),
);
final selectedPersonIdx = StateProvider<String>((ref) => "0");
final selectedEdit = StateProvider<NoLoginEditPersonKtpModel>(
(ref) => NoLoginEditPersonKtpModel(
personID: "",
personNIK: "",
personName: "",
personDob: "",
personSex: "",
personUrl: "",
personQrCode: "",
m_sexname: "",
),
);
// inputan edit
final eQrCode = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eNikCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eNamaCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eDobCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eDobDt = StateProvider<DateTime>(
(ref) => DateTime.now(),
);
final eSexCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eSexSelected = StateProvider<NoLoginSexModel>(
(ref) => NoLoginSexModel(
M_SexID: "",
M_SexCode: "",
m_sexname: "",
M_SexNameLang: "",
),
);
final barcodeX = StateProvider<Barcode>(
(ref) => Barcode(),
);
final noLoginprosesAksi = StateProvider<String>((ref) => Constant.getRiwayat);

View File

@@ -0,0 +1,4 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../model/no-login/no_login_auth_model.dart';
final noLoginCurrentUserProvider = StateProvider<NoLoginAuthModel?>((ref) => null);

View File

@@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:scanktpflutter/model/sex_model.dart';
import '../model/edit_person_model.dart';
import '../model/person_ktp_model.dart';
import '../../model/edit_person_model.dart';
import '../../model/person_ktp_model.dart';
// list scan
final listScanRwt = StateProvider<List<PersonKtp>>(
@@ -53,4 +54,4 @@ final eSexSelected = StateProvider<SexModel>(
m_sexname: "",
M_SexNameLang: "",
),
);
);

View File

@@ -0,0 +1,118 @@
import '../../model/no-login/no_login_person_ktp_model.dart';
import '../../model/no-login/no_login_sex_model.dart';
import '../../model/no-login/no_login_sukses_person_model.dart';
import '../base_repository.dart';
class NoLoginScanRepository extends BaseRepository {
NoLoginScanRepository({required super.dio});
Future<List<NoLoginPersonKtpModel>> listRiwayatScanRepo({
required String host,
required String client_id,
required String token,
}) async {
// final service = "${Constant.baseUrl}xauth/login";
final service =
"http://${host}/one-api/scan-ktpv2-no-login/Scanktpv2/listRiwayatScan";
final resp = await post(
param: {"client_id": client_id},
service: service,
token: token,
);
final result = List<NoLoginPersonKtpModel>.empty(growable: true);
resp['data'].forEach((e) {
final model = NoLoginPersonKtpModel.fromJson(e);
result.add(model);
});
return result;
}
Future<List<NoLoginSuksesPersonKtpModel>> prosesScan({
required String host,
required String base64File,
required String client_id,
required String qr_code,
required String token,
}) async {
final service =
"http://${host}/one-api/scan-ktpv2-no-login/Scanktpv2/proses_scan";
final resp = await post(
param: {
"base64File": base64File,
"client_id": client_id,
"qr_code": qr_code,
},
service: service,
token: token,
);
final result = List<NoLoginSuksesPersonKtpModel>.empty(growable: true);
resp['data'].forEach((e) {
final model = NoLoginSuksesPersonKtpModel.fromJson(e);
result.add(model);
});
return result;
}
// sex
Future<List<NoLoginSexModel>> sexRepo({
required String host,
required String client_id,
required String token,
}) async {
// final service = "${Constant.baseUrl}xauth/login";
final service =
"http://${host}/one-api/scan-ktpv2-no-login/Scanktpv2/getSex";
final resp = await post(
param: {"client_id": client_id},
service: service,
token: token,
);
final result = List<NoLoginSexModel>.empty(growable: true);
resp['data'].forEach((e) {
final model = NoLoginSexModel.fromJson(e);
result.add(model);
});
return result;
}
// edit
Future<String> prosesEdit({
required String token,
required String host,
required String client_id,
required String Person_ID,
required String Person_NIK,
required String Person_Name,
required String Person_Dob,
required String Person_Sex,
}) async {
final service =
"http://${host}/one-api/scan-ktpv2-no-login/Scanktpv2/proses_edit";
final resp = await post(
param: {
"Person_ID": Person_ID,
"Person_NIK": Person_NIK,
"Person_Name": Person_Name,
"Person_Dob": Person_Dob,
"Person_Sex": Person_Sex,
"client_id": client_id,
},
service: service,
token: token,
);
if (resp['status'] == "OK") {
return "Sukses Update Data";
} else {
resp['message'];
}
return resp['message'];
}
}

View File

@@ -0,0 +1,89 @@
import '../../model/no-login/no_login_auth_model.dart';
import '../base_repository.dart';
class NoLoginAuthRepository extends BaseRepository {
NoLoginAuthRepository({required super.dio});
// loginF
Future<NoLoginAuthModel> loginF({
required String client_id,
required String host,
required String password,
}) async {
final param = {
"client_id": client_id,
"password": password,
"host": host,
};
final service = "http://$host/one-api/scan-ktpv2-no-login/Scanktpv2/loginF";
final resp = await post(param: param, service: service);
// if (resp['status'] != "OK") {
// return resp['message'];
// }
// return resp['status'];
final result = NoLoginAuthModel(
host: resp['data']['host'],
token: resp["data"]["token"],
client_id: resp['data']['client_id'],
expire_date: resp['data']['expire_date'],
isLogin: resp['data']['isLogin'],
);
return result;
}
// createToken
Future<NoLoginAuthModel> createToken({
required String client_id,
required String host,
}) async {
final param = {
"client_id": client_id,
"host": host,
};
// final service = "${Constant.baseUrl}xauth/login";
final service =
"http://$host/one-api/scan-ktpv2-no-login/Scanktpv2/createToken";
final resp = await post(param: param, service: service);
final result = NoLoginAuthModel(
host: resp['data']['host'],
token: resp["data"]["token"],
client_id: resp['data']['client_id'],
expire_date: resp['data']['expire_date'],
isLogin: resp['data']['isLogin'],
);
return result;
}
// refreshToken
Future<NoLoginAuthModel> refreshToken(
{required String client_id,
required String host,
required String expire_date}) async {
final param = {
"client_id": client_id,
"host": host,
"expire_date": expire_date,
};
// final service = "${Constant.baseUrl}xauth/login";
final service =
"http://$host/one-api/scan-ktpv2-no-login/Scanktpv2/refreshToken";
final resp = await post(param: param, service: service);
final result = NoLoginAuthModel(
host: resp['data']['host'],
token: resp["data"]["token"],
client_id: resp['data']['client_id'],
expire_date: resp['data']['expire_date'],
isLogin: resp['data']['isLogin'],
);
return result;
}
}

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../provider/scan_provider.dart';
import 'package:scanktpflutter/provider/scan_provider.dart';
import '../../screen/home/card_riwayat_scan.dart';
import '../../screen/home/list_riwayat_scan_provider.dart';

View File

@@ -0,0 +1,184 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../model/no-login/no_login_edit_person_ktp_model.dart';
import '../../model/no-login/no_login_person_ktp_model.dart';
import '../../model/no-login/no_login_sex_model.dart';
import '../../app/app_extension.dart';
import '../../app/route.dart';
import '../../app/constant.dart';
import '../../provider/no-login-scan/no_login_scan_provider.dart';
class NoLoginCardRiwayatScan extends HookConsumerWidget {
final NoLoginPersonKtpModel data;
const NoLoginCardRiwayatScan({
super.key,
required this.data,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
SystemUiOverlay.bottom,
]);
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus!.unfocus();
},
child: Card(
elevation: 2,
color: Constant.textWhite,
child: Padding(
padding: EdgeInsets.all(
Constant.getActualYPhone(context: context, y: 12),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
data.personNIK,
style: Constant.titleInputan600(context: context).copyWith(
color: Constant.textBlack,
),
),
InkWell(
onTap: () {
// print('id : ${data.personID}');
ref.read(selectedPersonIdx.notifier).state =
data.personID;
// set SEX
ref.read(eSexSelected.notifier).state = NoLoginSexModel(
M_SexID: data.personSex,
M_SexCode: "",
m_sexname: data.m_sexname,
M_SexNameLang: ""
);
ref.read(selectedEdit.notifier).state = NoLoginEditPersonKtpModel(
personID: data.personID,
personNIK: data.personNIK,
personName: data.personName,
m_sexname: data.m_sexname,
personDob: data.personDob,
personSex: data.personSex,
personUrl: data.personUrl,
personQrCode: data.personQrCode,
);
Navigator.of(context).pushNamed(
noLoginEditScanRoute,
);
},
child: Icon(
Icons.edit,
color: Constant.bgButton,
size: 20,
),
),
],
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 12,
),
),
// row nama
Row(
children: [
Icon(
Icons.person_2_outlined,
color: Constant.bgIcon,
size: 20,
),
SizedBox(
width: Constant.getActualXPhone(
context: context,
x: 8,
),
),
Text(
data.personName,
style: Constant.cardText(context: context).copyWith(
color: Constant.textCardGrey,
),
),
],
),
// row nama
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 10,
),
),
// row dob
Row(
children: [
Icon(
Icons.date_range_outlined,
color: Constant.bgIcon,
size: 20,
),
SizedBox(
width: Constant.getActualXPhone(
context: context,
x: 8,
),
),
Text(
formatDateJiffy(data.personDob),
style: Constant.cardText(context: context).copyWith(
color: Constant.textCardGrey,
),
),
],
),
// row dob
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 10,
),
),
// row jenis kelamin
Row(
children: [
Image.asset(
'images/icongender.png',
width: Constant.getActualXPhone(
context: context,
x: 20,
),
height: Constant.getActualYPhone(
context: context,
y: 20,
),
fit: BoxFit.cover,
),
SizedBox(
width: Constant.getActualXPhone(
context: context,
x: 8,
),
),
Text(
data.m_sexname,
style: Constant.cardText(context: context).copyWith(
color: Constant.textCardGrey,
),
),
],
),
// row jenis kelamin
],
),
),
),
);
}
}

View File

@@ -0,0 +1,357 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../app/app_extension.dart';
import '../../screen/no-login-home/no_login_riwayat_scan_provider.dart';
// import 'package:shared_preferences/shared_preferences.dart';
import '../../provider/no-login/no_login_current_user_provider.dart';
import '../../provider/no-login-scan/no_login_scan_provider.dart';
import '../../app/constant.dart';
import '../../app/route.dart';
import '../../widget/customsnackbarwidget.dart';
import '../no-login/no_login_refresh_token_provider.dart';
import 'no_login_card_riwayat_scan.dart';
class NoLoginHomeScreen extends HookConsumerWidget {
const NoLoginHomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
SystemUiOverlay.bottom,
]);
final currentUser = ref.watch(noLoginCurrentUserProvider);
// final username = currentUser?.model.username ?? "-";
final host = currentUser?.host ?? "";
final isLoading = useState<bool>(false);
final listScanArr = ref.watch(listScanRwt);
final client_id = currentUser?.client_id ?? "";
final token = currentUser?.token ?? "";
final expire_date = currentUser?.expire_date ?? "";
final readProsesAksi = ref.watch(noLoginprosesAksi);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
if (client_id == "0" || token == "") {
// not logged in and doesn't have token
Navigator.of(context)
.pushNamedAndRemoveUntil(noLoginRoute, (route) => false);
return;
}
if (token != "") {
if (await isTokenExpired() == true) {
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
}
}
});
return () {};
}, [currentUser]);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestap) async {
ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan(
host: host,
client_id: client_id,
token: token,
expire_date: expire_date,
);
});
return () {};
}, []);
// refreshToken Provider
ref.listen(noLoginRefreshTokenProvider, (prev, next) async {
if (next is NoLoginRefreshTokenStateLoading) {
isLoading.value = true;
} else if (next is NoLoginRefreshTokenStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginRefreshTokenStateDone) {
isLoading.value = false;
// check proses aksi yg berlangsung
if (readProsesAksi == Constant.getRiwayat) {
ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan(
host: host,
client_id: client_id,
token: token,
expire_date: expire_date,
);
}
}
});
// listRiwayProvider
ref.listen(noLoginRiwayatScanProvider, (prev, next) async {
if (next is NoLoginRiwayatScanStateLoading) {
isLoading.value = true;
} else if (next is NoLoginRiwayatScanStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginRiwayatScanStateDone) {
isLoading.value = false;
}
});
void getRiwayat() async {
if (await isTokenExpired() == true) {
ref.read(noLoginprosesAksi.notifier).state = Constant.getRiwayat;
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
} else {
ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan(
host: host,
client_id: client_id,
token: token,
expire_date: expire_date,
);
}
}
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus!.unfocus();
},
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Constant.bgGrey,
body: Column(
children: [
// atas
Image.asset(
'images/vektoratas.png',
width: double.infinity,
fit: BoxFit.cover,
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 34,
),
),
// button scan ktp
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: SizedBox(
width: double.infinity,
height: Constant.getActualYPhone(
context: context,
y: 48,
),
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pushNamed(noLoginScanRoute);
},
style: ElevatedButton.styleFrom(
backgroundColor: Constant.bgButton,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'images/iconbarcode.png',
width: Constant.getActualXPhone(
context: context,
x: 20,
),
height: Constant.getActualYPhone(
context: context,
y: 20,
),
fit: BoxFit.cover,
),
SizedBox(
width: Constant.getActualXPhone(
context: context,
x: 10,
),
),
Text(
'SCAN KTP BARU',
style:
Constant.titleButton500(context: context).copyWith(
color: Constant.textWhite,
),
),
],
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 56,
),
),
// judul
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: Column(
children: [
// DEBUG
// Text(currentUser?.token ?? ""),
// SizedBox(
// height: Constant.getActualYPhone(
// context: context,
// y: 24,
// ),
// ),
// Text(currentUser?.expire_date ?? ""),
// SizedBox(
// height: Constant.getActualYPhone(
// context: context,
// y: 24,
// ),
// ),
// DEBUG
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Riwayat',
style: Constant.titleRiwayat(context: context).copyWith(
color: Constant.textBlack,
),
),
Text(
'20 Scan Terakhir',
style:
Constant.titleInputan500(context: context).copyWith(
color: Constant.inputanGrey,
),
),
],
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 24,
),
),
],
),
),
// loading
if (isLoading.value)
Expanded(
child: Center(
child: CircularProgressIndicator(
color: Constant.bgButton,
),
),
)
// belum ada riwayat
else if (listScanArr.isEmpty)
Expanded(
child: RefreshIndicator(
color: Constant.bgButton,
onRefresh: () async {
getRiwayat();
},
child: ListView(
physics:
AlwaysScrollableScrollPhysics(), // Agar bisa di-refresh meski kosong
children: [
SizedBox(
height: MediaQuery.of(context).size.height *
0.3), // Atur tinggi agar teks di tengah
Align(
alignment: Alignment.center,
child: Text(
'Belum Ada Riwayat',
style: Constant.titleInputan500(context: context),
),
),
],
),
),
)
// list card
else
Expanded(
child: RefreshIndicator(
color: Constant.bgButton,
onRefresh: () async {
getRiwayat();
},
child: Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(context: context, x: 13),
right: Constant.getActualXPhone(context: context, x: 13),
bottom: Constant.getActualYPhone(context: context, y: 20),
),
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: listScanArr.length,
itemBuilder: (context, i) {
final obj = listScanArr[i];
return Padding(
padding: EdgeInsets.only(
top: Constant.getActualYPhone(
context: context, y: 15),
),
child: NoLoginCardRiwayatScan(
data: obj,
),
);
},
),
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,88 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scanktpflutter/model/no-login/no_login_person_ktp_model.dart';
import '../../repository/no-login-scan/no_login_scan_repository.dart';
import '../../provider/no-login-scan/no_login_scan_provider.dart';
import '../../provider/dio_provider.dart';
import '../../repository/base_repository.dart';
// 3. state provider
final noLoginRiwayatScanProvider =
StateNotifierProvider<NoLoginRiwayatScanNotifier, NoLoginRiwayatScanState>(
(ref) => NoLoginRiwayatScanNotifier(ref: ref));
// 2. notifier
class NoLoginRiwayatScanNotifier
extends StateNotifier<NoLoginRiwayatScanState> {
final Ref ref;
NoLoginRiwayatScanNotifier({required this.ref})
: super(NoLoginRiwayatScanStateInit());
void noLoginRiwayatScan({
required String host,
required String client_id,
required String token,
required String expire_date,
}) async {
try {
state = NoLoginRiwayatScanStateLoading();
final resp = await NoLoginScanRepository(
dio: ref.read(dioProvider),
).listRiwayatScanRepo(
host: host,
client_id: client_id,
token: token,
);
// print(resp);
state = NoLoginRiwayatScanStateDone(model: resp);
ref.read(listScanRwt.notifier).state = resp;
// // fungsi check token expired
// if (await isTokenExpired()) {
// print("Token expired, refreshing...");
// ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
// client_id: userId,
// host: host,
// expire_date:expire_date,
// );
// }
} catch (e) {
if (e is BaseRepositoryException) {
state = NoLoginRiwayatScanStateError(message: e.message);
} else {
state = NoLoginRiwayatScanStateError(message: e.toString());
}
}
}
}
// 1. state
abstract class NoLoginRiwayatScanState extends Equatable {
final DateTime date;
const NoLoginRiwayatScanState(this.date);
@override
List<Object?> get props => [date];
}
class NoLoginRiwayatScanStateInit extends NoLoginRiwayatScanState {
NoLoginRiwayatScanStateInit() : super(DateTime.now());
}
class NoLoginRiwayatScanStateLoading extends NoLoginRiwayatScanState {
NoLoginRiwayatScanStateLoading() : super(DateTime.now());
}
class NoLoginRiwayatScanStateError extends NoLoginRiwayatScanState {
final String message;
NoLoginRiwayatScanStateError({
required this.message,
}) : super(DateTime.now());
}
class NoLoginRiwayatScanStateDone extends NoLoginRiwayatScanState {
final List<NoLoginPersonKtpModel> model;
NoLoginRiwayatScanStateDone({
required this.model,
}) : super(DateTime.now());
}

View File

@@ -0,0 +1,81 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scanktpflutter/repository/no-login-scan/no_login_scan_repository.dart';
import '../../provider/dio_provider.dart';
import '../../repository/base_repository.dart';
// 3. state provider
final noLoginEditScanProvider = StateNotifierProvider<NoLoginEditScanNotifier, NoLoginEditScanState>(
(ref) => NoLoginEditScanNotifier(ref: ref));
// 2. notifier
class NoLoginEditScanNotifier extends StateNotifier<NoLoginEditScanState> {
final Ref ref;
NoLoginEditScanNotifier({required this.ref}) : super(NoLoginEditScanStateInit());
void editScan({
required String token,
required String host,
required String client_id,
required String Person_ID,
required String Person_NIK,
required String Person_Name,
required String Person_Dob,
required String Person_Sex,
}) async {
try {
state = NoLoginEditScanStateLoading();
final resp = await NoLoginScanRepository(
dio: ref.read(dioProvider),
).prosesEdit(
token: token,
host: host,
Person_ID: Person_ID,
Person_NIK: Person_NIK,
Person_Name: Person_Name,
Person_Dob: Person_Dob,
Person_Sex: Person_Sex,
client_id: client_id,
);
// print(resp);
state = NoLoginEditScanStateDone(pesan: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = NoLoginEditScanStateError(message: e.message);
} else {
state = NoLoginEditScanStateError(message: e.toString());
}
}
}
}
// 1. state
abstract class NoLoginEditScanState extends Equatable {
final DateTime date;
const NoLoginEditScanState(this.date);
@override
List<Object?> get props => [date];
}
class NoLoginEditScanStateInit extends NoLoginEditScanState {
NoLoginEditScanStateInit() : super(DateTime.now());
}
class NoLoginEditScanStateLoading extends NoLoginEditScanState {
NoLoginEditScanStateLoading() : super(DateTime.now());
}
class NoLoginEditScanStateError extends NoLoginEditScanState {
final String message;
NoLoginEditScanStateError({
required this.message,
}) : super(DateTime.now());
}
class NoLoginEditScanStateDone extends NoLoginEditScanState {
final String pesan;
NoLoginEditScanStateDone({
required this.pesan,
}) : super(DateTime.now());
}

View File

@@ -0,0 +1,715 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:scanktpflutter/model/no-login/no_login_sex_model.dart';
import 'package:scanktpflutter/provider/no-login-scan/no_login_scan_provider.dart';
import 'package:scanktpflutter/screen/no-login-scan/no_login_edit_scan_provider.dart';
import 'package:scanktpflutter/screen/scan/edit_scan_provider.dart';
import '../../model/sex_model.dart';
import '../../provider/no-login/no_login_current_user_provider.dart';
import '../../widget/customsnackbarwidget.dart';
import '../../app/app_extension.dart';
import '../../app/constant.dart';
import '../../app/route.dart';
import '../../provider/current_user_provider.dart';
import '../home/list_riwayat_scan_provider.dart';
import '../no-login-home/no_login_riwayat_scan_provider.dart';
import '../no-login/no_login_refresh_token_provider.dart';
import 'no_login_sex_provider.dart';
class NoLoginEditScanScreen extends HookConsumerWidget {
const NoLoginEditScanScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
SystemUiOverlay.bottom,
]);
final currentUser = ref.watch(noLoginCurrentUserProvider);
// final username = currentUser?.model.username ?? "-";
final host = currentUser?.host ?? "";
final isLoading = useState<bool>(false);
final client_id = currentUser?.client_id ?? "";
final token = currentUser?.token ?? "";
final expire_date = currentUser?.expire_date ?? "";
final readProsesAksi = ref.watch(noLoginprosesAksi);
final baseUrl = "https://$host/";
final selectedPersonId = ref.watch(selectedPersonIdx);
final listDataEdit = ref.watch(selectedEdit);
final listSex = useState<List<NoLoginSexModel>>(
List.empty(growable: true),
);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
if (client_id == "0" || token == "") {
// not logged in and doesn't have token
Navigator.of(context)
.pushNamedAndRemoveUntil(noLoginRoute, (route) => false);
return;
}
if (token != "") {
if (await isTokenExpired() == true) {
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
}
}
});
return () {};
}, [currentUser]);
// sex
ref.listen(noLoginSexProvider, (prev, next) {
if (next is NoLoginSexStateLoading) {
isLoading.value = true;
} else if (next is NoLoginSexStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginSexStateDone) {
isLoading.value = false;
listSex.value = next.model;
// ref.read(eSexCtr.notifier).state =
// TextEditingController(text: next.model[0].m_sexname);
final sexSel = ref.read(selectedEdit.notifier).state;
if (next.model.isNotEmpty) {
final matchedItems = next.model
.where((item) => item.M_SexID == sexSel.personSex)
.toList();
if (matchedItems.length == 1) {
ref.read(eSexCtr.notifier).state = TextEditingController(
text: matchedItems[0].m_sexname,
);
} else {
// Jika lebih dari satu atau tidak ada yang cocok
print('Error: Multiple or no matching items found.');
// Bisa juga mengatur default value jika tidak ditemukan
ref.read(eSexCtr.notifier).state =
TextEditingController(text: ''); // atau nilai default lain
}
}
}
});
// isLoading
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) async {
if (isLoading.value == true) {
snackbarWidget(
context,
"Sedang Memuat Data",
snackbarType.warning,
Duration(days: 1),
);
}
});
return () {};
}, [isLoading]);
// check person id
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) async {
final personIDx = selectedPersonId;
if (personIDx == "0") {
snackbarWidget(
context,
'Gagal mendapatkan data',
snackbarType.error,
Duration(seconds: 3),
);
Navigator.of(context)
.pushNamedAndRemoveUntil(homeRoute, (route) => false);
return;
} else {
// listDataEdit
// set ke inputan
ref.read(eQrCode.notifier).state =
TextEditingController(text: listDataEdit.personQrCode);
ref.read(eNikCtr.notifier).state =
TextEditingController(text: listDataEdit.personNIK);
ref.read(eNamaCtr.notifier).state =
TextEditingController(text: listDataEdit.personName);
ref.read(eDobCtr.notifier).state = TextEditingController(
text: formatDateJiffy(listDataEdit.personDob));
ref.read(eDobDt.notifier).state =
DateTime.parse(listDataEdit.personDob);
final parsedDate = DateTime.parse(listDataEdit.personDob);
// print("Parsed Date: $parsedDate");
ref.read(eDobDt.notifier).state = parsedDate;
// sex
ref.read(noLoginSexProvider.notifier).sex(
host: host,
client_id: client_id,
token: token,
);
}
});
return () {};
}, [selectedPersonId]);
// date picker
Future<void> _selectDate(BuildContext context, WidgetRef ref) async {
DateTime? newSelectedDate = await showDatePicker(
initialEntryMode: DatePickerEntryMode.calendarOnly,
context: context,
initialDate: (listDataEdit.personDob != "0000-00-00")
? DateTime.parse(listDataEdit.personDob)
: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (newSelectedDate != null) {
ref.read(eDobDt.notifier).state = newSelectedDate;
ref.read(eDobCtr.notifier).state.text =
formatDateJiffy(newSelectedDate.toLocal().toString());
}
}
// refreshToken Provider
ref.listen(noLoginRefreshTokenProvider, (prev, next) async {
if (next is NoLoginRefreshTokenStateLoading) {
isLoading.value = true;
} else if (next is NoLoginRefreshTokenStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginRefreshTokenStateDone) {
isLoading.value = false;
// check proses aksi yg berlangsung
if (readProsesAksi == Constant.getRiwayat) {
ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan(
host: host,
client_id: client_id,
token: token,
expire_date: expire_date,
);
} else if (readProsesAksi == Constant.postEditScan) {
ref.read(noLoginEditScanProvider.notifier).editScan(
token: token,
host: host,
client_id: client_id,
Person_ID: selectedPersonId,
Person_NIK: ref.read(eNikCtr).text,
Person_Name: ref.read(eNamaCtr).text,
Person_Dob: ref.read(eDobCtr).text,
Person_Sex: ref.read(eSexSelected).M_SexID,
);
}
}
});
// void getRiwayat
void getRiwayat() async {
if (await isTokenExpired() == true) {
ref.read(noLoginprosesAksi.notifier).state = Constant.getRiwayat;
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
} else {
ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan(
host: host,
client_id: client_id,
token: token,
expire_date: expire_date,
);
}
}
// listRiwayProvider
ref.listen(noLoginRiwayatScanProvider, (prev, next) async {
if (next is NoLoginRiwayatScanStateLoading) {
isLoading.value = true;
} else if (next is NoLoginRiwayatScanStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginRiwayatScanStateDone) {
// isLoading.value = false;
Navigator.of(context).pop();
Navigator.of(context).pushNamedAndRemoveUntil(
noLoginHomeRoute,
(route) => false,
);
}
});
// proses edit
ref.listen(noLoginEditScanProvider, (prev, next) {
if (next is NoLoginEditScanStateLoading) {
isLoading.value = true;
} else if (next is NoLoginEditScanStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginEditScanStateDone) {
isLoading.value = false;
getRiwayat();
}
});
void postEdit() async {
if (await isTokenExpired() == true) {
ref.read(noLoginprosesAksi.notifier).state = Constant.postEditScan;
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
} else {
ref.read(noLoginEditScanProvider.notifier).editScan(
token: token,
host: host,
client_id: client_id,
Person_ID: selectedPersonId,
Person_NIK: ref.read(eNikCtr).text,
Person_Name: ref.read(eNamaCtr).text,
Person_Dob: ref.read(eDobCtr).text,
Person_Sex: ref.read(eSexSelected).M_SexID,
);
}
}
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus!.unfocus();
},
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Constant.bgGrey,
body: ListView(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
// atas
Image.asset(
'images/vektoratas.png',
width: double.infinity,
fit: BoxFit.cover,
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 34,
),
),
// image ktp
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Image.network(
baseUrl + listDataEdit.personUrl,
fit: BoxFit.fitWidth,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 32,
),
),
// inputan qrcode
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'QR Code',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: TextField(
readOnly: true,
maxLines: 2,
controller: ref.read(eQrCode),
decoration: InputDecoration(
// hintText: "NIK",
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
// inputan nik
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'NIK',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: TextField(
controller: ref.read(eNikCtr),
keyboardType: TextInputType.number,
decoration: InputDecoration(
// hintText: "NIK",
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
// inputan nama
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'Nama',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: TextField(
controller: ref.read(eNamaCtr),
decoration: InputDecoration(
// hintText: "NIK",
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
// inputan dob
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'DOB',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: TextField(
readOnly: true,
controller: ref.read(eDobCtr),
onTap: () {
_selectDate(context, ref);
},
decoration: InputDecoration(
// hintText: "NIK",
suffixIcon: IconButton(
icon: Icon(Icons.calendar_today,
color: Constant.textCardGrey),
onPressed: () {
_selectDate(context, ref);
},
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
// inputan jenis kelamin
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'Jenis Kelamin',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<NoLoginSexModel>(
isExpanded: true,
value: (listSex.value.isNotEmpty)
? listSex.value.firstWhere(
(sex) =>
sex.M_SexID ==
ref.read(eSexSelected.notifier).state.M_SexID,
orElse: () => listSex.value[0],
)
: null,
items: listSex.value.map((NoLoginSexModel g) {
return DropdownMenuItem<NoLoginSexModel>(
value: g,
child: Text(
g.m_sexname,
style: Constant.title_400(context: context).copyWith(
color: Colors.black,
fontWeight: FontWeight.normal,
),
),
);
}).toList(),
onChanged: (NoLoginSexModel? value) {
if (value != null) {
ref.read(eSexSelected.notifier).state = value;
ref.read(eSexCtr.notifier).state = TextEditingController(
text: value.m_sexname,
);
FocusScope.of(context).unfocus();
}
},
buttonStyleData: ButtonStyleData(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: Colors.grey),
),
padding: EdgeInsets.symmetric(horizontal: 12),
width: double.infinity,
),
dropdownStyleData: DropdownStyleData(
maxHeight: 100,
width: MediaQuery.of(context).size.width - 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.white,
),
offset: Offset(0, 2),
),
menuItemStyleData: MenuItemStyleData(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
onMenuStateChange: (isOpen) {
if (!isOpen) {
ref.read(eSexCtr).clear();
}
},
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 44,
),
),
// button save
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 16,
),
right: Constant.getActualXPhone(
context: context,
x: 16,
),
),
child: SizedBox(
width: double.infinity,
height: Constant.getActualYPhone(
context: context,
y: 48,
),
child: ElevatedButton(
onPressed: () {
postEdit();
},
style: ElevatedButton.styleFrom(
backgroundColor: Constant.bgButton,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'SAVE',
style:
Constant.titleButton500(context: context).copyWith(
color: Constant.textWhite,
),
),
],
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 70,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,717 @@
import 'dart:convert';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image/image.dart' as img;
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:scanktpflutter/model/no-login/no_login_edit_person_ktp_model.dart';
import 'package:scanktpflutter/model/no-login/no_login_sex_model.dart';
import '../../app/app_extension.dart';
import '../../app/route.dart';
import '../../app/constant.dart';
import '../../provider/no-login-scan/no_login_scan_provider.dart';
import '../../provider/no-login/no_login_current_user_provider.dart';
import '../../widget/customsnackbarwidget.dart';
import '../no-login-home/no_login_riwayat_scan_provider.dart';
import '../no-login/no_login_refresh_token_provider.dart';
import 'no_login_upload_scan_provider.dart';
class NoLoginScanScreen extends HookConsumerWidget {
const NoLoginScanScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
SystemUiOverlay.bottom,
]);
final judulAppBar = useState<String>("Scan QRCode");
final cameraOn = useState<bool>(true);
final qrCodeStr = useState<String>("Scan QRCode untuk dapat merekam");
final judulTombol = useState<String>("SCAN QR CODE");
final awalan = useState("Info : ");
final judulPosisiHp = useState<String>("Posisi HP Landscape");
// final scannerCtr = useState<MobileScanner?>(null);
final cameraOrScanner = useState<String>("SCAN");
final loadingScreen = useState<bool>(true);
final cameraController = useState<CameraController?>(null);
final initializeControllerFuture = useState<Future<void>?>(null);
final capturedImage = useState<XFile?>(null);
final croppedImage = useState<File?>(null);
final isLoading = useState<bool>(false);
final isLoadingUpload = useState<bool>(false);
// auth
final currentUser = ref.watch(noLoginCurrentUserProvider);
final host = currentUser?.host ?? "";
final client_id = currentUser?.client_id ?? "";
final expire_date = currentUser?.expire_date ?? "";
final token = currentUser?.token ?? "";
final selectedResolution =
useState<ResolutionPreset>(ResolutionPreset.medium);
final readProsesAksi = ref.watch(noLoginprosesAksi);
final base64Post = useState("");
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
if (client_id == "0" || token == "") {
// not logged in and doesn't have token
Navigator.of(context)
.pushNamedAndRemoveUntil(noLoginRoute, (route) => false);
return;
}
if (token != "") {
if (await isTokenExpired() == true) {
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
}
}
});
return () {};
}, [currentUser]);
Future<void> initializeCamera() async {
try {
final cameras = await availableCameras();
cameraController.value = CameraController(
cameras[0],
selectedResolution.value,
);
initializeControllerFuture.value = cameraController.value!.initialize();
await initializeControllerFuture.value;
if (cameraController.value!.value.isInitialized) {
await cameraController.value!.setFlashMode(FlashMode.off);
}
} catch (e) {
print('Error initializing camera: $e');
}
}
useEffect(() {
Future<void> initialize() async {
// delay 3 second e
await Future.delayed(
Duration(seconds: 3),
);
await initializeCamera();
loadingScreen.value = false; // Hide loading screen untuk inisialisasi
}
initialize();
return null;
}, []);
if (loadingScreen.value) {
return Scaffold(
appBar: AppBar(
title: (judulTombol.value == "SCAN")
? Text(
judulPosisiHp.value,
style: Constant.titlePosisiHP(context: context),
)
: Text(
judulAppBar.value,
style: Constant.titlePosisiHP(context: context),
),
automaticallyImplyLeading: false,
),
backgroundColor: Colors.black.withOpacity(0.5),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: Constant.textWhite,
),
SizedBox(height: 20),
Text(
"Menunggu Camera Siap",
style: TextStyle(
color: Constant.textWhite,
),
),
],
),
),
bottomNavigationBar: BottomAppBar(
height: Constant.getActualYPhone(
context: context,
y: 100,
),
shape: const CircularNotchedRectangle(),
child: Row(
children: [
ElevatedButton.icon(
onPressed: () {
capturedImage.value = null;
croppedImage.value = null;
ref.read(barcodeX.notifier).state = Barcode();
judulTombol.value = "SCAN QR CODE";
qrCodeStr.value = "Scan QRCode untuk dapat merekam";
cameraOn.value = false;
awalan.value = "Info :";
cameraOrScanner.value = "SCAN";
Navigator.of(context).pop();
},
icon: Icon(
Icons.arrow_back,
size: 17,
color: Constant.textWhite,
),
label: Text(
'Kembali',
style: Constant.cardText(context: context).copyWith(
color: Constant.textWhite,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Constant.textCardGrey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
),
const Spacer(),
],
),
),
);
}
void handleBarcode(BarcodeCapture barcodes) async {
if (barcodes.barcodes.isNotEmpty) {
final scannedBarcode =
barcodes.barcodes.firstOrNull?.displayValue ?? "";
if (scannedBarcode.isNotEmpty) {
await initializeCamera();
ref.read(barcodeX.notifier).state = barcodes.barcodes.first;
qrCodeStr.value = scannedBarcode;
awalan.value = "QrCode : ";
cameraOn.value = true;
judulTombol.value = "FOTO";
cameraOrScanner.value = "CAMERA";
}
}
}
useEffect(() {
Future<void> initializeCamera() async {
try {
final cameras = await availableCameras();
cameraController.value = CameraController(
cameras[0],
selectedResolution.value,
);
initializeControllerFuture.value =
cameraController.value!.initialize();
await initializeControllerFuture.value;
if (cameraController.value!.value.isInitialized) {
await cameraController.value!.setFlashMode(FlashMode.off);
}
} catch (e) {
print('Error initializing camera: $e');
}
}
initializeCamera();
return () {};
}, []);
// void getRiwayat
void getRiwayat() async {
if (await isTokenExpired() == true) {
ref.read(noLoginprosesAksi.notifier).state = Constant.getRiwayat;
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
} else {
ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan(
host: host,
client_id: client_id,
token: token,
expire_date: expire_date,
);
}
}
// void prosesUpload
void prosesUpload() async {
print("Token expired, refreshing...");
print("client_id : $client_id");
print("host : $host");
print("expire_date : $expire_date");
print("token : $token");
print("qrCodeStr.value : ${qrCodeStr.value}");
if (await isTokenExpired() == true) {
ref.read(noLoginprosesAksi.notifier).state = Constant.postUploadFoto;
print("Token expired, refreshing...");
ref.read(noLoginRefreshTokenProvider.notifier).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
} else {
ref.read(noLoginUploadScanProvider.notifier).uploadScan(
host: host,
base64File: base64Post.value,
client_id: client_id,
qr_code: qrCodeStr.value,
token: token,
);
}
}
Future<void> initializeCameraAfterChangeResolution() async {
final cameras = await availableCameras();
cameraController.value = CameraController(
cameras[0],
// ResolutionPreset.max,
// ResolutionPreset.medium,
selectedResolution.value);
initializeControllerFuture.value = cameraController.value!.initialize();
await initializeControllerFuture.value;
await cameraController.value!.setFlashMode(FlashMode.off);
}
Future<File> rotateAndCropImage(File imageFile) async {
Uint8List imageBytes = await imageFile.readAsBytes();
img.Image? original = img.decodeImage(imageBytes);
if (original == null) return imageFile;
img.Image rotated = img.bakeOrientation(original);
int width = rotated.width;
int height = rotated.height;
bool isPortrait = height > width;
int cropWidth, cropHeight;
if (isPortrait) {
cropHeight = (height * 0.7).toInt();
cropWidth = (cropHeight ~/ 1.59).toInt();
} else {
cropWidth = (width * 0.7).toInt();
cropHeight = (cropWidth ~/ 1.59).toInt();
}
int left = ((width - cropWidth) ~/ 2).toInt();
int top = ((height - cropHeight) ~/ 2).toInt();
img.Image cropped = img.copyCrop(rotated,
x: left, y: top, width: cropWidth, height: cropHeight);
File croppedFile = File('${imageFile.path}_cropped.jpg');
await croppedFile.writeAsBytes(img.encodeJpg(cropped));
return croppedFile;
}
Future<File> rotateImage(File imageFile) async {
Uint8List bytes = await imageFile.readAsBytes();
img.Image? image = img.decodeImage(bytes);
if (image == null) return imageFile;
img.Image rotated = img.copyRotate(image, angle: -90);
File rotatedFile = File('${imageFile.path}_rotated.jpg');
await rotatedFile.writeAsBytes(img.encodeJpg(rotated));
return rotatedFile;
}
Future<void> captureAndCropImage() async {
try {
isLoading.value = true;
await initializeControllerFuture.value;
// Ambil gambar dari kamera
final image = await cameraController.value!.takePicture();
File cropped = await rotateAndCropImage(File(image.path));
// Rotate gambar setelah cropping
File rotatedImage = await rotateImage(cropped);
// Convert to grayscale
Uint8List bytes = await rotatedImage.readAsBytes();
img.Image? coloredImage = img.decodeImage(bytes);
if (coloredImage != null) {
img.Image grayscaleImage = img.grayscale(coloredImage);
rotatedImage.writeAsBytesSync(img.encodeJpg(grayscaleImage));
}
// Simpan hasil yang sudah di-crop dan di-rotate
capturedImage.value = image;
croppedImage.value = rotatedImage;
// post ke BE
Uint8List finalBytes = await croppedImage.value!.readAsBytes();
String base64String = base64Encode(finalBytes);
base64Post.value = base64String;
prosesUpload();
} catch (e) {
print("Error capturing image: $e");
} finally {
isLoading.value = false;
}
}
// refreshToken Provider
ref.listen(noLoginRefreshTokenProvider, (prev, next) async {
if (next is NoLoginRefreshTokenStateLoading) {
isLoading.value = true;
} else if (next is NoLoginRefreshTokenStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginRefreshTokenStateDone) {
isLoading.value = false;
// check proses aksi yg berlangsung
if (readProsesAksi == Constant.getRiwayat) {
ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan(
host: host,
client_id: client_id,
token: token,
expire_date: expire_date,
);
} else if (readProsesAksi == Constant.postUploadFoto) {
ref.read(noLoginUploadScanProvider.notifier).uploadScan(
host: host,
base64File: base64Post.value,
client_id: client_id,
qr_code: qrCodeStr.value,
token: token,
);
}
}
});
// listRiwayProvider
ref.listen(noLoginRiwayatScanProvider, (prev, next) async {
if (next is NoLoginRiwayatScanStateLoading) {
isLoading.value = true;
} else if (next is NoLoginRiwayatScanStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginRiwayatScanStateDone) {
// isLoading.value = false;
Navigator.of(context).pop();
Navigator.of(context).pushNamed(
noLoginEditScanRoute,
);
}
});
// proses upload
ref.listen(noLoginUploadScanProvider, (prev, next) {
if (next is NoLoginUploadScanStateLoading) {
isLoadingUpload.value = true;
} else if (next is NoLoginUploadScanStateError) {
isLoadingUpload.value = false;
// errorMessage.value = next.message;
print("Err : ${next.message}");
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is NoLoginUploadScanStateDone) {
isLoadingUpload.value = false;
ref.read(selectedPersonIdx.notifier).state = next.model[0].personID;
// set SEX
ref.read(eSexSelected.notifier).state = NoLoginSexModel(
M_SexID: next.model[0].personSex,
M_SexCode: "",
m_sexname: next.model[0].m_sexname,
M_SexNameLang: "");
ref.read(selectedEdit.notifier).state = NoLoginEditPersonKtpModel(
personID: next.model[0].personID,
personNIK: next.model[0].personNIK,
personName: next.model[0].personName,
m_sexname: next.model[0].m_sexname,
personDob: next.model[0].personDob,
personSex: next.model[0].personSex,
personUrl: next.model[0].personUrl,
personQrCode: next.model[0].personQrCode,
);
getRiwayat();
}
});
// loading proses upload useEffect
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) {
if (isLoadingUpload.value == true) {
snackbarWidget(
context,
'Sedang Upload Foto...',
snackbarType.warning,
Duration(seconds: 3),
);
}
});
return () {};
}, [isLoadingUpload.value]);
return Scaffold(
appBar: AppBar(
title: (cameraOrScanner.value == "SCAN")
? Text(
judulAppBar.value,
style: Constant.titlePosisiHP(context: context),
)
: Text(
judulPosisiHp.value,
style: Constant.titlePosisiHP(context: context),
),
automaticallyImplyLeading: false,
actions: [
if (cameraOn.value == true)
DropdownButton<ResolutionPreset>(
value: selectedResolution.value,
onChanged: (ResolutionPreset? newValue) {
if (newValue != null) {
selectedResolution.value = newValue;
initializeCameraAfterChangeResolution();
}
},
items: ResolutionPreset.values.map((ResolutionPreset value) {
return DropdownMenuItem<ResolutionPreset>(
value: value,
child: Text(value.toString().split('.').last),
);
}).toList(),
underline: SizedBox.shrink(),
),
],
),
backgroundColor: Colors.black.withOpacity(0.5),
body: Stack(
children: [
// jika camera scan ktp true show
if (cameraOrScanner.value == "CAMERA") ...[
if (cameraController.value != null && croppedImage.value == null)
FutureBuilder<void>(
future: initializeControllerFuture.value,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return CameraPreview(cameraController.value!);
} else {
return Center(
child: CircularProgressIndicator(
color: Constant.textWhite,
),
);
}
},
),
// jika sudah ada foto
if (croppedImage.value != null) ...[
Container(
width: double.infinity,
height: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.file(
croppedImage.value!,
fit: BoxFit.contain,
),
),
),
] else ...[
// bingkai foto
Positioned.fill(
top: Constant.getActualYPhone(
context: context,
y: 40,
),
child: CustomPaint(
painter: OverlayPainter(),
),
),
],
],
if (cameraOrScanner.value == "SCAN") ...[
SizedBox(
width: double.infinity,
height: double.infinity,
child: MobileScanner(
// controller: scannerCtr.value?.controller,
onDetect: handleBarcode,
),
),
],
// Info
Container(
width: double.infinity,
height: Constant.getActualYPhone(
context: context,
y: 100,
),
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 10),
color: Constant.inputanGrey,
child: Text(
"${awalan.value} ${qrCodeStr.value}",
style: TextStyle(color: Colors.white),
),
),
// loading
if (isLoading.value)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.5),
child: const Center(
child: CircularProgressIndicator(
color: Colors.white,
),
),
),
),
],
),
bottomNavigationBar: BottomAppBar(
height: Constant.getActualYPhone(
context: context,
y: 100,
),
shape: const CircularNotchedRectangle(),
child: Row(
children: [
ElevatedButton.icon(
onPressed: () {
capturedImage.value = null;
croppedImage.value = null;
ref.read(barcodeX.notifier).state = Barcode();
judulTombol.value = "SCAN QR CODE";
qrCodeStr.value = "Scan QRCode untuk dapat merekam";
cameraOn.value = false;
awalan.value = "Info :";
cameraOrScanner.value = "SCAN";
Navigator.of(context).pop();
},
icon: Icon(
Icons.arrow_back,
size: 17,
color: Constant.textWhite,
),
label: Text(
'Kembali',
style: Constant.cardText(context: context).copyWith(
color: Constant.textWhite,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Constant.textCardGrey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
),
const Spacer(),
// scan
ElevatedButton.icon(
onPressed: captureAndCropImage,
icon: Icon(
Icons.camera,
size: 17,
color: Constant.textWhite,
),
label: Text(
judulTombol.value,
style: Constant.cardText(context: context).copyWith(
color: Constant.textWhite,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Constant.bgButton,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
),
],
),
),
);
}
}
class OverlayPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black.withOpacity(0.6)
..style = PaintingStyle.fill;
final borderPaint = Paint()
..color = Colors.yellow
..strokeWidth = 4
..style = PaintingStyle.stroke;
double boxHeight = size.height * 0.7;
double boxWidth = boxHeight / 1.59;
double left = (size.width - boxWidth) / 2;
double top = (size.height - boxHeight) / 2;
Path path = Path()
..addRect(Rect.fromLTWH(0, 0, size.width, size.height))
..addRect(Rect.fromLTWH(left, top, boxWidth, boxHeight))
..fillType = PathFillType.evenOdd;
canvas.drawPath(path, paint);
canvas.drawRect(Rect.fromLTWH(left, top, boxWidth, boxHeight), borderPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}

View File

@@ -0,0 +1,73 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scanktpflutter/model/no-login/no_login_sex_model.dart';
import 'package:scanktpflutter/repository/no-login-scan/no_login_scan_repository.dart';
import '../../provider/dio_provider.dart';
import '../../repository/base_repository.dart';
// 3. state provider
final noLoginSexProvider =
StateNotifierProvider<NoLoginSexNotifier, NoLoginSexState>(
(ref) => NoLoginSexNotifier(ref: ref));
// 2. notifier
class NoLoginSexNotifier extends StateNotifier<NoLoginSexState> {
final Ref ref;
NoLoginSexNotifier({required this.ref}) : super(NoLoginSexStateInit());
void sex({
required String host,
required String client_id,
required String token,
}) async {
try {
state = NoLoginSexStateLoading();
final resp = await NoLoginScanRepository(
dio: ref.read(dioProvider),
).sexRepo(
host: host,
client_id: client_id,
token: token,
);
// print(resp);
state = NoLoginSexStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = NoLoginSexStateError(message: e.message);
} else {
state = NoLoginSexStateError(message: e.toString());
}
}
}
}
// 1. state
abstract class NoLoginSexState extends Equatable {
final DateTime date;
const NoLoginSexState(this.date);
@override
List<Object?> get props => [date];
}
class NoLoginSexStateInit extends NoLoginSexState {
NoLoginSexStateInit() : super(DateTime.now());
}
class NoLoginSexStateLoading extends NoLoginSexState {
NoLoginSexStateLoading() : super(DateTime.now());
}
class NoLoginSexStateError extends NoLoginSexState {
final String message;
NoLoginSexStateError({
required this.message,
}) : super(DateTime.now());
}
class NoLoginSexStateDone extends NoLoginSexState {
final List<NoLoginSexModel> model;
NoLoginSexStateDone({
required this.model,
}) : super(DateTime.now());
}

View File

@@ -0,0 +1,78 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../model/no-login/no_login_sukses_person_model.dart';
import '../../repository/no-login-scan/no_login_scan_repository.dart';
import '../../provider/dio_provider.dart';
import '../../repository/base_repository.dart';
// 3. state provider
final noLoginUploadScanProvider =
StateNotifierProvider<NoLoginUploadScanNotifier, NoLoginUploadScanState>(
(ref) => NoLoginUploadScanNotifier(ref: ref));
// 2. notifier
class NoLoginUploadScanNotifier extends StateNotifier<NoLoginUploadScanState> {
final Ref ref;
NoLoginUploadScanNotifier({required this.ref})
: super(NoLoginUploadScanStateInit());
void uploadScan({
required String host,
required String client_id,
required String base64File,
required String qr_code,
required String token,
}) async {
try {
state = NoLoginUploadScanStateLoading();
final resp = await NoLoginScanRepository(
dio: ref.read(dioProvider),
).prosesScan(
host: host,
base64File: base64File,
client_id: client_id,
qr_code: qr_code,
token: token,
);
// print(resp);
state = NoLoginUploadScanStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = NoLoginUploadScanStateError(message: e.message);
} else {
state = NoLoginUploadScanStateError(message: e.toString());
}
}
}
}
// 1. state
abstract class NoLoginUploadScanState extends Equatable {
final DateTime date;
const NoLoginUploadScanState(this.date);
@override
List<Object?> get props => [date];
}
class NoLoginUploadScanStateInit extends NoLoginUploadScanState {
NoLoginUploadScanStateInit() : super(DateTime.now());
}
class NoLoginUploadScanStateLoading extends NoLoginUploadScanState {
NoLoginUploadScanStateLoading() : super(DateTime.now());
}
class NoLoginUploadScanStateError extends NoLoginUploadScanState {
final String message;
NoLoginUploadScanStateError({
required this.message,
}) : super(DateTime.now());
}
class NoLoginUploadScanStateDone extends NoLoginUploadScanState {
final List<NoLoginSuksesPersonKtpModel> model;
NoLoginUploadScanStateDone({
required this.model,
}) : super(DateTime.now());
}

View File

@@ -0,0 +1,101 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../model/no-login/no_login_auth_model.dart';
import '../../provider/no-login/no_login_current_user_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/constant.dart';
import '../../app/route.dart';
class NoLoginSplashScreen extends HookConsumerWidget {
const NoLoginSplashScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
SystemUiOverlay.bottom,
]);
// useEffect(() {
// WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
// final shared = await SharedPreferences.getInstance();
// final bearerString = shared.getString(Constant.bearerName);
// if (bearerString == null || bearerString == "null") {
// Timer(const Duration(seconds: 3), () {
// Navigator.of(context).pushNamedAndRemoveUntil(
// noLoginRoute,
// (route) => false,
// );
// });
// return;
// }
// final xmodel = jsonDecode(bearerString);
// if (xmodel == null) return;
// final authModel = NoLoginAuthModel(
// host: xmodel["host"],
// token: xmodel["token"],
// client_id: xmodel['client_id'],
// expire_date: xmodel['expire_date'],
// isLogin: xmodel['isLogin'],
// );
// ref.read(noLoginCurrentUserProvider.notifier).state = authModel;
// Timer(const Duration(seconds: 2), () {
// Navigator.of(context).pushNamedAndRemoveUntil(
// noLoginHomeRoute,
// (route) => false,
// );
// });
// });
// return () {};
// }, []);
return Scaffold(
backgroundColor: Constant.textWhite,
body: Column(
children: [
// Bagian atas
Align(
alignment: Alignment.topRight,
child: SizedBox(
width: Constant.getActualXPhone(context: context, x: 246),
child: Image.asset(
'images/splashatas.png',
fit: BoxFit.fitWidth,
),
),
),
Spacer(),
// Logo di tengah
Center(
child: Image.asset(
'images/logo.png',
width: Constant.getActualXPhone(context: context, x: 164),
),
),
Spacer(),
// Bagian bawah
Align(
alignment: Alignment.bottomLeft,
child: SizedBox(
width: Constant.getActualXPhone(context: context, x: 246),
child: Image.asset(
'images/splashbawah.png',
fit: BoxFit.fitWidth,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,79 @@
import 'dart:convert';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scanktpflutter/model/no-login/no_login_auth_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/constant.dart';
import '../../provider/no-login/no_login_current_user_provider.dart';
import '../../repository/no-login/no_login_auth_repository.dart';
import '../../provider/dio_provider.dart';
// 3. state provider
final noLoginProvider = StateNotifierProvider<NoLoginNotifier, NoLoginState>(
(ref) => NoLoginNotifier(ref: ref));
// 2. notifier
class NoLoginNotifier extends StateNotifier<NoLoginState> {
final Ref ref;
NoLoginNotifier({required this.ref}) : super(NoLoginStateInit());
void prosesLoginF({
required String client_id,
required String host,
required String password,
}) async {
state = NoLoginStateLoading();
final resp = await NoLoginAuthRepository(
dio: ref.read(dioProvider),
).loginF(
client_id: client_id,
host: host,
password: password,
);
state = NoLoginStateDone(model: resp);
// Simpan ke SharedPreferences
final shared = await SharedPreferences.getInstance();
// Simpan token dalam format JSON
final token = jsonEncode({
"expire_date": resp.expire_date,
"host": resp.host,
"date": DateTime.now().toIso8601String(),
"token": resp.token,
"client_id": resp.client_id,
"isLogin": resp.isLogin,
});
await shared.setString(Constant.bearerName, token);
ref.read(noLoginCurrentUserProvider.notifier).state = resp;
}
}
// 1. state
abstract class NoLoginState extends Equatable {
final DateTime date;
const NoLoginState(this.date);
@override
List<Object?> get props => [date];
}
class NoLoginStateInit extends NoLoginState {
NoLoginStateInit() : super(DateTime.now());
}
class NoLoginStateLoading extends NoLoginState {
NoLoginStateLoading() : super(DateTime.now());
}
class NoLoginStateError extends NoLoginState {
final String message;
NoLoginStateError({required this.message}) : super(DateTime.now());
}
class NoLoginStateDone extends NoLoginState {
final NoLoginAuthModel model;
NoLoginStateDone({required this.model}) : super(DateTime.now());
}

View File

@@ -0,0 +1,197 @@
// import 'dart:convert';
// import 'package:equatable/equatable.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
// import '../../provider/no-login/no_login_current_user_provider.dart';
// import '../../model/no-login/no_login_auth_model.dart';
// import '../../repository/no-login/no_login_auth_repository.dart';
// import 'package:shared_preferences/shared_preferences.dart';
// import '../../app/constant.dart';
// import '../../provider/dio_provider.dart';
// import '../../repository/base_repository.dart';
// // 3. state provider
// final noLoginRefreshTokenProvider = StateNotifierProvider<
// NoLoginRefreshTokenNotifier,
// NoLoginRefreshTokenState>((ref) => NoLoginRefreshTokenNotifier(ref: ref));
// // 2. notifier
// class NoLoginRefreshTokenNotifier
// extends StateNotifier<NoLoginRefreshTokenState> {
// final Ref ref;
// NoLoginRefreshTokenNotifier({required this.ref})
// : super(NoLoginRefreshTokenStateInit());
// void refreshToken({
// required String client_id,
// required String host,
// required String expire_date,
// }) async {
// try {
// state = NoLoginRefreshTokenStateLoading();
// final resp = await NoLoginAuthRepository(
// dio: ref.read(dioProvider),
// ).refreshToken(
// client_id: client_id,
// host: host,
// expire_date: expire_date,
// );
// // print(resp);
// state = NoLoginRefreshTokenStateDone(model: resp);
// //Simpan ke token
// final shared = await SharedPreferences.getInstance();
// String expireDateStr = resp.expire_date;
// if (!expireDateStr.contains("T")) {
// expireDateStr = expireDateStr.replaceFirst(" ", "T");
// }
// // Simpan dalam format ISO 8601
// // await shared.setString("expire_date", expireDateStr);
// final token = jsonEncode({
// "host": host,
// "date": DateTime.now().toString(),
// "token": resp.token,
// "client_id": resp.client_id,
// // "expire_date": resp.expire_date,
// "expire_date": expireDateStr,
// "isLogin": resp.isLogin,
// });
// await shared.setString(Constant.bearerName, token);
// ref.read(noLoginCurrentUserProvider.notifier).state = resp;
// // print(shared.getString(Constant.bearerName));
// } catch (e) {
// if (e is BaseRepositoryException) {
// state = NoLoginRefreshTokenStateError(message: e.message);
// } else {
// state = NoLoginRefreshTokenStateError(message: e.toString());
// }
// }
// }
// }
// // 1. state
// abstract class NoLoginRefreshTokenState extends Equatable {
// final DateTime date;
// const NoLoginRefreshTokenState(this.date);
// @override
// List<Object?> get props => [date];
// }
// class NoLoginRefreshTokenStateInit extends NoLoginRefreshTokenState {
// NoLoginRefreshTokenStateInit() : super(DateTime.now());
// }
// class NoLoginRefreshTokenStateLoading extends NoLoginRefreshTokenState {
// NoLoginRefreshTokenStateLoading() : super(DateTime.now());
// }
// class NoLoginRefreshTokenStateError extends NoLoginRefreshTokenState {
// final String message;
// NoLoginRefreshTokenStateError({
// required this.message,
// }) : super(DateTime.now());
// }
// class NoLoginRefreshTokenStateDone extends NoLoginRefreshTokenState {
// final NoLoginAuthModel model;
// NoLoginRefreshTokenStateDone({
// required this.model,
// }) : super(DateTime.now());
// }
// new
import 'dart:convert';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/no-login/no_login_current_user_provider.dart';
import '../../model/no-login/no_login_auth_model.dart';
import '../../repository/no-login/no_login_auth_repository.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/constant.dart';
import '../../provider/dio_provider.dart';
import '../../repository/base_repository.dart';
// 3. State Provider
final noLoginRefreshTokenProvider = StateNotifierProvider<
NoLoginRefreshTokenNotifier,
NoLoginRefreshTokenState>((ref) => NoLoginRefreshTokenNotifier(ref: ref));
// 2. Notifier
class NoLoginRefreshTokenNotifier
extends StateNotifier<NoLoginRefreshTokenState> {
final Ref ref;
NoLoginRefreshTokenNotifier({required this.ref})
: super(NoLoginRefreshTokenStateInit());
void refreshToken({
required String client_id,
required String host,
required String expire_date,
}) async {
try {
state = NoLoginRefreshTokenStateLoading();
final resp = await NoLoginAuthRepository(
dio: ref.read(dioProvider),
).refreshToken(
client_id: client_id,
host: host,
expire_date: expire_date,
);
// Simpan token ke SharedPreferences
final shared = await SharedPreferences.getInstance();
final tokenData = jsonEncode({
"expire_date": resp.expire_date,
"host": host,
"date": DateTime.now().toString(),
"token": resp.token,
"client_id": resp.client_id,
"isLogin": resp.isLogin,
});
await shared.setString(Constant.bearerName, tokenData);
ref.read(noLoginCurrentUserProvider.notifier).state = resp;
state = NoLoginRefreshTokenStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = NoLoginRefreshTokenStateError(message: e.message);
} else {
state = NoLoginRefreshTokenStateError(message: e.toString());
}
}
}
}
// 1. State
abstract class NoLoginRefreshTokenState extends Equatable {
final DateTime date;
const NoLoginRefreshTokenState(this.date);
@override
List<Object?> get props => [date];
}
class NoLoginRefreshTokenStateInit extends NoLoginRefreshTokenState {
NoLoginRefreshTokenStateInit() : super(DateTime.now());
}
class NoLoginRefreshTokenStateLoading extends NoLoginRefreshTokenState {
NoLoginRefreshTokenStateLoading() : super(DateTime.now());
}
class NoLoginRefreshTokenStateError extends NoLoginRefreshTokenState {
final String message;
NoLoginRefreshTokenStateError({
required this.message,
}) : super(DateTime.now());
}
class NoLoginRefreshTokenStateDone extends NoLoginRefreshTokenState {
final NoLoginAuthModel model;
NoLoginRefreshTokenStateDone({
required this.model,
}) : super(DateTime.now());
}

View File

@@ -0,0 +1,439 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/route.dart';
import '../../app/constant.dart';
import '../../model/no-login/no_login_auth_model.dart';
import '../../provider/no-login/no_login_current_user_provider.dart';
import '../../widget/customsnackbarwidget.dart';
import 'no_login_provider.dart';
class NoLoginScreen extends HookConsumerWidget {
const NoLoginScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
SystemUiOverlay.bottom,
]);
final clientIdCtr = useTextEditingController(
text: "sasdev",
);
final passwordCtr = useTextEditingController(
text: "sasdev123",
);
final hostCtr = useTextEditingController(
text: "devone.aplikasi.web.id",
);
final isLoading = useState(false);
final isSuccess = useState(false);
final hasNavigated = useState(false);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final shared = await SharedPreferences.getInstance();
final bearerString = shared.getString(Constant.bearerName);
if (bearerString == null || bearerString.isEmpty) return;
final xmodel = jsonDecode(bearerString);
if (xmodel == null) return;
final authModel = NoLoginAuthModel(
host: xmodel["host"],
token: xmodel["token"],
client_id: xmodel['client_id'],
expire_date: xmodel['expire_date'],
isLogin: xmodel['isLogin'],
);
ref.read(noLoginCurrentUserProvider.notifier).state = authModel;
if (!hasNavigated.value) {
hasNavigated.value = true;
Timer(const Duration(seconds: 2), () {
Navigator.of(context).pushNamedAndRemoveUntil(
noLoginHomeRoute,
(route) => false,
);
});
}
// Timer(const Duration(seconds: 2), () {
// Navigator.of(context).pushNamedAndRemoveUntil(
// noLoginHomeRoute,
// (route) => false,
// );
// });
});
return () {};
}, []);
// loginF
ref.listen(noLoginProvider, (prev, next) {
if (next is NoLoginStateLoading) {
isLoading.value = true;
} else if (next is NoLoginStateError) {
isLoading.value = false;
final errorMessage = next.message.isNotEmpty
? next.message
: "Terjadi kesalahan, silakan coba lagi.";
print('ERROR: $errorMessage');
snackbarWidget(
context,
next.message,
snackbarType.error,
const Duration(seconds: 3),
);
} else if (next is NoLoginStateDone) {
isLoading.value = false;
isSuccess.value = true;
if (!hasNavigated.value) {
hasNavigated.value = true;
Navigator.of(context)
.pushNamedAndRemoveUntil(noLoginHomeRoute, (route) => false);
}
// Navigator.of(context)
// .pushNamedAndRemoveUntil(noLoginHomeRoute, (route) => false);
}
});
void login() {
if (clientIdCtr.text.isEmpty ||
passwordCtr.text.isEmpty ||
hostCtr.text.isEmpty) {
snackbarWidget(
context,
'Inputan wajib diisi',
snackbarType.error,
const Duration(seconds: 3),
);
} else {
// print('proses login');
ref.read(noLoginProvider.notifier).prosesLoginF(
client_id: clientIdCtr.text,
host: hostCtr.text,
password: passwordCtr.text,
);
}
}
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus!.unfocus();
},
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Constant.textWhite,
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// atas
Image.asset(
'images/vektoratas.png',
width: double.infinity,
fit: BoxFit.cover,
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 40,
),
),
// konten didalamnya
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: Text(
'Selamat Datang',
style: Constant.title_700(context: context).copyWith(
color: Constant.textBlack,
),
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: Text(
'Silahkan masuk untuk mengakses akun Anda',
style: Constant.title_400(context: context).copyWith(
color: Constant.textBlack,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 64,
),
),
// inputan
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: Text(
'Client ID',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: TextField(
controller: clientIdCtr,
decoration: const InputDecoration(
hintText: "Masukkan Client ID",
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 20,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: Text(
'Password',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: TextField(
controller: passwordCtr,
obscureText: true,
decoration: const InputDecoration(
hintText: "Masukkan Password",
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 20,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: Text(
'Host',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: TextField(
controller: hostCtr,
decoration: const InputDecoration(
hintText: "Masukkan Host",
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 64,
),
),
// inputan
// button login
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 20,
),
right: Constant.getActualXPhone(
context: context,
x: 20,
),
),
child: SizedBox(
width: double.infinity,
height: Constant.getActualYPhone(
context: context,
y: 48,
),
child: ElevatedButton(
onPressed: () async {
(isLoading.value || (isSuccess.value == true))
? null
: login();
},
style: ElevatedButton.styleFrom(
backgroundColor: Constant.bgButton,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
child: Text(
(isLoading.value) ? 'Loading...' : 'LOGIN',
style: Constant.titleButton500(context: context).copyWith(
color: Constant.textWhite,
),
),
),
),
),
// konten didalamnya
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 60,
),
),
],
),
),
),
);
}
}

View File

@@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:scanktpflutter/screen/scan/edit_scan_provider.dart';
import '../../model/sex_model.dart';
import '../../screen/scan/sex_provider.dart';
import '../../provider/scan_provider.dart';
import '../../screen/scan/sex_provider.dart';
import '../../widget/customsnackbarwidget.dart';
import '../../app/app_extension.dart';

View File

@@ -5,10 +5,12 @@
import FlutterMacOS
import Foundation
import mobile_scanner
import path_provider_foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@@ -352,6 +352,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.15.0"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: "91d28b825784e15572fdc39165c5733099ce0e69c6f6f0964ebdbf98a62130fd"
url: "https://pub.dev"
source: hosted
version: "6.0.6"
path:
dependency: transitive
description:

View File

@@ -49,6 +49,7 @@ dependencies:
image: ^4.1.3
permission_handler: ^11.3.0
dropdown_button2: ^2.3.9
mobile_scanner: ^6.0.6
dev_dependencies:
flutter_test: