diff --git a/app_petty_cash/lib/app/app_extension.dart b/app_petty_cash/lib/app/app_extension.dart new file mode 100644 index 0000000..015415f --- /dev/null +++ b/app_petty_cash/lib/app/app_extension.dart @@ -0,0 +1,103 @@ +import 'package:intl/intl.dart'; + +extension AppStringExtension on String { + String currencyFormat() { + final numberFormat = NumberFormat.currency( + locale: 'id_ID', + decimalDigits: 0, + symbol: 'Rp ', + ); + return numberFormat.format(double.parse(this)); + } + + String dateFormat() { + final dateFormatFromApi = DateFormat('yyyy-M-dd hh:mm:ss'); + // final dateFormat = DateFormat('dd MMM yyyy'); + final dateFormat = DateFormat('dd/M/yy'); + return dateFormat.format(dateFormatFromApi.parse(this)); + } + + String dateFormatdmy() { + final dateFormatFromApi = DateFormat('yyyy-M-dd hh:mm:ss'); + // final dateFormat = DateFormat('dd MMM yyyy'); + final dateFormat = DateFormat('dd-MM-yyyy hh:mm:ss'); + return dateFormat.format(dateFormatFromApi.parse(this)); + } + + String dateFormatdmyHistorical() { + final dateFormatFromApi = DateFormat('yyyy-MM-dd'); + // final dateFormat = DateFormat('dd MMM yyyy'); + final dateFormat = DateFormat('dd-MM-yyyy'); + return dateFormat.format(dateFormatFromApi.parse(this)); + } +} + +String formatBulanIndo(String tanggal) { + DateTime dateTime = DateFormat("yyyy-MM-dd").parse(tanggal); + + var m = DateFormat('MM').format(dateTime); + var month = ""; + switch (m) { + case '01': + { + month = "Januari"; + } + break; + case '02': + { + month = "Februari"; + } + break; + case '03': + { + month = "Maret"; + } + break; + case '04': + { + month = "April"; + } + break; + case '05': + { + month = "Mei"; + } + break; + case '06': + { + month = "Juni"; + } + break; + case '07': + { + month = "Juli"; + } + break; + case '08': + { + month = "Agustus"; + } + break; + case '09': + { + month = "September"; + } + break; + case '10': + { + month = "Oktober"; + } + break; + case '11': + { + month = "November"; + } + break; + case '12': + { + month = "Desember"; + } + break; + } + return month; +} diff --git a/app_petty_cash/lib/app/constant.dart b/app_petty_cash/lib/app/constant.dart new file mode 100644 index 0000000..1d8499b --- /dev/null +++ b/app_petty_cash/lib/app/constant.dart @@ -0,0 +1,556 @@ +// ignore_for_file: prefer_const_constructors, non_constant_identifier_names + +import 'package:flutter/material.dart'; + +class Constant { + static double designHeight = 1024; + static double designWidth = 1440; + + static double designHeightPhone = 844; + static double designWidthPhone = 390; + + static String bearerName = "petty-cash"; + static String version = "2.00"; + + // static String baseUrl = "https://devregonline.pramita.co.id/one-api/xdoc/"; + + // tester devbandungraya + static String baseUrl = + "http://devbandungraya.aplikasi.web.id/one-api-doctor/doctor_mitra/"; + + static String baseUrl_appdoctor = + "http://devbandungraya.aplikasi.web.id/one-api/app_doctor/"; + + // * surabaya + // static String baseUrl = + // "http://surabaya.bisone.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://surabaya.bisone.pramitalab.id/one-api/app_doctor/"; + + // * jakarta + // static String baseUrl = + // "http://jakarta.bisone2.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://jakarta.bisone2.pramitalab.id/one-api/app_doctor/"; + + // * bandung + // static String baseUrl = + // "http://bandung.bisone3.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://bandung.bisone3.pramitalab.id/one-api/app_doctor/"; + + // * salatiga + // static String baseUrl = + // "http://salatigaraya.bisone7.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://salatigaraya.bisone7.pramitalab.id/one-api/app_doctor/"; + + // * semarang + // static String baseUrl = + // "http://semarang.bisone7.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://semarang.bisone7.pramitalab.id/one-api/app_doctor/"; + + // * magelang + // static String baseUrl = + // "http://magelang.bisone7.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://magelang.bisone7.pramitalab.id/one-api/app_doctor/"; + + // // * tegal + // static String baseUrl = + // "http://tegal.bisone7.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://tegal.bisone7.pramitalab.id/one-api/app_doctor/"; + + // // * cirebon + // static String baseUrl = + // "http://cirebon.bisone4.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://cirebon.bisone4.pramitalab.id/one-api/app_doctor/"; + + // // * tasik + // static String baseUrl = + // "http://tasikmalaya.bisone2.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://tasikmalaya.bisone2.pramitalab.id/one-api/app_doctor/"; + + // // * jogja + // static String baseUrl = + // "http://jogja.bisone.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://jogja.bisone.pramitalab.id/one-api/app_doctor/"; + + // // * bali + // static String baseUrl = + // "http://bali.bisone.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://bali.bisone.pramitalab.id/one-api/app_doctor/"; + + // // * medan + // static String baseUrl = + // "http://medan.bisone4.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://medan.bisone4.pramitalab.id/one-api/app_doctor/"; + + // // * palembang + // static String baseUrl = + // "http://palembang.bisone5.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://palembang.bisone5.pramitalab.id/one-api/app_doctor/"; + + // // * pontianak + // static String baseUrl = + // "http://pontianakraya.bisone2.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://pontianakraya.bisone2.pramitalab.id/one-api/app_doctor/"; + + // // * balikpapan + // static String baseUrl = + // "http://balikpapan.bisone2.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://balikpapan.bisone2.pramitalab.id/one-api/app_doctor/"; + + // // * padang + // static String baseUrl = + // "http://padang.bisone4.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://padang.bisone4.pramitalab.id/one-api/app_doctor/"; + + // // * pekanbaru + // static String baseUrl = + // "http://pekanbaru.bisone4.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://pekanbaru.bisone4.pramitalab.id/one-api/app_doctor/"; + + // // * makassar + // static String baseUrl = + // "http://makassar.bisone2.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://makassar.bisone2.pramitalab.id/one-api/app_doctor/"; + + // // * manado + // static String baseUrl = + // "http://manado.bisone2.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://manado.bisone2.pramitalab.id/one-api/app_doctor/"; + + // * bekasi + // static String baseUrl = + // "http://bekasiraya.bisone2.pramitalab.id/one-api-doctor/doctor_mitra/"; + + // static String baseUrl_appdoctor = + // "http://bekasiraya.bisone2.pramitalab.id/one-api/app_doctor/"; + + //color Theme + // login marketing + static Color subTitleBlack = const Color(0xff000000); + static Color backgroundColor = const Color(0xffFFFFFF); + static Color buttonLoginBackgroundColor = const Color(0xffE61301); + + static Color buttonRedBackgroundColor = const Color(0xffd90504); + static Color textLoginColor = const Color(0xffFFFFFF); + static Color shadowLoginMarketing = Colors.grey.withOpacity(0.3); + static Color boxDecorationShadowLoginMaketing = Colors.blueGrey; + static Color textRedProblemMaketing = const Color(0xffE61301); + + // home + static Color backgroundColorWelcome = const Color(0xffE61301); + static Color textMonthToDate = const Color(0xff80848E); + static Color textListStatusOrder = const Color(0xff070708); + static Color confirmed = const Color(0xff0BAF48); + static Color backgroundTotalOrderRight = const Color(0xffF6F5F5); + static Color dividerHorizontal = const Color(0xffF4F6F8); + static Color backgroundTopHome = const Color(0xffFEF0EB); + + // order detail + static Color textOrderDetail = const Color(0xff7E7F81); + static Color cardConfirmed = const Color(0xffEBF9F0); + static Color cardUnconfirmed = const Color(0xffFEF0EB); + static Color textDropdown = const Color(0xff282828); + + // pendaftaran Pasien + static Color textCardPendaftaranPasien = const Color(0xff2E2D2D); + // static Color cardColorPendaftaranPasien = const Color(0xffFDFDFD); + static Color cardColorPendaftaranPasien = const Color(0xffF4F6F8); + static Color floatingbg = const Color(0xffDC2626); + + // pendaftaran pasien - demografi + static Color backgroundCardNetral1_100 = const Color(0xffD1D2D6); + static Color backgroundCardGreen_600 = const Color(0xff0BAF48); + + // divider + static Color dividerColor = Color(0xffD1D2D6); + + // grey + static Color greyColor = Color(0xff80848E); + static Color chipText = Color(0xff637381); + + // chip bg color + static Color chipBg = Color(0xffEDEFF2); + + // checkbox color + static Color checkboxActive = Colors.red; + static Color checkboxCheck = Colors.white; + static Color checkboxSelected = Colors.red.shade100; + + // icon file + static Color fileColor = Color(0xFF087D33); + + // fixing color + static Color red_100 = Color(0xffFFB9B3); + static Color red_200 = Color(0xffFE8A80); + static Color red_300 = Color(0xffFE5B4D); + static Color red_400 = Color(0xffFE2C1A); + static Color red_500 = Color(0xffE61301); + static Color red_600 = Color(0xffB20F01); + static Color red_700 = Color(0xff7F0A01); + static Color red_800 = Color(0xff4C0600); + static Color red_900 = Color(0xff190200); + + static Color green_100 = Color(0xffBCFBD3); + static Color green_200 = Color(0xff8CF8B4); + static Color green_300 = Color(0xff5CF595); + static Color green_400 = Color(0xff2CF275); + static Color green_500 = Color(0xff0EDD5B); + static Color green_600 = Color(0xff0BAF48); + static Color green_700 = Color(0xff087D33); + static Color green_800 = Color(0xff054D20); + static Color green_900 = Color(0xff021D0C); + + static Color netral_100 = Color(0xffD1D2D6); + static Color netral_200 = Color(0xffB6B8BE); + static Color netral_300 = Color(0xff9B9EA6); + static Color netral_400 = Color(0xff80848E); + static Color netral_500 = Color(0xff676A74); + static Color netral_600 = Color(0xff4F5259); + static Color netral_700 = Color(0xff37393E); + static Color netral_800 = Color(0xff1F2023); + static Color netral_900 = Color(0xff070708); + + static Color white = Color(0xffFDFDFD); + + static Color textBlack = Color(0xff070708); + static Color textRed = Color(0xffE61301); + static Color textGrey = Color(0xff80848E); + static Color textWhite = Color(0xffFDFDFD); + static Color textGreen = Color(0xff0BAF48); + + //typoGraphy + static TextStyle titleH1Login({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w700, + ); + } + + static TextStyle titleH3_700({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w700, + ); + } + + static TextStyle titleH3_700V2({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 32), + fontWeight: FontWeight.w700, + ); + } + + static TextStyle titleH3_700GantiPassword({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 14), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle titleH3_700SignOut({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 30), + fontWeight: FontWeight.w600, + ); + } + + static TextStyle titleH4Login({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 16), + fontWeight: FontWeight.w400, + ); + } + + // homescreen + static TextStyle titleLast30Days({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 16), + fontWeight: FontWeight.w700, + ); + } + + static TextStyle titleH4_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 20), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle H4_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 20), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle H4_600({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 20), + fontWeight: FontWeight.w600, + ); + } + + static TextStyle H4_700V2({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 28), + fontWeight: FontWeight.w700, + ); + } + + static TextStyle titleH5({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 18), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle caption1_400({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 11), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle caption3_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 9), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle titleH2({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 32), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle titleH1_600({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 36), + fontWeight: FontWeight.w600, + ); + } + + static TextStyle body3({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 12), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle body1({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 16), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle body1_400({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 16), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle body1_400_dibulan({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 14), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle body1_600({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 16), + fontWeight: FontWeight.w600, + ); + } + + static TextStyle body1_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 16), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle body2_400({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 14), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle titleH3({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle titleH3_600({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w600, + ); + } + + static TextStyle H3_600({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w600, + ); + } + + static TextStyle H3_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle H5_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 18), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle body2({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 14), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle body2_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 14), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle body2_500_italic({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 14), + fontWeight: FontWeight.w400, + fontStyle: FontStyle.italic); + } + + static TextStyle body1_700({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 16), + fontWeight: FontWeight.w700, + ); + } + + static TextStyle H3_700({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w700, + ); + } + + static TextStyle body3_400({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 12), + fontWeight: FontWeight.w400, + ); + } + + static TextStyle body3_500({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 12), + fontWeight: FontWeight.w500, + ); + } + + static TextStyle titleH7_700({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 12), + fontWeight: FontWeight.w700, + ); + } + + // pendaftaran pasien + static TextStyle titleH5_600({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 18), + fontWeight: FontWeight.w600, + ); + } + + // demografi + static TextStyle body2_600({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 14), + fontWeight: FontWeight.w600, + ); + } + + //size convertion + static double getActualX({ + required BuildContext context, + required double x, + }) { + return x / designWidth * MediaQuery.of(context).size.width; + } + + static double getActualY({ + required BuildContext context, + required double y, + }) { + return y / designHeight * MediaQuery.of(context).size.height; + } + + static double getActualXPhone({ + required BuildContext context, + required double x, + }) { + return x / designWidthPhone * MediaQuery.of(context).size.width; + } + + static double getActualYPhone({ + required BuildContext context, + required double y, + }) { + return y / designHeightPhone * MediaQuery.of(context).size.height; + } +} diff --git a/app_petty_cash/lib/app/route.dart b/app_petty_cash/lib/app/route.dart new file mode 100644 index 0000000..56a2d9a --- /dev/null +++ b/app_petty_cash/lib/app/route.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +import '../screen/login/login_screen.dart'; + +const loginRoute = "/loginRoute"; +const menuRoute = "/menuRoute"; + +class AppRoute { + static Route generateRoute(RouteSettings settings) { + // default + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: const EdgeInsets.all(0)), + child: const LoginScreen(), + ); + }); + } +} diff --git a/app_petty_cash/lib/model/auth_model.dart b/app_petty_cash/lib/model/auth_model.dart new file mode 100644 index 0000000..cc33824 --- /dev/null +++ b/app_petty_cash/lib/model/auth_model.dart @@ -0,0 +1,72 @@ +class AuthModel { + final String token; + final AuthDoctorModel model; + + AuthModel({ + required this.token, + required this.model, + }); +} + +class AuthDoctorModel { + + // "M_UserID": "1", + // "M_UserUsername": "alhadad", + // "M_UserM_DoctorCode": "3101210841", + // "M_UserM_DoctorID": "9562", + // "M_UserM_StaffID": "376", + // "ip": "139.194.156.91", + // "agent": "PostmanRuntime/7.29.2" + + late String doctorID; + late String doctorName; + + // new + late String M_UserID; + late String M_UserUsername; + late String M_UserM_DoctorCode; + late String M_UserM_DoctorID; + late String M_UserM_StaffID; + late String M_UserM_MouID; + late String ip; + late String agent; + + AuthDoctorModel({ + // required this.doctorID, + // required this.doctorName, + + required this.M_UserID, + required this.M_UserUsername, + required this.M_UserM_DoctorCode, + required this.M_UserM_DoctorID, + this.M_UserM_StaffID = "", + this.M_UserM_MouID = "", + this.ip = "", + this.agent = "" + }); + + AuthDoctorModel.fromJson(Map json) { + M_UserID = json['M_UserID'].toString(); + M_UserUsername = json['M_UserUsername'].toString(); + M_UserM_DoctorCode = json['M_UserM_DoctorCode'].toString(); + M_UserM_DoctorID = json['M_UserM_DoctorID'].toString(); + M_UserM_StaffID = json['M_UserM_StaffID'].toString(); + M_UserM_MouID = json['M_UserM_MouID'].toString(); + ip = json['ip'].toString(); + agent = json['agent'].toString(); + } + + Map toJson() { + final Map data = {}; + data['M_UserID'] = M_UserID; + data['M_UserUsername'] = M_UserUsername; + data['M_UserM_DoctorCode'] = M_UserM_DoctorCode; + data['M_UserM_DoctorID'] = M_UserM_DoctorID; + data['M_UserM_StaffID'] = M_UserM_StaffID; + data['M_UserM_MouID'] = M_UserM_MouID; + data['ip'] = ip; + data['agent'] = agent; + + return data; + } +} diff --git a/app_petty_cash/lib/provider/current_menu_provider.dart b/app_petty_cash/lib/provider/current_menu_provider.dart new file mode 100644 index 0000000..1c7d735 --- /dev/null +++ b/app_petty_cash/lib/provider/current_menu_provider.dart @@ -0,0 +1,3 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final currentPageProvider = StateProvider((ref) => null); \ No newline at end of file diff --git a/app_petty_cash/lib/provider/current_user_provider.dart b/app_petty_cash/lib/provider/current_user_provider.dart new file mode 100644 index 0000000..0fe7606 --- /dev/null +++ b/app_petty_cash/lib/provider/current_user_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../model/auth_model.dart'; + +final currentUserProvider = StateProvider((ref) => null); diff --git a/app_petty_cash/lib/provider/dio_provider.dart b/app_petty_cash/lib/provider/dio_provider.dart new file mode 100644 index 0000000..f03191a --- /dev/null +++ b/app_petty_cash/lib/provider/dio_provider.dart @@ -0,0 +1,4 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final dioProvider = Provider((ref) => Dio()); diff --git a/app_petty_cash/lib/repository/auth_repository.dart b/app_petty_cash/lib/repository/auth_repository.dart new file mode 100644 index 0000000..4154225 --- /dev/null +++ b/app_petty_cash/lib/repository/auth_repository.dart @@ -0,0 +1,90 @@ +import '../app/constant.dart'; +import '../model/auth_model.dart'; +import 'base_repository.dart'; + +class AuthRepository extends BaseRepository { + AuthRepository({required super.dio}); + + Future login({ + required String username, + required String dokterId, + required String password, + }) async { + final param = { + "username": username, + "doctor_id": dokterId, + "password": password + + // "username": "alhadad1", + // "doctor_id": "2891", + // "password": "3" + }; + // final service = "${Constant.baseUrl}xauth/login"; + final service = "${Constant.baseUrl}auth/login"; + final resp = await post(param: param, service: service); + final result = AuthModel( + token: resp["data"]["token"], + model: AuthDoctorModel.fromJson(resp["data"]["user"]), + ); + return result; + } + + Future logout({ + required String M_UserID, + required String M_UserUsername, + }) async { + final param = { + "M_UserID": M_UserID, + "M_UserUsername": M_UserUsername + // "username": "alhadad", + // "doctor_id": "3101210841", + // "password": "riau123" + }; + + final service = "${Constant.baseUrl}auth/logout"; + final resp = await post(param: param, service: service); + + if (resp["status"] == "OK") { + return resp['status']; + } else { + return resp['message']; + } + } + + Future change_password({ + required String token, + required String M_UserID, + required String username, + required String doctor_id, + required String new_password, + required String confirm_password, + }) async { + final param = { + "token": token, + "M_UserID": M_UserID, + "username": username, + "doctor_id": doctor_id, + "new_password": new_password, + "confirm_password": confirm_password + + // "username": "alhadad", + // "doctor_id": "3101210841", + // "password": "riau123" + }; + + final service = "${Constant.baseUrl}auth/change_password"; + final resp = await post(param: param, service: service); + + // final result = AuthModel( + // token: resp["data"]["token"], + // model: AuthDoctorModel.fromJson(resp["data"]["user"]), + // ); + // return result; + + if (resp["status"] == "OK") { + return resp['message']; + } else { + return resp['message']; + } + } +} diff --git a/app_petty_cash/lib/repository/base_repository.dart b/app_petty_cash/lib/repository/base_repository.dart new file mode 100644 index 0000000..2ed2a75 --- /dev/null +++ b/app_petty_cash/lib/repository/base_repository.dart @@ -0,0 +1,104 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; + +abstract class BaseRepository { + final Dio dio; + BaseRepository({required this.dio}); + + Future> post({ + required Map param, + required String service, + String? token, + }) async { + try { + final response = await dio.post( + // Constant.baseUrl + service, + service, + data: jsonEncode(param), + options: Options( + headers: token != null + ? { + HttpHeaders.contentTypeHeader: "application/json", + HttpHeaders.authorizationHeader: "Bearer $token", + } + : { + HttpHeaders.contentTypeHeader: "application/json", + }, + contentType: "application/json", + ), + ); + if (response.statusCode != 200) { + throw BaseRepositoryException( + message: "Invalid Http Response ${response.statusCode}", + ); + } + Map jsonData = jsonDecode(response.data); + if (jsonData["status"] != "OK") { + throw BaseRepositoryException( + message: jsonData["message"], + ); + } else { + return jsonData; + } + } on DioError catch (e) { + throw BaseRepositoryException(message: e.message); + } on SocketException catch (e) { + throw BaseRepositoryException(message: e.message); + } on BaseRepositoryException catch (e) { + throw BaseRepositoryException(message: e.message); + } + } + + Future> get({ + // required Map param, + required String service, + String? token, + }) async { + try { + final response = await dio.post( + // Constant.baseUrl + service, + service, + // data: jsonEncode(param), + options: Options( + headers: token != null + ? { + HttpHeaders.contentTypeHeader: "application/json", + HttpHeaders.authorizationHeader: "Bearer $token", + } + : { + HttpHeaders.contentTypeHeader: "application/json", + }, + contentType: "application/json", + ), + ); + if (response.statusCode != 200) { + throw BaseRepositoryException( + message: "Invalid Http Response ${response.statusCode}", + ); + } + Map jsonData = jsonDecode(response.data); + if (jsonData["status"] != "OK") { + throw BaseRepositoryException( + message: jsonData["message"], + ); + } else { + return jsonData; + } + } on DioError catch (e) { + throw BaseRepositoryException(message: e.message); + } on SocketException catch (e) { + throw BaseRepositoryException(message: e.message); + } on BaseRepositoryException catch (e) { + throw BaseRepositoryException(message: e.message); + } + } +} + +class BaseRepositoryException implements Exception { + final String message; + BaseRepositoryException({ + required this.message, + }); +} diff --git a/app_petty_cash/lib/screen/login/block_body.dart b/app_petty_cash/lib/screen/login/block_body.dart new file mode 100644 index 0000000..8aae4b8 --- /dev/null +++ b/app_petty_cash/lib/screen/login/block_body.dart @@ -0,0 +1,278 @@ +// ignore_for_file: prefer_const_constructors, use_full_hex_values_for_flutter_colors + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../app/constant.dart'; +import '../../app/route.dart'; +import '../../provider/current_menu_provider.dart'; +import '../../provider/current_user_provider.dart'; +import 'custom_text_field.dart'; +import 'login_provider.dart'; + +class BlockBody extends HookConsumerWidget { + const BlockBody({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isPasswordObscured = useState(true); + final ctrlUsername = useTextEditingController(text: ""); + final ctrlDokterId = useTextEditingController(text: ""); + final ctrlPassword = useTextEditingController(text: ""); + final isLoading = useState(false); + final errorMessage = useState(""); + final isSuccess = useState(false); + + ref.listen(loginProvider, (prev, next) { + if (next is LoginStateLoading) { + isLoading.value = true; + } else if (next is LoginStateError) { + isLoading.value = false; + errorMessage.value = next.message; + Timer(const Duration(seconds: 3), () { + errorMessage.value = ""; + }); + } else if (next is LoginStateDone) { + isLoading.value = false; + isSuccess.value = true; + ref.read(currentPageProvider.state).update((state) => 0); + ref.read(currentUserProvider.notifier).state = next.model; + + // print(next.model.model.doctorName); + + // Navigator.of(context).pop(); + // Navigator.of(context).pushNamedAndRemoveUntil(menuRoute,(route) => true,); + // Navigator.popAndPushNamed(context,menuRoute); + + Navigator.of(context) + .pushNamedAndRemoveUntil(menuRoute, (route) => false); + } + }); + + return ListView( + children: [ + Container( + width: Constant.getActualXPhone(context: context, x: 390), + height: Constant.getActualYPhone(context: context, y: 343), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('images/header_tanpa_logo.png'), + fit: BoxFit.cover, + ), + ), + child: Column( + children: [ + SizedBox( + height: Constant.getActualYPhone(context: context, y: 136), + ), + Container( + width: Constant.getActualXPhone(context: context, x: 148), + height: Constant.getActualYPhone(context: context, y: 78), + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.fitHeight, + image: AssetImage('images/logo_pramita.png'), + ), + ), + ), + ], + ), + ), + SizedBox( + height: Constant.getActualYPhone(context: context, y: 43), + ), + Container( + width: Constant.getActualXPhone(context: context, x: 391), + height: Constant.getActualYPhone(context: context, y: 454), + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 32), + right: Constant.getActualXPhone(context: context, x: 32), + top: Constant.getActualYPhone(context: context, y: 20), + bottom: Constant.getActualYPhone(context: context, y: 20), + ), + child: Column( + children: [ + // form + SizedBox( + width: Constant.getActualXPhone(context: context, x: 180), + height: Constant.getActualYPhone(context: context, y: 29), + child: Align( + alignment: Alignment.center, + child: Text("LOGIN DOKTER", + style: Constant.titleH3_700(context: context) + .copyWith(color: Constant.subTitleBlack)), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 4), + ), + + // notify error dari backend start + + if (isLoading.value) + SizedBox( + width: Constant.getActualXPhone(context: context, x: 20), + height: Constant.getActualYPhone(context: context, y: 20), + child: CircularProgressIndicator(), + ), + + if (errorMessage.value != "") + Text( + "Peringatan : ${errorMessage.value}", + style: Constant.titleH7_700(context: context) + .copyWith(color: Constant.textRedProblemMaketing), + ), + + // notify error dari backend end + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 24), + ), + // textfield marketing ID + SizedBox( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + child: CustomTextField( + ctrl: ctrlDokterId, + isPassword: false, + hintText: "Doctor ID", + labelText: "Doctor ID", + isError: (errorMessage.value != "") ? true : false, + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 20), + ), + // textfield username + SizedBox( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + child: CustomTextField( + ctrl: ctrlUsername, + isPassword: false, + hintText: "Username", + labelText: "Username", + isError: (errorMessage.value != "") ? true : false, + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 20), + ), + // textfield password + SizedBox( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + child: CustomTextField( + ctrl: ctrlPassword, + isPassword: false, + obscureText: isPasswordObscured.value, + hintText: "Password", + labelText: "Password", + onToggle: () { + isPasswordObscured.value = !isPasswordObscured.value; + }, + isError: (errorMessage.value != "") ? true : false, + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 40), + ), + // button login + SizedBox( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + child: ElevatedButton( + onPressed: (isSuccess.value == true) + ? () {} + : () { + if (ctrlUsername.text.isEmpty || + ctrlDokterId.text.isEmpty || + ctrlPassword.text.isEmpty) { + isLoading.value = true; + errorMessage.value = 'Inputan harus diisi'; + Timer(const Duration(seconds: 3), () { + isLoading.value = false; + errorMessage.value = ""; + }); + } else { + ref.read(loginProvider.notifier).login( + username: ctrlUsername.text, + dokterId: ctrlDokterId.text, + password: ctrlPassword.text); + } + }, + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith((st) => + (isSuccess.value == true) + ? Constant.textGrey + : Constant.buttonLoginBackgroundColor), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + side: BorderSide(color: Colors.red), + ), + ), + shadowColor: MaterialStateProperty.all(Color(0xffff48423d)), + elevation: MaterialStateProperty.all(4.0), + ), + child: Align( + alignment: Alignment.center, + child: Text( + 'LOGIN', + style: Constant.titleH3_700(context: context) + .copyWith(color: Constant.textLoginColor), + ), + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 22), + ), + + // versi + Align( + alignment: Alignment.center, + child: Text( + 'Versi ${Constant.version}', + style: Constant.titleH4Login(context: context) + .copyWith(color: Constant.textGrey), + ), + ), + + // text problem login + + // SizedBox( + // width: Constant.getActualXPhone(context: context, x: 121), + // height: Constant.getActualYPhone(context: context, y: 20), + // // color: Colors.brown, + // child: Align( + // alignment: Alignment.center, + // child: InkWell( + // onTap: () { + // Navigator.of(context).pushNamed(problemLoginRoute); + // }, + // child: Text( + // 'Problem Login ? ', + // style: Constant.body1_400(context: context) + // .copyWith(color: Constant.textRedProblemMaketing), + // ), + // ), + // ), + // ), + ], + ), + ), + ], + ); + } +} diff --git a/app_petty_cash/lib/screen/login/block_bodyv2.dart b/app_petty_cash/lib/screen/login/block_bodyv2.dart new file mode 100644 index 0000000..42b75c3 --- /dev/null +++ b/app_petty_cash/lib/screen/login/block_bodyv2.dart @@ -0,0 +1,375 @@ +// ignore_for_file: prefer_const_constructors, use_full_hex_values_for_flutter_colors + +import 'dart:async'; + +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/constant.dart'; +import '../../app/route.dart'; +import '../../provider/current_menu_provider.dart'; +import '../../provider/current_user_provider.dart'; +import 'custom_text_field.dart'; +import 'login_provider.dart'; + +class BlockBodyV2 extends HookConsumerWidget { + const BlockBodyV2({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isPasswordObscured = useState(true); + final ctrlUsername = useTextEditingController(text: ""); + final ctrlDokterId = useTextEditingController(text: ""); + final ctrlPassword = useTextEditingController(text: ""); + final isLoading = useState(false); + final errorMessage = useState(""); + final isSuccess = useState(false); + final focusNodeDokterID = useFocusNode(); + final focusNodeDokterUsername = useFocusNode(); + final focusNodeDokterPassword = useFocusNode(); + + final dokterIDhasFocus = useState(false); + final usernamehasFocus = useState(false); + final passwordhasFocus = useState(false); + + focusNodeDokterID.addListener(() { + if (focusNodeDokterID.hasPrimaryFocus) { + dokterIDhasFocus.value = true; + usernamehasFocus.value = false; + passwordhasFocus.value = false; + } else { + dokterIDhasFocus.value = false; + } + }); + + focusNodeDokterUsername.addListener(() { + if (focusNodeDokterUsername.hasFocus) { + dokterIDhasFocus.value = false; + usernamehasFocus.value = true; + passwordhasFocus.value = false; + } else { + usernamehasFocus.value = false; + } + }); + + focusNodeDokterPassword.addListener(() { + if (focusNodeDokterPassword.hasFocus) { + dokterIDhasFocus.value = false; + usernamehasFocus.value = false; + passwordhasFocus.value = true; + } else { + passwordhasFocus.value = false; + } + }); + + ref.listen(loginProvider, (prev, next) { + if (next is LoginStateLoading) { + isLoading.value = true; + } else if (next is LoginStateError) { + isLoading.value = false; + errorMessage.value = next.message; + Timer(const Duration(seconds: 3), () { + errorMessage.value = ""; + }); + } else if (next is LoginStateDone) { + isLoading.value = false; + isSuccess.value = true; + ref.read(currentPageProvider.state).update((state) => 0); + ref.read(currentUserProvider.notifier).state = next.model; + + // print(next.model.model.doctorName); + + // Navigator.of(context).pop(); + // Navigator.of(context).pushNamedAndRemoveUntil(menuRoute,(route) => true,); + // Navigator.popAndPushNamed(context,menuRoute); + + Navigator.of(context) + .pushNamedAndRemoveUntil(menuRoute, (route) => false); + } + }); + + return ListView( + children: [ + SizedBox( + height: Constant.getActualYPhone(context: context, y: 30), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 40), + right: Constant.getActualXPhone(context: context, x: 40), + ), + child: Container( + width: Constant.getActualXPhone(context: context, x: 283), + height: Constant.getActualYPhone(context: context, y: 100), + decoration: BoxDecoration( + // color: Colors.green, + image: DecorationImage( + fit: BoxFit.fitWidth, + // image: AssetImage('images/logo_pramita.png'), + image: AssetImage('images/logo_gabung_bg_png.png'), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone(context: context, y: 60), + ), + // form + Container( + width: Constant.getActualXPhone(context: context, x: 180), + height: Constant.getActualYPhone(context: context, y: 40), + // color: Colors.amber, + child: Align( + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + "LOGIN", + style: Constant.titleH3_700V2(context: context) + .copyWith(color: Constant.subTitleBlack), + ), + Text( + " DOKTER", + style: Constant.titleH3_700V2(context: context).copyWith( + color: Color(0xffd70302), + ), + ), + ], + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 20), + ), + + // notify error dari backend start + + if (isLoading.value) + SizedBox( + width: Constant.getActualXPhone(context: context, x: 20), + height: Constant.getActualYPhone(context: context, y: 20), + child: Center(child: CircularProgressIndicator()), + ), + + if (errorMessage.value != "") + Center( + child: Text( + "Peringatan : ${errorMessage.value}", + style: Constant.titleH7_700(context: context) + .copyWith(color: Constant.textRedProblemMaketing), + ), + ), + + // notify error dari backend end + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 50), + ), + // textfield marketing ID + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 100), + right: Constant.getActualXPhone(context: context, x: 100), + ), + child: Container( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + // color: Colors.blue, + // decoration: BoxDecoration( + // // border: Border.all(color: Colors.black), + // color: Color(0xffee0606), + // borderRadius: BorderRadius.circular(30), + // ), + child: CustomTextFieldLogin( + // onTap: () { + // dokterIDhasFocus.value = true; + // }, + // onEditingComplete: () { + // dokterIDhasFocus.value = false; + // }, + onChange: (String x) { + // print(x); + if (x.isNotEmpty || ctrlDokterId.text.isNotEmpty) { + print('onchange'); + focusNodeDokterID.requestFocus(); + } + }, + ctrl: ctrlDokterId, + focusNode: focusNodeDokterID, + hasFocus: dokterIDhasFocus.value, + isPassword: false, + hintText: "DOKTER ID", + labelText: "DOKTER ID", + isError: (errorMessage.value != "") ? true : false, + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 40), + ), + // textfield username + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 100), + right: Constant.getActualXPhone(context: context, x: 100), + ), + child: Container( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + // color: Colors.blue, + // decoration: BoxDecoration( + // // border: Border.all(color: Colors.black), + // color: Color(0xffee0606), + // borderRadius: BorderRadius.circular(30), + // ), + child: CustomTextFieldLogin( + // onTap: () { + // if (ctrlUsername.text.isNotEmpty) { + // focusNodeDokterUsername.requestFocus(); + // } + // }, + onChange: (String x) { + // print(x); + if (x.isNotEmpty || ctrlUsername.text.isNotEmpty) { + focusNodeDokterUsername.requestFocus(); + } + }, + ctrl: ctrlUsername, + focusNode: focusNodeDokterUsername, + hasFocus: usernamehasFocus.value, + isPassword: false, + hintText: "USERNAME", + labelText: "USERNAME", + isError: (errorMessage.value != "") ? true : false, + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 40), + ), + // textfield password + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 100), + right: Constant.getActualXPhone(context: context, x: 100), + ), + child: Container( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + // color: Colors.blue, + // decoration: BoxDecoration( + // // border: Border.all(color: Colors.black), + // color: Color(0xffee0606), + // borderRadius: BorderRadius.circular(30), + // ), + child: CustomTextFieldLogin( + // onTap: () { + // if (ctrlPassword.text.isNotEmpty) { + // focusNodeDokterPassword.requestFocus(); + // } + // }, + onChange: (String x) { + // print(x); + if (x.isNotEmpty || ctrlPassword.text.isNotEmpty) { + focusNodeDokterPassword.requestFocus(); + } + }, + ctrl: ctrlPassword, + focusNode: focusNodeDokterPassword, + hasFocus: passwordhasFocus.value, + isPassword: false, + obscureText: isPasswordObscured.value, + hintText: "PASSWORD", + labelText: "PASSWORD", + onToggle: () { + isPasswordObscured.value = !isPasswordObscured.value; + }, + isError: (errorMessage.value != "") ? true : false, + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 40), + ), + // button login + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 100), + right: Constant.getActualXPhone(context: context, x: 100), + ), + child: SizedBox( + width: Constant.getActualXPhone(context: context, x: 320), + height: Constant.getActualYPhone(context: context, y: 50), + child: ElevatedButton( + onPressed: (isSuccess.value == true) + ? () {} + : () { + if (ctrlUsername.text.isEmpty || + ctrlDokterId.text.isEmpty || + ctrlPassword.text.isEmpty) { + isLoading.value = true; + errorMessage.value = 'Inputan harus diisi'; + Timer(const Duration(seconds: 3), () { + isLoading.value = false; + errorMessage.value = ""; + }); + } else { + ref.read(loginProvider.notifier).login( + username: ctrlUsername.text, + dokterId: ctrlDokterId.text, + password: ctrlPassword.text); + } + }, + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith((st) => + (isSuccess.value == true) + ? Constant.textGrey + : Constant.buttonRedBackgroundColor), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + side: BorderSide(color: Colors.red), + ), + ), + shadowColor: MaterialStateProperty.all(Color(0xffff48423d)), + elevation: MaterialStateProperty.all(4.0), + ), + child: Align( + alignment: Alignment.center, + child: Text( + 'SIGN IN', + style: Constant.titleH3_700(context: context) + .copyWith(color: Constant.textLoginColor), + ), + ), + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 22), + ), + + // versi + Align( + alignment: Alignment.center, + child: Text( + 'Versi ${Constant.version}', + style: Constant.titleH4Login(context: context) + .copyWith(color: Constant.textGrey), + ), + ), + ], + ); + } +} diff --git a/app_petty_cash/lib/screen/login/block_header.dart b/app_petty_cash/lib/screen/login/block_header.dart new file mode 100644 index 0000000..f4c028a --- /dev/null +++ b/app_petty_cash/lib/screen/login/block_header.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +import '../../app/constant.dart'; + +class BlokHeader extends StatelessWidget { + const BlokHeader({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Image.asset( + 'images/Background.png', + fit: BoxFit.fill, + width: Constant.getActualXPhone(context: context, x: 390), + height: double.infinity, + ); + } +} diff --git a/app_petty_cash/lib/screen/login/custom_text_field.dart b/app_petty_cash/lib/screen/login/custom_text_field.dart new file mode 100644 index 0000000..0422ca8 --- /dev/null +++ b/app_petty_cash/lib/screen/login/custom_text_field.dart @@ -0,0 +1,290 @@ +import 'package:flutter/material.dart'; + +import '../../app/constant.dart'; + +// login +class CustomTextFieldLogin extends StatelessWidget { + final String hintText; + final String labelText; + final bool isPassword; + final bool obscureText; + final bool isMaxLine; + final void Function()? onToggle; + final TextEditingController? ctrl; + final void Function(String)? onChange; + final void Function(String)? onSubmitted; + final void Function()? onTap; + final void Function()? onEditingComplete; + final FocusNode? focusNode; + final bool isPrefix; + + final bool isError; + final bool isTextArea; + final bool isReadOnly; + + final bool hasFocus; + + const CustomTextFieldLogin({ + Key? key, + required this.hintText, + required this.labelText, + this.isPassword = false, + this.isMaxLine = false, + this.onToggle, + this.obscureText = false, + this.ctrl, + this.onChange, + this.onSubmitted, + this.focusNode, + this.isPrefix = false, + this.isTextArea = false, + this.isError = false, + this.isReadOnly = false, + this.hasFocus = false, + this.onTap, + this.onEditingComplete + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return TextField( + autofocus: true, + showCursor: (hasFocus) ? true : false, + readOnly: isReadOnly, + controller: ctrl, + enableInteractiveSelection: false, + style: + Constant.titleH4Login(context: context).copyWith(color: Colors.white), + obscureText: obscureText, + onChanged: onChange, + onSubmitted: onSubmitted, + onTap: onTap, + onEditingComplete: onEditingComplete, + focusNode: focusNode, + maxLines: (isTextArea) ? 4 : 1, + cursorColor: Colors.white, + decoration: InputDecoration( + fillColor: (hasFocus) ? Color(0xffff6868) : Color(0xffd90504), + filled: true, + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Constant.buttonLoginBackgroundColor, width: 2), + borderRadius: BorderRadius.circular(30), + ), + enabledBorder: (isError == true) + ? OutlineInputBorder( + // ignore: prefer_const_constructors + borderRadius: BorderRadius.all(Radius.circular(30)), + borderSide: BorderSide( + color: Constant.textRedProblemMaketing, + width: 1, + ), + ) + : OutlineInputBorder( + // ignore: prefer_const_constructors + borderRadius: BorderRadius.all(Radius.circular(30)), + borderSide: BorderSide( + color: Constant.buttonLoginBackgroundColor, + width: 2, + ), + ), + // hintStyle: Constant.body1_400(context: context) + // .copyWith(color: Constant.textBlack), + // labelStyle: Constant.body3_400(context: context), + hintStyle: + Constant.body3_400(context: context).copyWith(color: Colors.white), + labelStyle: Constant.body3_400(context: context), + // labelText: labelText, + hintText: hintText, + alignLabelWithHint: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(30), + ), + prefixIcon: isPrefix ? const Icon(Icons.search) : null, + prefixStyle: isPrefix ? Constant.titleH4Login(context: context) : null, + // suffixIcon: isPassword + // ? IconButton( + // alignment: Alignment.centerRight, + // onPressed: onToggle, + // icon: const Icon(Icons.remove_red_eye), + // iconSize: Constant.getActualYPhone(context: context, y: 24), + // ) + // : null, + ), + ); + } +} + +// selain login +class CustomTextField extends StatelessWidget { + final String hintText; + final String labelText; + final bool isPassword; + final bool obscureText; + final bool isMaxLine; + final void Function()? onToggle; + final TextEditingController? ctrl; + final void Function(String)? onChange; + final void Function(String)? onSubmitted; + final FocusNode? focusNode; + final bool isPrefix; + + final bool isError; + final bool isTextArea; + final bool isReadOnly; + + const CustomTextField( + {Key? key, + required this.hintText, + required this.labelText, + this.isPassword = false, + this.isMaxLine = false, + this.onToggle, + this.obscureText = false, + this.ctrl, + this.onChange, + this.onSubmitted, + this.focusNode, + this.isPrefix = false, + this.isTextArea = false, + this.isError = false, + this.isReadOnly = false}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return TextField( + readOnly: isReadOnly, + controller: ctrl, + style: Constant.titleH4Login(context: context), + obscureText: obscureText, + onChanged: onChange, + onSubmitted: onSubmitted, + focusNode: focusNode, + maxLines: (isTextArea) ? 4 : 1, + decoration: InputDecoration( + // focusedBorder: OutlineInputBorder( + // borderSide: BorderSide(color: Constant.textGrey, width: 1), + // borderRadius: BorderRadius.circular(8.0), + // ), + enabledBorder: (isError == true) + ? OutlineInputBorder( + // ignore: prefer_const_constructors + borderRadius: BorderRadius.all(Radius.circular(8)), + borderSide: BorderSide( + color: Constant.textRedProblemMaketing, + width: 1, + ), + ) + : null, + // hintStyle: Constant.body1_400(context: context) + // .copyWith(color: Constant.textBlack), + // labelStyle: Constant.body3_400(context: context), + hintStyle: Constant.body3_400(context: context), + labelStyle: Constant.body3_400(context: context), + labelText: labelText, + hintText: hintText, + alignLabelWithHint: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + prefixIcon: isPrefix ? const Icon(Icons.search) : null, + prefixStyle: isPrefix ? Constant.titleH4Login(context: context) : null, + // suffixIcon: isPassword + // ? IconButton( + // alignment: Alignment.centerRight, + // onPressed: onToggle, + // icon: const Icon(Icons.remove_red_eye), + // iconSize: Constant.getActualYPhone(context: context, y: 24), + // ) + // : null, + ), + ); + } +} + +// untuk inputan selain login +class CustomTextFieldV2 extends StatelessWidget { + final String hintText; + final String labelText; + final bool isPassword; + final bool obscureText; + final bool isMaxLine; + final void Function()? onToggle; + final TextEditingController? ctrl; + final void Function(String)? onChange; + final void Function(String)? onSubmitted; + final FocusNode? focusNode; + final bool isPrefix; + final InkWell? suffixIcon; + + final bool isError; + final bool isTextArea; + + const CustomTextFieldV2( + {Key? key, + required this.hintText, + required this.labelText, + this.isPassword = false, + this.isMaxLine = false, + this.onToggle, + this.obscureText = false, + this.ctrl, + this.onChange, + this.onSubmitted, + this.focusNode, + this.isPrefix = false, + this.isTextArea = false, + this.isError = false, + this.suffixIcon}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return TextField( + controller: ctrl, + style: Constant.titleH4Login(context: context), + obscureText: obscureText, + onChanged: onChange, + onSubmitted: onSubmitted, + focusNode: focusNode, + maxLines: (isTextArea) ? 4 : 1, + decoration: InputDecoration( + // focusedBorder: OutlineInputBorder( + // borderSide: BorderSide(color: Constant.textGrey, width: 1), + // borderRadius: BorderRadius.circular(8.0), + // ), + enabledBorder: (isError == true) + ? OutlineInputBorder( + // ignore: prefer_const_constructors + borderRadius: BorderRadius.all(Radius.circular(8)), + borderSide: BorderSide( + color: Constant.textRedProblemMaketing, + width: 1, + ), + ) + : null, + hintStyle: Constant.body3_400(context: context), + labelStyle: Constant.body3_400(context: context), + labelText: labelText, + hintText: hintText, + alignLabelWithHint: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + prefixIcon: isPrefix ? const Icon(Icons.search) : null, + prefixStyle: + isPrefix ? Constant.titleH4Login(context: context) : null, + suffixIcon: suffixIcon + // suffixIcon: isPassword + // ? IconButton( + // alignment: Alignment.centerRight, + // onPressed: onToggle, + // icon: const Icon(Icons.remove_red_eye), + // iconSize: Constant.getActualYPhone(context: context, y: 24), + // ) + // : null, + ), + ); + } +} diff --git a/app_petty_cash/lib/screen/login/login_provider.dart b/app_petty_cash/lib/screen/login/login_provider.dart new file mode 100644 index 0000000..14c5cc0 --- /dev/null +++ b/app_petty_cash/lib/screen/login/login_provider.dart @@ -0,0 +1,83 @@ +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../app/constant.dart'; +import '../../model/auth_model.dart'; +import '../../provider/current_user_provider.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/auth_repository.dart'; +import '../../repository/base_repository.dart'; + +// 3. state provider +final loginProvider = StateNotifierProvider( + (ref) => LoginNotifier(ref: ref)); + +// 2. notifier +class LoginNotifier extends StateNotifier { + final Ref ref; + LoginNotifier({required this.ref}) : super(LoginStateInit()); + void login({ + required String username, + required String dokterId, + required String password, + }) async { + try { + state = LoginStateLoading(); + final resp = await AuthRepository(dio: ref.read(dioProvider)) + .login(username: username, dokterId: dokterId, password: password); + + // print(resp); + state = LoginStateDone(model: resp); + //Simpan ke token + final shared = await SharedPreferences.getInstance(); + final token = jsonEncode({ + "date": DateTime.now().toString(), + "model": resp.model, + "token": resp.token + }); + await shared.setString(Constant.bearerName, token); + ref.read(currentUserProvider.notifier).state = resp; + + // print(shared.getString(Constant.bearerName)); + } catch (e) { + if (e is BaseRepositoryException) { + state = LoginStateError(message: e.message); + } else { + state = LoginStateError(message: e.toString()); + } + } + } +} + +// 1. state +abstract class LoginState extends Equatable { + final DateTime date; + const LoginState(this.date); + @override + List get props => [date]; +} + +class LoginStateInit extends LoginState { + LoginStateInit() : super(DateTime.now()); +} + +class LoginStateLoading extends LoginState { + LoginStateLoading() : super(DateTime.now()); +} + +class LoginStateError extends LoginState { + final String message; + LoginStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class LoginStateDone extends LoginState { + final AuthModel model; + LoginStateDone({ + required this.model, + }) : super(DateTime.now()); +} diff --git a/app_petty_cash/lib/screen/login/login_screen.dart b/app_petty_cash/lib/screen/login/login_screen.dart new file mode 100644 index 0000000..478e867 --- /dev/null +++ b/app_petty_cash/lib/screen/login/login_screen.dart @@ -0,0 +1,79 @@ +// ignore_for_file: use_build_context_synchronously, prefer_const_constructors + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../app/constant.dart'; +import '../../app/route.dart'; +import '../../model/auth_model.dart'; +import '../../provider/current_menu_provider.dart'; +import '../../provider/current_user_provider.dart'; +import 'block_body.dart'; +import 'block_bodyv2.dart'; + +class LoginScreen extends HookConsumerWidget { + const LoginScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final shared = await SharedPreferences.getInstance(); + final bearerString = shared.get(Constant.bearerName).toString(); + final xmodel = jsonDecode(bearerString); + if (xmodel == null) return; + final authModel = AuthModel( + token: xmodel["token"], + model: AuthDoctorModel( + M_UserM_DoctorID: xmodel["model"]["M_UserM_DoctorID"], + M_UserUsername: xmodel["model"]["M_UserUsername"], + M_UserM_DoctorCode: xmodel["model"]["M_UserM_DoctorCode"], + M_UserM_MouID: xmodel["model"]["M_UserM_MouID"] ?? "", + M_UserID: xmodel["model"]["M_UserID"]), + ); + ref.read(currentUserProvider.notifier).state = authModel; + + ref.read(currentPageProvider.state).update((state) => 0); + + Navigator.of(context).pushNamedAndRemoveUntil( + menuRoute, + (route) => false, + ); + }); + return () {}; + }, []); + + return GestureDetector( + // buat dismiss keyboard + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Scaffold( + resizeToAvoidBottomInset: true, + backgroundColor: Constant.backgroundColor, + body: SafeArea( + child: Center( + child: Container( + width: Constant.getActualXPhone(context: context, x: 390), + height: Constant.getActualYPhone(context: context, y: 844), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffdadae6), + Color(0xfff6f7fb), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + // child: BlockBody(), + child: BlockBodyV2(), + ), + ), + ), + ), + ); + } +} diff --git a/app_petty_cash/lib/screen/splash/splash_screen.dart b/app_petty_cash/lib/screen/splash/splash_screen.dart new file mode 100644 index 0000000..4a80b1a --- /dev/null +++ b/app_petty_cash/lib/screen/splash/splash_screen.dart @@ -0,0 +1,64 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../app/constant.dart'; +import '../../app/route.dart'; +import '../../provider/current_user_provider.dart'; + +class SplashScreen extends HookConsumerWidget { + const SplashScreen({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + // useEffect(() { + // WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + // final doctorID = + // ref.read(currentUserProvider)?.model.M_UserM_DoctorCode ?? "0"; + // // print(doctorID); + + // if (doctorID == "0") { + // //not login + + // Navigator.of(context) + // .pushNamedAndRemoveUntil(loginRoute, (route) => false); + // return; + + // } else { + // Navigator.of(context) + // .pushNamedAndRemoveUntil(menuRoute, (route) => false); + // } + // }); + // return () {}; + // }, []); + + // setelah 3 detik redirect ke login route + Timer(const Duration(seconds: 3), () { + Navigator.of(context).pushNamedAndRemoveUntil( + loginRoute, + (route) => false, + ); + }); + + return Scaffold( + // appBar: AppBar( + // backgroundColor: Constant.backgroundColor, + // elevation: 0, + // ), + backgroundColor: Constant.backgroundColor, + body: SafeArea( + child: Container( + width: Constant.getActualXPhone(context: context, x: 390), + height: Constant.getActualYPhone(context: context, y: 844), + child: Image.asset( + "images/splashscreen.png", + fit: BoxFit.cover, + // scale: 1, + ), + // color: Colors.green, + ), + ), + ); + } +}