first commit

This commit is contained in:
kicap
2023-11-04 03:55:38 +08:00
commit 212fb855f9
169 changed files with 8572 additions and 0 deletions

View File

@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:validatorless/validatorless.dart';
import '../../../app/themes/app_colors.dart';
import '../../../app/themes/app_text.dart';
import '../../widgets/my_button.dart';
import '../../widgets/my_textformfield.dart';
import './login_screen_view_model.dart';
class LoginScreenView extends StatelessWidget {
const LoginScreenView({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<LoginScreenViewModel>.reactive(
viewModelBuilder: () => LoginScreenViewModel(),
onViewModelReady: (LoginScreenViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
LoginScreenViewModel model,
Widget? child,
) {
return Scaffold(
backgroundColor: warningColor,
body: WillPopScope(
onWillPop: () async {
model.log.i('backPressed: ${model.globalVar.backPressed}');
if (model.globalVar.backPressed == 'backNormal') {
// model.back();
model.quitApp(context);
}
return false;
},
child: SafeArea(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: SingleChildScrollView(
child: Form(
key: model.formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 50,
),
Center(
child: Image.asset(
'assets/logo.png',
width: 200,
height: 200,
),
),
const SizedBox(
height: 10,
),
const Text(
'Halaman Login',
),
const Text(
'(Survei App)',
style: italicTextStyle,
),
const SizedBox(
height: 20,
),
MyTextFormField(
controller: model.usernameController,
labelText: 'Username',
hintText: 'Masukkan Username',
suffixIcon: const Icon(Icons.person),
validator: Validatorless.required(
'Username tidak boleh kosong'),
),
const SizedBox(
height: 15,
),
MyTextFormField(
controller: model.passwordController,
hintText: 'Masukkan Password',
labelText: 'Password',
suffixIcon: GestureDetector(
child: model.isPasswordVisible
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
onTap: () {
model.isPasswordVisible =
!model.isPasswordVisible;
model.log.i(
'isPasswordVisible: ${model.isPasswordVisible}');
model.notifyListeners();
},
),
obscureText: !model.isPasswordVisible,
validator: Validatorless.required(
'Password tidak boleh kosong'),
),
const SizedBox(
height: 20,
),
SizedBox(
width: 250,
child: MyButton(
// theBackgroundColor: lightColor,
textColor: fontColor,
text: 'Login',
onPressed: () {
if (model.formKey.currentState!.validate()) {
model.login();
}
},
),
),
],
),
),
),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,61 @@
// import '../../../app/app.router.dart';
import 'package:cek_suara_app/app/app.router.dart';
import 'package:cek_suara_app/model/my_response.model.dart';
import 'package:cek_suara_app/model/tim_survei_model.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../../../app/app.logger.dart';
import '../../../app/core/custom_base_view_model.dart';
class LoginScreenViewModel extends CustomBaseViewModel {
final log = getLogger('LoginScreenViewModel');
// variable
final formKey = GlobalKey<FormState>();
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool isPasswordVisible = false;
Future<void> init() async {
globalVar.backPressed = 'backNormal';
}
login() async {
easyLoading.customLoading('Login..');
globalVar.backPressed = 'cantBack';
try {
var formData = FormData.fromMap({
'username': usernameController.text,
'password': passwordController.text,
});
var response =
await httpService.postWithFormData('login/tim_survei', formData);
MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
var data = myResponseModel.data;
TimSurveiModel timSurveiModel = TimSurveiModel.fromJson(data);
mySharedPrefs.setString('nik', timSurveiModel.nik!);
mySharedPrefs.setString('nama', timSurveiModel.nama!);
mySharedPrefs.setString('id_caleg', timSurveiModel.idCaleg.toString());
mySharedPrefs.setString('level', 'tim_survei');
snackbarService.showSnackbar(
message: 'Selamat datang kembali ${timSurveiModel.nama}',
title: 'Login Berhasil',
duration: const Duration(milliseconds: 2500),
);
navigationService.navigateToTimSurveiIndexTrackingView();
} catch (e) {
log.e('error: $e');
} finally {
easyLoading.dismissLoading();
globalVar.backPressed = 'backNormal';
}
// navigationService.pushNamedAndRemoveUntil('/home-screen');
// await navigationService.navigateToAdminIndexTrackingView();
}
}

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../../app/themes/app_colors.dart';
import '../../../app/themes/app_text.dart';
import './splash_screen_view_model.dart';
class SplashScreenView extends StatelessWidget {
const SplashScreenView({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<SplashScreenViewModel>.reactive(
viewModelBuilder: () => SplashScreenViewModel(),
onViewModelReady: (SplashScreenViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
SplashScreenViewModel model,
Widget? child,
) {
return Scaffold(
backgroundColor: warningColor,
body: Center(
child: Column(
children: [
const Expanded(
flex: 1,
child: SizedBox(
height: 100,
),
),
Image.asset(
'assets/logo.png',
width: 200,
height: 200,
),
const SizedBox(
height: 10,
),
Text(
'SISTEM CEK SUARA',
style: boldTextStyle.copyWith(
// color: backgroundColor,
fontSize: 20,
),
textAlign: TextAlign.center,
),
Text(
'(Survei App)',
style: boldTextStyle.copyWith(
// color: backgroundColor,
fontStyle: FontStyle.italic,
),
textAlign: TextAlign.center,
),
const Expanded(child: SizedBox()),
Text(
'Created By',
style: regularTextStyle.copyWith(
fontSize: 12,
),
textAlign: TextAlign.center,
),
Text(
'Kicap Karan',
style: boldTextStyle.copyWith(
fontSize: 13,
fontStyle: FontStyle.italic,
),
textAlign: TextAlign.center,
),
Text(
'www.kicap-karan.com',
style: boldTextStyle.copyWith(
fontSize: 13,
),
),
const SizedBox(
height: 20,
)
],
),
),
);
},
);
}
}

View File

@ -0,0 +1,13 @@
import '../../../app/app.logger.dart';
import '../../../app/app.router.dart';
import '../../../app/core/custom_base_view_model.dart';
class SplashScreenViewModel extends CustomBaseViewModel {
final log = getLogger('SplashScreenViewModel');
Future<void> init() async {
// await 3 seconds then go to login
await mySharedPrefs.clear();
await Future.delayed(const Duration(seconds: 3));
await navigationService.navigateTo(Routes.loginScreenView);
}
}

View File

@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../../../app/themes/app_colors.dart';
import '../../../../app/themes/app_text.dart';
import '../../../widgets/top_container.dart';
import './first_page_view_model.dart';
class FirstPageView extends StatelessWidget {
const FirstPageView({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<FirstPageViewModel>.reactive(
viewModelBuilder: () => FirstPageViewModel(),
onViewModelReady: (FirstPageViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
FirstPageViewModel model,
Widget? child,
) {
return Scaffold(
body: WillPopScope(
onWillPop: () async {
// model.log.i('backPressed: ${model.globalVar.backPressed}');
if (model.globalVar.backPressed == 'exitApp') {
// model.back();
model.quitApp(context);
}
return false;
},
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TopContainer(
title: 'Jumlah Pemilih',
value: '${model.counter} Orang',
icon: Icons.people_alt_outlined,
background: blueColor,
),
const SizedBox(
height: 20,
),
RichText(
text: TextSpan(
text: 'Selamat Datang, ',
style: regularTextStyle,
children: [
TextSpan(
text: '${model.nama}\n\n',
style: boldTextStyle,
),
const TextSpan(
text: 'Silahkan tambahkan data ',
style: regularTextStyle,
),
TextSpan(
text: 'Pemilih ',
style: boldTextStyle.copyWith(
color: greenColor,
fontStyle: FontStyle.italic,
),
),
const TextSpan(
text: 'dengan menekan menu ',
style: regularTextStyle,
),
TextSpan(
text: 'Survei ',
style: boldTextStyle.copyWith(
color: greenColor,
fontStyle: FontStyle.italic,
),
),
const TextSpan(
text: 'di bawah sebelah kanan.\n\n',
style: regularTextStyle,
),
const TextSpan(
text: 'Menu ',
style: regularTextStyle,
),
TextSpan(
text: 'History ',
style: boldTextStyle.copyWith(
color: greenColor,
fontStyle: FontStyle.italic,
),
),
const TextSpan(
text:
'pada bawah tengah digunakan untuk melihat data yang sudah diinputkan.\n\n',
style: regularTextStyle,
),
const TextSpan(
text: 'Menu ',
style: regularTextStyle,
),
TextSpan(
text: 'Pengaturan ',
style: boldTextStyle.copyWith(
color: greenColor,
fontStyle: FontStyle.italic,
),
),
const TextSpan(
text:
'pada bawah debelah kanan digunakan untuk mengubah data diri anda.\n\nSekian dari Developer',
style: regularTextStyle,
),
TextSpan(
text: '\nKicap Karan ',
style: boldTextStyle.copyWith(
color: dangerColor,
fontStyle: FontStyle.italic,
),
),
],
),
),
],
),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,30 @@
import '../../../../app/app.logger.dart';
import '../../../../app/core/custom_base_view_model.dart';
import '../../../../model/my_response.model.dart';
class FirstPageViewModel extends CustomBaseViewModel {
final log = getLogger('FirstPageViewModel');
int counter = 0;
String? nik;
String? nama;
Future<void> init() async {
globalVar.backPressed = 'exitApp';
nik = await mySharedPrefs.getString('nik');
nama = await mySharedPrefs.getString('nama');
await getData();
}
getData() async {
setBusy(true);
try {
var response = await httpService.get('tim_survei/counter/$nik');
MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
counter = myResponseModel.data;
} catch (e) {
log.e(e.toString());
} finally {
setBusy(false);
}
}
}

View File

@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../../../app/themes/app_colors.dart';
import '../../../../app/themes/app_text.dart';
import './halaman_history_view_model.dart';
class HalamanHistoryView extends StatelessWidget {
const HalamanHistoryView({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<HalamanHistoryViewModel>.reactive(
viewModelBuilder: () => HalamanHistoryViewModel(),
onViewModelReady: (HalamanHistoryViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
HalamanHistoryViewModel model,
Widget? child,
) {
return Scaffold(
body: WillPopScope(
onWillPop: () async {
// model.log.i('backPressed: ${model.globalVar.backPressed}');
if (model.globalVar.backPressed == 'exitApp') {
// model.back();
model.quitApp(context);
}
return false;
},
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(15),
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
color: warningColor,
),
child: Row(
children: [
Text(
"Caleg : ",
style: italicTextStyle.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 10),
Expanded(
child: Text(
model.namaCaleg,
style: boldTextStyle.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const SizedBox(height: 20),
Expanded(
child: Container(
alignment: model.isBusy
? Alignment.center
: model.status == true
? model.counter > 0
? Alignment.topCenter
: Alignment.center
: Alignment.center,
height: double.infinity,
width: double.infinity,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
color: warningColor,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisSize: MainAxisSize.min,
children: [
if (model.isBusy)
const Center(
child: CircularProgressIndicator()),
if (!model.isBusy &&
model.status == true &&
model.counter > 0)
for (var i = 0; i < model.counter; i++)
Card(
child: ListTile(
// leading is datetime dummy
leading:
Text(model.listPemilih[i].createdAt!),
title: Text(
model.listPemilih[i].namaPemilih!,
style: boldTextStyle,
),
subtitle: Text(
model.listPemilih[i].namaArea!,
),
trailing: IconButton(
icon: const Icon(
Icons.info_outline,
color: mainColor,
),
onPressed: () {
// model.showDetailCaleg(model.listCalegModel[i]);
},
),
),
),
if (!model.isBusy &&
model.status == true &&
model.counter == 0)
const Center(
child: Text(
'Belum ada data',
style: boldTextStyle,
),
),
if (!model.isBusy && model.status == false)
const Center(
child: Text(
'Error: Gagal mengambil data dari server',
style: boldTextStyle,
),
),
],
),
),
),
),
],
),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,40 @@
import 'package:cek_suara_app/model/my_response.model.dart';
import 'package:cek_suara_app/model/pemilih_model.dart';
import '../../../../app/app.logger.dart';
import '../../../../app/core/custom_base_view_model.dart';
class HalamanHistoryViewModel extends CustomBaseViewModel {
final log = getLogger('HalamanHistoryViewModel');
int counter = 0;
List<PemilihModel> listPemilih = [];
bool status = false;
String namaCaleg = '...';
Future<void> init() async {
await getData();
}
getData() async {
setBusy(true);
try {
String? nik = await mySharedPrefs.getString('nik');
var response = await httpService.get('tim_survei/$nik');
MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
PemilihDetailModel pemilihDetailModel =
PemilihDetailModel.fromJson(myResponseModel.data);
listPemilih = pemilihDetailModel.pemilihModel!;
log.i('listPemilih: $listPemilih');
namaCaleg = listPemilih[0].namaCaleg!;
counter = pemilihDetailModel.jumlah!;
log.i('counter: $counter');
status = true;
} catch (e) {
log.e(e.toString());
status = false;
} finally {
setBusy(false);
}
}
}

View File

@ -0,0 +1,36 @@
import 'package:cek_suara_app/ui/views/tim_survei_index_tracking/first_page/first_page_view.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import './halaman_pengaturan_view_model.dart';
class HalamanPengaturanView extends StatelessWidget {
const HalamanPengaturanView({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<HalamanPengaturanViewModel>.reactive(
viewModelBuilder: () => HalamanPengaturanViewModel(),
onViewModelReady: (HalamanPengaturanViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
HalamanPengaturanViewModel model,
Widget? child,
) {
return WillPopScope(
onWillPop: () async {
// model.log.i('backPressed: ${model.globalVar.backPressed}');
if (model.globalVar.backPressed == 'exitApp') {
// model.back();
model.quitApp(context);
}
return false;
},
child: const FirstPageView(),
);
},
);
}
}

View File

@ -0,0 +1,7 @@
import '../../../../app/core/custom_base_view_model.dart';
class HalamanPengaturanViewModel extends CustomBaseViewModel {
Future<void> init() async {
globalVar.backPressed = 'exitApp';
}
}

View File

@ -0,0 +1,62 @@
import 'package:cek_suara_app/ui/widgets/my_textformfield.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import './bottom_sheet_cari_area_view_model.dart';
class BottomSheetCariAreaView extends StatelessWidget {
final SheetRequest request;
final Function(SheetResponse)? completer;
const BottomSheetCariAreaView({
Key? key,
required this.request,
this.completer,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ViewModelBuilder<BottomSheetCariAreaViewModel>.reactive(
viewModelBuilder: () => BottomSheetCariAreaViewModel(),
onViewModelReady: (BottomSheetCariAreaViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
BottomSheetCariAreaViewModel model,
Widget? child,
) {
return SafeArea(
child: Container(
alignment: Alignment.topCenter,
width: double.infinity,
height: MediaQuery.of(context).size.height,
margin: const EdgeInsets.all(25),
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: MyTextFormField(
hintText: 'Cari Area',
labelText: 'Cari Area',
controller: model.searchController,
suffixIcon: IconButton(
onPressed: () {
completer!(
SheetResponse(
confirmed: true,
data: model.searchController.text,
),
);
},
icon: const Icon(Icons.search),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import '../../../../../app/app.logger.dart';
import '../../../../../app/core/custom_base_view_model.dart';
class BottomSheetCariAreaViewModel extends CustomBaseViewModel {
final log = getLogger('BottomSheetCariAreaViewModel');
TextEditingController searchController = TextEditingController();
Future<void> init() async {}
}

View File

@ -0,0 +1,272 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:validatorless/validatorless.dart';
import '../../../../app/themes/app_colors.dart';
import '../../../widgets/my_button.dart';
import '../../../widgets/my_textformfield.dart';
import './halaman_survei_view_model.dart';
class HalamanSurveiView extends StatelessWidget {
const HalamanSurveiView({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<HalamanSurveiViewModel>.reactive(
viewModelBuilder: () => HalamanSurveiViewModel(),
onViewModelReady: (HalamanSurveiViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
HalamanSurveiViewModel model,
Widget? child,
) {
return Scaffold(
body: WillPopScope(
onWillPop: () async {
// model.log.i('backPressed: ${model.globalVar.backPressed}');
if (model.globalVar.backPressed == 'exitApp') {
// model.back();
model.quitApp(context);
}
return false;
},
child: SafeArea(
child: Container(
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: Form(
key: model.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Center(child: Text('Foto KTP / Wajah Pemilih')),
// create a rectangle container that later use to show image with child is KTP icon
Center(
child: Stack(
children: [
Container(
height: 100,
width: 150,
decoration: BoxDecoration(
color: mainColor,
borderRadius: BorderRadius.circular(10),
),
child: model.imageBytes != null
? Image.memory(
model.imageBytes!,
fit: BoxFit.fill,
)
: const Icon(
Icons.credit_card_rounded,
color: Colors.white,
size: 50,
),
),
Positioned(
bottom: 0,
right: 0,
child: CircleAvatar(
radius: 15,
backgroundColor: sixthGrey,
child: IconButton(
onPressed: () {
model.addImage();
},
icon: const Icon(
Icons.add,
color: backgroundColor3,
size: 15,
)),
),
),
],
),
),
const SizedBox(height: 20),
MyTextFormField(
hintText: 'Masukkan KTP / No HP Pemilih',
labelText: 'KTP / No HP Pemilih',
maxLength: 16,
suffixIcon: const Icon(Icons.add_card_rounded),
controller: model.ktpController,
keyboardType: TextInputType.number,
validator: Validatorless.multiple(
[
Validatorless.required(
'KTP / No HP Pemilih tidak boleh kosong'),
Validatorless.number(
'KTP / No HP Pemilih harus angka'),
Validatorless.min(
10, 'KTP / No HP Pemilih minimal 10 digit'),
Validatorless.max(
16, 'KTP / No HP Pemilih maksimal 16 digit'),
],
),
),
const SizedBox(height: 20),
MyTextFormField(
hintText: 'Masukkan Nama Pemilih',
labelText: 'Nama Pemilih',
suffixIcon: const Icon(Icons.person),
controller: model.namaController,
validator: Validatorless.required(
'Nama Pemilih tidak boleh kosong'),
),
const SizedBox(height: 20),
const Text(
' Pilih Area',
),
if (model.isBusy)
const Center(child: CircularProgressIndicator()),
if (!model.isBusy && model.selectedArea != null)
Container(
width: double.infinity,
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
border: Border.all(
color: sixthGrey,
),
),
child: Row(
children: [
// icon search
IconButton(
onPressed: () async =>
await model.searchArea(),
icon: const Icon(
Icons.search,
color: sixthGrey,
),
),
const SizedBox(width: 10),
Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: model.selectedArea!,
icon: const Icon(Icons.arrow_drop_down),
iconSize: 24,
elevation: 16,
style: const TextStyle(
color: Colors.black),
onChanged: (String? newValue) {
model.log.i(newValue);
model.selectedArea = newValue!;
// model.changeArea(newValue);
model.notifyListeners();
},
items: model.listAreaString
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,
overflow:
TextOverflow.ellipsis),
);
}).toList()),
),
),
],
),
),
// const SizedBox(height: 20),
// const Text(
// ' Pilih Caleg',
// ),
// if (model.isBusy)
// const Center(child: CircularProgressIndicator()),
// if (!model.isBusy)
// Container(
// width: double.infinity,
// height: 60,
// padding: const EdgeInsets.symmetric(horizontal: 10),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(25),
// border: Border.all(
// color: sixthGrey,
// ),
// ),
// child: Expanded(
// child: DropdownButtonHideUnderline(
// child: DropdownButton<String>(
// isExpanded: true,
// value: model.selectedCaleg!,
// icon: const Icon(Icons.arrow_drop_down),
// iconSize: 24,
// elevation: 16,
// style: const TextStyle(color: Colors.black),
// onChanged: (String? newValue) {
// model.log.i(newValue);
// model.selectedCaleg = newValue!;
// model.notifyListeners();
// },
// items: model.listCalegString
// .map<DropdownMenuItem<String>>(
// (String value) {
// return DropdownMenuItem<String>(
// value: value,
// child: Text(value,
// overflow: TextOverflow.ellipsis),
// );
// }).toList()),
// ),
// ),
// ),
const SizedBox(height: 20),
Center(
child: SizedBox(
width: 200,
child: MyButton(
text: 'Upload Data',
onPressed: () {
if (model.imageBytes == null) {
model.snackbarService.showSnackbar(
message:
'Foto KTP / Wajah Pemilih tidak boleh kosong',
);
model.addImage();
return;
}
if (model.formKey.currentState!.validate()) {
// hide keyboard
FocusScope.of(context).unfocus();
model.dialogService
.showDialog(
title: 'Upload Data',
description:
'Apakah anda yakin ingin mengupload data?',
buttonTitle: 'Ya',
cancelTitle: 'Tidak',
)
.then((value) async {
if (value!.confirmed) {
await model.uploadData();
}
});
// model.uploadData();
}
},
),
),
),
],
),
),
),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,212 @@
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.dart';
import '../../../../app/app.bottomsheets.dart';
import '../../../../app/app.logger.dart';
import '../../../../app/core/custom_base_view_model.dart';
import '../../../../model/area_model.dart';
import '../../../../model/my_response.model.dart';
class HalamanSurveiViewModel extends CustomBaseViewModel {
final log = getLogger('HalamanSurveiViewModel');
// form
final formKey = GlobalKey<FormState>();
TextEditingController ktpController = TextEditingController();
TextEditingController namaController = TextEditingController();
// image picker
String? _imagePath;
final ImagePicker _picker = ImagePicker();
XFile? imageFile;
Uint8List? imageBytes;
// area
List<AreaModel> listAreaModel = [];
List<String> listAreaString = [];
List<AreaModel> allListAreaModel = [];
String? selectedArea;
int areaIndex = 0;
// // caleg
// List<CalegModel> listCalegModel = [];
// List<String> listCalegString = [];
// String? selectedCaleg;
// ini baru
String? idCaleg;
String? nik;
Future<void> init() async {
globalVar.backPressed = 'exitApp';
String idCaleg = await mySharedPrefs.getString('id_caleg') ?? '';
this.idCaleg = idCaleg;
nik = await mySharedPrefs.getString('nik') ?? '';
await getData();
}
getData() async {
log.i('getData');
setBusy(true);
globalVar.backPressed = 'cantBack';
try {
// this one is before
// var response = await httpService.get('area');
// String? nik = await mySharedPrefs.getString('nik');
var response = await httpService.get('area/cek_area/$nik');
log.i(response.data);
MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
AreaListModel areaListModel =
AreaListModel.fromJson(myResponseModel.data);
listAreaModel = areaListModel.area!;
allListAreaModel = areaListModel.area!;
for (var element in listAreaModel) {
listAreaString.add(element.namaArea!);
}
selectedArea = listAreaString[0];
// int idArea = listAreaModel[0].idArea!;
// await getCaleg(idArea);
// getCaleg()
} catch (e) {
log.e(e);
} finally {
globalVar.backPressed = 'exitApp';
setBusy(false);
}
}
// getCaleg(int idArea) async {
// log.i('getCaleg');
// log.i('idArea: $idArea');
// selectedCaleg = null;
// listCalegModel = [];
// listCalegString = [];
// setBusy(true);
// try {
// var response = await httpService.get('caleg/area/$idArea');
// log.i(response.data);
// MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
// // log.i(myResponseModel.data);
// CalegListModel calegListModel =
// CalegListModel.fromJson(myResponseModel.data);
// listCalegModel = calegListModel.caleg!;
// for (var element in listCalegModel) {
// listCalegString.add(element.namaCaleg!);
// }
// selectedCaleg = listCalegString[0];
// // log.i('listCalegModel: $listCalegModel');
// // log.i('listCalegString: $listCalegString');
// // log.i('selectedCaleg: $selectedCaleg');
// } catch (e) {
// log.e(e);
// } finally {
// setBusy(false);
// }
// }
// changeArea(String? value) async {
// int idArea = listAreaModel[listAreaString.indexOf(value!)].idArea!;
// // log.i('idArea: $idArea');
// await getCaleg(idArea);
// }
void addImage() async {
try {
final XFile? image = await _picker.pickImage(source: ImageSource.camera);
if (image != null) {
imageFile = image;
_imagePath = image.path;
imageBytes = await image.readAsBytes();
log.i('image path: $_imagePath');
notifyListeners();
}
} catch (e) {
log.e(e);
}
}
searchArea() async {
var res = await bottomSheetService.showCustomSheet(
variant: BottomSheetType.bottomSheetCariAreaView,
ignoreSafeArea: false,
isScrollControlled: true,
);
if (res!.confirmed) {
log.i('res.data: ${res.data}');
String area = res.data;
if (area == '') {
listAreaModel = allListAreaModel;
} else {
listAreaModel = [];
for (var element in allListAreaModel) {
if (element.namaArea!.toLowerCase().contains(area.toLowerCase())) {
listAreaModel.add(element);
}
}
}
listAreaString = [];
for (var element in listAreaModel) {
listAreaString.add(element.namaArea!);
}
selectedArea = listAreaString[0];
// int idArea = listAreaModel[0].idArea!;
// await getCaleg(idArea);
notifyListeners();
}
}
uploadData() async {
log.i('uploadData');
setBusy(true);
easyLoading.customLoading('Uploading data...');
globalVar.backPressed = 'cantBack';
try {
String idArea = listAreaModel[listAreaString.indexOf(selectedArea!)]
.idArea
.toString();
var fomData = FormData.fromMap(
{
'ktp': ktpController.text,
'nama': namaController.text,
'idCaleg': idCaleg,
'foto': await MultipartFile.fromFile(
_imagePath!,
filename: imageFile!.name,
contentType: MediaType('image', 'jpg'),
),
'idArea': idArea,
'nik': nik,
},
);
await httpService.postWithFormData('tim_survei', fomData);
snackbarService.showSnackbar(
message: 'Data berhasil diupload',
title: 'Berhasil',
duration: const Duration(milliseconds: 2500),
);
// clear data
ktpController.clear();
namaController.clear();
_imagePath = null;
imageFile = null;
imageBytes = null;
notifyListeners();
} catch (e) {
log.e(e);
} finally {
easyLoading.dismissLoading();
globalVar.backPressed = 'exitApp';
setBusy(false);
}
}
}

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:stylish_bottom_bar/model/bar_items.dart';
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
import '../../../app/app.router.dart';
import '../../../app/themes/app_colors.dart';
import '../../../app/themes/app_text.dart';
import './tim_survei_index_tracking_view_model.dart';
class TimSurveiIndexTrackingView extends StatelessWidget {
const TimSurveiIndexTrackingView({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<TimSurveiIndexTrackingViewModel>.reactive(
viewModelBuilder: () => TimSurveiIndexTrackingViewModel(),
onViewModelReady: (TimSurveiIndexTrackingViewModel model) async {
await model.init();
},
builder: (
BuildContext context,
TimSurveiIndexTrackingViewModel model,
Widget? child,
) {
return Scaffold(
extendBody: false,
body: ExtendedNavigator(
router: TimSurveiIndexTrackingViewRouter(),
navigatorKey: StackedService.nestedNavigationKey(5),
),
bottomNavigationBar: StylishBottomBar(
items: [
for (var item in model.bottomNavBarList)
BottomBarItem(
icon: Icon(item['icon'],
color: model.currentIndex ==
model.bottomNavBarList.indexOf(item)
? warningColor
: fontColor),
title: Text(
item['name'],
style: regularTextStyle.copyWith(
color: model.currentIndex ==
model.bottomNavBarList.indexOf(item)
? warningColor
: fontColor,
),
// textAlign: TextAlign.l,
),
backgroundColor:
model.currentIndex == model.bottomNavBarList.indexOf(item)
? fontColor
: fontColor,
),
],
currentIndex: model.currentIndex,
option: BubbleBarOptions(),
hasNotch: true,
backgroundColor: warningColor,
onTap: (value) {
model.handleNavigation(value);
},
// fabLocation: StylishBarFabLocation.center,
),
);
},
);
}
}

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.logger.dart';
import '../../../app/app.router.dart';
import '../../../services/global_var.dart';
import '../../../services/shared_prefs.dart';
class TimSurveiIndexTrackingViewModel extends IndexTrackingViewModel {
final log = getLogger('TimSurveiIndexTrackingViewModel');
final mySharedPrefs = locator<MySharedPrefs>();
final navigationService = locator<NavigationService>();
final snackbarService = locator<SnackbarService>();
final globalVar = locator<GlobalVar>();
final _bottomNavBarList = [
{
'name': 'Survei',
'icon': Icons.supervised_user_circle_outlined,
'header': 'Halaman Survei',
},
{
'name': 'History',
'icon': Icons.history_edu_outlined,
'header': 'History Survei',
},
{
'name': 'Pengaturan',
'icon': Icons.settings_outlined,
'header': 'Pengaturan',
},
];
String header = 'Pengaturan';
List<Map<String, dynamic>> get bottomNavBarList => _bottomNavBarList;
final List<String> _views = [
TimSurveiIndexTrackingViewRoutes.halamanSurveiView,
TimSurveiIndexTrackingViewRoutes.halamanHistoryView,
TimSurveiIndexTrackingViewRoutes.halamanPengaturanView,
];
Future<void> init() async {
if (await mySharedPrefs.getString('level') != 'tim_survei') {
snackbarService.showSnackbar(
message: 'Anda tidak memiliki akses',
title: 'Akses Ditolak',
duration: const Duration(milliseconds: 2500),
);
navigationService.clearStackAndShow(Routes.loginScreenView);
}
setIndex(2);
}
void handleNavigation(int index) {
// log.d("handleNavigation: $index");
// log.d("currentIndex: $currentIndex");
// if (currentIndex == index) return;
setIndex(index);
header = _bottomNavBarList[index]['header'] as String;
navigationService.navigateTo(
_views[index],
id: 5,
);
}
}

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import '../../app/themes/app_colors.dart';
class MyButton extends StatelessWidget {
const MyButton({
Key? key,
required this.text,
this.theBackgroundColor = mainColor,
this.textColor = backgroundColor,
this.onPressed,
}) : super(key: key);
final String text;
final VoidCallback? onPressed;
final Color theBackgroundColor;
final Color textColor;
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: theBackgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: onPressed,
child: Text(
text,
style: TextStyle(
color: textColor,
fontSize: 18,
),
),
);
}
}

View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import '../../app/themes/app_colors.dart';
class MyTextFormField extends StatelessWidget {
const MyTextFormField({
Key? key,
this.labelText,
this.hintText,
this.obscureText,
this.validator,
this.suffixIcon,
this.prefixIcon,
this.focusNode,
this.controller,
this.maxLines = 1,
this.onEditingComplete,
this.keyboardType = TextInputType.text,
this.initialValue,
this.readOnly = false,
this.maxLength,
}) : super(key: key);
final String? labelText;
final String? hintText;
final bool? obscureText;
final FormFieldValidator<String>? validator;
final Widget? suffixIcon;
final Widget? prefixIcon;
final FocusNode? focusNode;
final TextEditingController? controller;
final int maxLines;
final VoidCallback? onEditingComplete;
final TextInputType keyboardType;
final String? initialValue;
final bool readOnly;
final int? maxLength;
@override
Widget build(BuildContext context) {
return TextFormField(
maxLength: maxLength,
readOnly: readOnly,
initialValue: initialValue,
onEditingComplete: onEditingComplete,
maxLines: maxLines,
controller: controller,
focusNode: focusNode,
obscureText: obscureText ?? false,
keyboardType: keyboardType,
decoration: InputDecoration(
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide(
// color: mainColor,
),
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide(
// color: mainColor,
),
),
focusedErrorBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide(
color: dangerColor,
),
),
errorBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide(
color: dangerColor,
),
),
labelText: labelText,
hintText: hintText,
labelStyle: const TextStyle(color: fontColor),
),
validator: validator,
);
}
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import '../../app/themes/app_colors.dart';
class TopContainer extends StatelessWidget {
const TopContainer({
super.key,
required this.title,
required this.value,
required this.icon,
required this.background,
});
final String title;
final String value;
final IconData icon;
final Color background;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
width: double.infinity,
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(10),
),
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 3,
child: Text(
title,
style: const TextStyle(
color: fontGrey,
fontSize: 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.justify,
),
),
const Expanded(
flex: 1,
child: Text(
':',
style: TextStyle(
color: fontGrey,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
flex: 5,
child: Text(
value,
style: const TextStyle(
color: fontGrey,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Icon(
icon,
color: fontGrey,
),
],
),
);
}
}