Compare commits
23 Commits
f4df67fe7f
...
develop_fl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40d6f5ec2f | ||
|
|
5bf87205cc | ||
|
|
1dd0e61a02 | ||
|
|
ab9089f8b4 | ||
|
|
e06bd02a21 | ||
|
|
6bab2dbef3 | ||
|
|
40453ab16b | ||
|
|
102760babc | ||
|
|
7bd8527df2 | ||
|
|
a7d83956ec | ||
|
|
3806462d7b | ||
|
|
f85afe7103 | ||
|
|
62409f9feb | ||
|
|
08dd105fe5 | ||
|
|
b4dea334d1 | ||
|
|
461fb00aed | ||
|
|
6e15b23ded | ||
|
|
9a78ec1d92 | ||
|
|
7d1e528546 | ||
|
|
eb7236671e | ||
|
|
3790e9e11a | ||
|
|
c23e68de36 | ||
|
|
d35ca7ab8b |
31
.vscode/launch.json
vendored
Normal file
31
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "absensi_sas_flutter_web",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": ["--web-port","5000"]
|
||||
},
|
||||
{
|
||||
"name": "absensi_sas_flutter",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "absensi_sas_flutter (profile mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "absensi_sas_flutter (release mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
images/btn_approval.png
Normal file
BIN
images/btn_approval.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
images/custom_marker1.png
Normal file
BIN
images/custom_marker1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -4,12 +4,26 @@ class Constant {
|
||||
// static double designHeight = 1024;
|
||||
// static double designWidth = 1440;
|
||||
|
||||
// base url graphql
|
||||
static String baseURLGraphQl = "http://devone.aplikasi.web.id:3300/query";
|
||||
// base url graphql web di server
|
||||
static String baseURLGraphQl = "https://devone.aplikasi.web.id/query";
|
||||
|
||||
// base url graphql web pada waktu develop
|
||||
// static String baseURLGraphQl = "http://devone.aplikasi.web.id:3300/query";
|
||||
|
||||
// static String baseURLGraphQl = "http://localhost:8080/query";
|
||||
|
||||
static String bearerName = "absensi-sas";
|
||||
static String accountGoogle = "absensi-google-account";
|
||||
|
||||
// api key google map
|
||||
static String apikeyGoogleMap = "AIzaSyCiN7EeJsUpXVLQKFfrj3sE5OTKebjpzek";
|
||||
static String baseUrlGoogleMapApis =
|
||||
"https://maps.googleapis.com/maps/api/geocode/json?";
|
||||
|
||||
// position longitude dan latitude awal
|
||||
static double positionLatitudeAwal = -7.566957;
|
||||
static double positionLongitudeAwal = 110.8080284;
|
||||
|
||||
static double designHeightPhone = 844;
|
||||
static double designWidthPhone = 390;
|
||||
|
||||
@@ -171,4 +185,47 @@ class Constant {
|
||||
fontFamily: 'Quicksand',
|
||||
);
|
||||
}
|
||||
|
||||
static TextStyle title_screen({required BuildContext context}) {
|
||||
return TextStyle(
|
||||
fontSize: Constant.getActualYPhone(context: context, y: 24),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Public Sans',
|
||||
);
|
||||
}
|
||||
|
||||
static TextStyle body_16({required BuildContext context}) {
|
||||
return TextStyle(
|
||||
fontSize: Constant.getActualYPhone(context: context, y: 16),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Public Sans',
|
||||
);
|
||||
}
|
||||
|
||||
static TextStyle body_14({required BuildContext context}) {
|
||||
return TextStyle(
|
||||
fontSize: Constant.getActualYPhone(context: context, y: 14),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Public Sans',
|
||||
);
|
||||
}
|
||||
|
||||
static TextStyle body_12({required BuildContext context}) {
|
||||
return TextStyle(
|
||||
fontSize: Constant.getActualYPhone(context: context, y: 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Public Sans',
|
||||
);
|
||||
}
|
||||
|
||||
static Color primaryBlue = const Color(0xff0C53B7);
|
||||
static Color bgBlue = const Color(0xff1890FF).withOpacity(0.16);
|
||||
static Color primaryOrange = const Color(0xffF15A29);
|
||||
static Color bgOrange = const Color(0xffF15A29).withOpacity(0.16);
|
||||
static Color secondaryBlue = const Color(0xff43ADA5);
|
||||
static Color bgSecondaryBlue = const Color(0xff43ADA5).withOpacity(0.16);
|
||||
static Color primaryGreen = const Color(0xff229A16);
|
||||
static Color bgGreen = const Color(0xff54D62C).withOpacity(0.08);
|
||||
static Color primaryRed = const Color(0xffB72136);
|
||||
static Color bgRed = const Color(0xffFF4842).withOpacity(0.08);
|
||||
}
|
||||
|
||||
41
lib/app/googleapis_location.dart
Normal file
41
lib/app/googleapis_location.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
Future<String> searchPosition(LatLng position) async {
|
||||
const apiKey = "AIzaSyAVUr4Ku4O1HlSkK8n9KGnUyqvsXBL-yfs";
|
||||
final latitudeString = position.latitude.toString();
|
||||
final longitudeString = position.longitude.toString();
|
||||
const url = 'https://maps.googleapis.com/maps/api/geocode/json';
|
||||
|
||||
try {
|
||||
final response = await Dio().get(
|
||||
url,
|
||||
queryParameters: {
|
||||
'latlng': '$latitudeString,$longitudeString',
|
||||
'key': apiKey,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
// Mengambil compound_code dari hasil JSON
|
||||
final plusCode = data['plus_code'];
|
||||
if (plusCode != null && plusCode['compound_code'] != null) {
|
||||
return plusCode['compound_code'];
|
||||
} else {
|
||||
print('Alamat Tidak Ditemukan');
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
print('Failed to load data');
|
||||
return "";
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error: $e');
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> positionToAddressGoogleApis(LatLng position) async {
|
||||
return await searchPosition(position);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:absensi_sas/screen/approval/approval_screen.dart';
|
||||
import 'package:absensi_sas/screen/approval_detail/approval_detail_screen.dart';
|
||||
import 'package:absensi_sas/screen/home/home_screen_v1.dart';
|
||||
import 'package:absensi_sas/screen/presensi/presensi_screen.dart';
|
||||
import 'package:absensi_sas/screen/presensi/presensi_selfie_screen.dart';
|
||||
import 'package:absensi_sas/test_flutter_web_map.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../screen/home/home_screen.dart';
|
||||
import '../test_flutter_map.dart';
|
||||
@@ -13,9 +16,23 @@ const testFlutterMapRoute = "/testFlutterMapRoute";
|
||||
const homeRoute = "/homeRoute";
|
||||
const presensiRoute = "/presensiRoute";
|
||||
const presensiSelfieRoute = "/presensiSelfieRoute";
|
||||
const approvalRoute = "/approvalRoute";
|
||||
const approvalDetailRoute = "/approvalDetailRoute";
|
||||
const testFlutterWebMapRoute = "/testFlutterWebMapRoute";
|
||||
|
||||
class AppRoute {
|
||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||
// flutter web map
|
||||
if (settings.name == testFlutterWebMapRoute) {
|
||||
return MaterialPageRoute(builder: (context) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context)
|
||||
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
|
||||
child: TestFlutterWebMap(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// test flutter map
|
||||
if (settings.name == testFlutterMapRoute) {
|
||||
return MaterialPageRoute(builder: (context) {
|
||||
@@ -81,6 +98,26 @@ class AppRoute {
|
||||
);
|
||||
});
|
||||
}
|
||||
// approval
|
||||
if (settings.name == approvalRoute) {
|
||||
return MaterialPageRoute(builder: (context) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context)
|
||||
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
|
||||
child: ApprovalScreen(),
|
||||
);
|
||||
});
|
||||
}
|
||||
// approvalDetail
|
||||
if (settings.name == approvalDetailRoute) {
|
||||
return MaterialPageRoute(builder: (context) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context)
|
||||
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
|
||||
child: ApprovalDetailScreen(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return MaterialPageRoute(builder: (context) {
|
||||
return MediaQuery(
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/test_flutter_web_map.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../app/route.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
|
||||
import 'test_map_x.dart';
|
||||
// import '../test_map.dart';
|
||||
|
||||
// final routerProvider = Provider((_) => GlobalKey<NavigatorState>());
|
||||
@@ -43,10 +47,27 @@ class MyApp extends StatelessWidget {
|
||||
// ),
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.orange,
|
||||
primaryColor: Constant.textOrange,
|
||||
primaryColorDark: Constant.textOrange,
|
||||
primaryColorLight: Constant.textOrange,
|
||||
colorScheme: Theme.of(context).colorScheme.copyWith(
|
||||
primary: Constant.textOrange,
|
||||
background: Colors.white,
|
||||
surface: Colors.white),
|
||||
// datePickerTheme: DatePickerThemeData(
|
||||
// // backgroundColor: Colors.white,
|
||||
// // headerHeadlineStyle:
|
||||
// // Constant.h3_400(context: context).copyWith(color: Colors.white),
|
||||
// headerBackgroundColor: Constant.textOrange,
|
||||
// headerForegroundColor: Colors.white,
|
||||
// surfaceTintColor: Colors.white,
|
||||
// backgroundColor: Colors.white),
|
||||
),
|
||||
|
||||
// home: TestMap(),
|
||||
initialRoute: loginRoute,
|
||||
// initialRoute: presensiSelfieRoute,
|
||||
// home: TestMapFlutterWeb(),
|
||||
// home:TestMapX(),
|
||||
// initialRoute: testFlutterMapRoute,
|
||||
onGenerateRoute: AppRoute.generateRoute,
|
||||
);
|
||||
|
||||
56
lib/model/approval_detail_model.dart
Normal file
56
lib/model/approval_detail_model.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
class ApprovalDetailModel {
|
||||
String? id;
|
||||
String? name;
|
||||
String? nip;
|
||||
String? date;
|
||||
String? time;
|
||||
String? address;
|
||||
double? lat;
|
||||
double? long;
|
||||
String? imagePath;
|
||||
String? statusID;
|
||||
String? statusName;
|
||||
|
||||
ApprovalDetailModel(
|
||||
{this.id,
|
||||
this.name,
|
||||
this.nip,
|
||||
this.date,
|
||||
this.time,
|
||||
this.address,
|
||||
this.lat,
|
||||
this.long,
|
||||
this.imagePath,
|
||||
this.statusID,
|
||||
this.statusName});
|
||||
|
||||
ApprovalDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
name = json['Name'];
|
||||
nip = json['nip'];
|
||||
date = json['date'];
|
||||
time = json['time'];
|
||||
address = json['address'];
|
||||
lat = json['lat'];
|
||||
long = json['long'];
|
||||
imagePath = json['imagePath'];
|
||||
statusID = json['statusID'];
|
||||
statusName = json['statusName'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['id'] = this.id;
|
||||
data['Name'] = this.name;
|
||||
data['nip'] = this.nip;
|
||||
data['date'] = this.date;
|
||||
data['time'] = this.time;
|
||||
data['address'] = this.address;
|
||||
data['lat'] = this.lat;
|
||||
data['long'] = this.long;
|
||||
data['imagePath'] = this.imagePath;
|
||||
data['statusID'] = this.statusID;
|
||||
data['statusName'] = this.statusName;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
68
lib/model/approval_model.dart
Normal file
68
lib/model/approval_model.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
class ApprovalModel {
|
||||
String? id;
|
||||
String? name;
|
||||
String? date;
|
||||
String? time;
|
||||
String? typename;
|
||||
String? typeID;
|
||||
String? presensiTypeName;
|
||||
String? presensitypeID;
|
||||
String? address;
|
||||
String? reasonType;
|
||||
String? reasontypeID;
|
||||
String? reasonDescription;
|
||||
String? statusID;
|
||||
String? statusName;
|
||||
|
||||
ApprovalModel(
|
||||
{this.id,
|
||||
this.name,
|
||||
this.date,
|
||||
this.time,
|
||||
this.typename,
|
||||
this.typeID,
|
||||
this.presensiTypeName,
|
||||
this.presensitypeID,
|
||||
this.address,
|
||||
this.reasonType,
|
||||
this.reasontypeID,
|
||||
this.reasonDescription,
|
||||
this.statusID,
|
||||
this.statusName});
|
||||
|
||||
ApprovalModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
name = json['name'];
|
||||
date = json['date'];
|
||||
time = json['time'];
|
||||
typename = json['typename'];
|
||||
typeID = json['typeID'];
|
||||
presensiTypeName = json['presensiTypeName'];
|
||||
presensitypeID = json['presensitypeID'];
|
||||
address = json['address'];
|
||||
reasonType = json['reasonType'];
|
||||
reasontypeID = json['reasontypeID'];
|
||||
reasonDescription = json['reasonDescription'];
|
||||
statusID = json['statusID'];
|
||||
statusName = json['statusName'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['id'] = this.id;
|
||||
data['name'] = this.name;
|
||||
data['date'] = this.date;
|
||||
data['time'] = this.time;
|
||||
data['typename'] = this.typename;
|
||||
data['typeID'] = this.typeID;
|
||||
data['presensiTypeName'] = this.presensiTypeName;
|
||||
data['presensitypeID'] = this.presensitypeID;
|
||||
data['address'] = this.address;
|
||||
data['reasonType'] = this.reasonType;
|
||||
data['reasontypeID'] = this.reasontypeID;
|
||||
data['reasonDescription'] = this.reasonDescription;
|
||||
data['statusID'] = this.statusID;
|
||||
data['statusName'] = this.statusName;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
24
lib/model/approval_type_model.dart
Normal file
24
lib/model/approval_type_model.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
class ApprovalTypeModel {
|
||||
String? id;
|
||||
String? value;
|
||||
String? name;
|
||||
bool? isActive;
|
||||
|
||||
ApprovalTypeModel({this.id, this.value, this.name, this.isActive});
|
||||
|
||||
ApprovalTypeModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
value = json['value'];
|
||||
name = json['name'];
|
||||
isActive = json['isActive'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['id'] = this.id;
|
||||
data['value'] = this.value;
|
||||
data['name'] = this.name;
|
||||
data['isActive'] = this.isActive;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
28
lib/provider/approval_filter_provider.dart
Normal file
28
lib/provider/approval_filter_provider.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:absensi_sas/model/approval_type_model.dart';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class ApprovalFilterProvider {
|
||||
DateTime startDate = DateTime.now();
|
||||
DateTime endDate = DateTime.now();
|
||||
String keyword = "";
|
||||
List<String> selectedApprovalType = [];
|
||||
final approvalTypeList = [
|
||||
ApprovalTypeModel(id: "1", name: "Presensi", isActive: false, value: "p"),
|
||||
ApprovalTypeModel(id: "2", name: "Cuti", isActive: true, value: "c"),
|
||||
ApprovalTypeModel(id: "3", name: "Lembur", isActive: false, value: "l"),
|
||||
];
|
||||
ApprovalFilterProvider(
|
||||
{required this.endDate,
|
||||
required this.keyword,
|
||||
required this.selectedApprovalType,
|
||||
required this.startDate});
|
||||
}
|
||||
|
||||
final approvalFilterProvider = StateProvider<ApprovalFilterProvider>((ref) {
|
||||
return ApprovalFilterProvider(
|
||||
selectedApprovalType: [],
|
||||
endDate: DateTime.now(),
|
||||
keyword: "",
|
||||
startDate: DateTime.now());
|
||||
});
|
||||
68
lib/provider/camera_controller_provider.dart
Normal file
68
lib/provider/camera_controller_provider.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
final imgPhotoWebProvider = StateProvider<String?>((ref) => "");
|
||||
|
||||
final cameraControllerProvider =
|
||||
StateNotifierProvider<CameraControllerNotifier, CameraController?>((ref) {
|
||||
return CameraControllerNotifier();
|
||||
});
|
||||
|
||||
// StateNotifier to manage CameraController
|
||||
class CameraControllerNotifier extends StateNotifier<CameraController?> {
|
||||
CameraControllerNotifier() : super(null) {
|
||||
_initializeCamera();
|
||||
}
|
||||
|
||||
Future<void> _initializeCamera() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
try {
|
||||
final cameras = await availableCameras();
|
||||
if (cameras.isNotEmpty) {
|
||||
final controller = CameraController(cameras[0], ResolutionPreset.max);
|
||||
await controller.initialize();
|
||||
state = controller;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error initializing camera: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> takePicture(
|
||||
// ValueNotifier<XFile?> imageFile,
|
||||
ValueNotifier<String> base64ImageString,
|
||||
ValueNotifier<String> imagePath,
|
||||
) async {
|
||||
// final flag = ValueNotifier<bool>(false);
|
||||
if (state != null) {
|
||||
try {
|
||||
final image = await state!.takePicture();
|
||||
final bytes = await image.readAsBytes();
|
||||
String base64Image = base64Encode(bytes);
|
||||
|
||||
// imageFile.value = image;
|
||||
base64ImageString.value = base64Image;
|
||||
imagePath.value = image.path;
|
||||
|
||||
// final shared = await SharedPreferences.getInstance();
|
||||
// shared.setString("base64Image", base64Image);
|
||||
} catch (e) {
|
||||
print('Error taking picture: $e');
|
||||
// flag.value = false;
|
||||
// RespErr(flag: false, message: 'Error taking picture: $e');
|
||||
}
|
||||
} else {
|
||||
print('CameraController is not initialized.');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
state?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
const String googleClientID =
|
||||
"856240587825-klh0dfjc44bovajg1rpq5vbvs4g7rh5j.apps.googleusercontent.com";
|
||||
|
||||
final googleSignInProvider = StateProvider<GoogleSignIn>((ref) {
|
||||
return GoogleSignIn(
|
||||
clientId: googleClientID,
|
||||
scopes: [
|
||||
'email',
|
||||
'https://www.googleapis.com/auth/contacts.readonly',
|
||||
@@ -10,6 +14,10 @@ final googleSignInProvider = StateProvider<GoogleSignIn>((ref) {
|
||||
);
|
||||
});
|
||||
|
||||
final currentUserGoogleProvider = StateProvider<GoogleSignInAccount?>((ref) => null);
|
||||
final currentUserGoogleProvider =
|
||||
StateProvider<GoogleSignInAccount?>((ref) => null);
|
||||
|
||||
final isNotifyFromLogout = StateProvider<bool>((ref) => false);
|
||||
final isNotifyFromLogout = StateProvider<bool>((ref) => false);
|
||||
|
||||
final currentLatitudeProvider = StateProvider<double>((ref) => 0.0);
|
||||
final currentLongitudeProvider = StateProvider<double>((ref) => 0.0);
|
||||
|
||||
10
lib/provider/location_provider.dart
Normal file
10
lib/provider/location_provider.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
final locationProvider = StateProvider<Map<String, dynamic>>((ref) {
|
||||
return {
|
||||
'status': null,
|
||||
'message': null,
|
||||
'latitude': null,
|
||||
'longitude': null,
|
||||
};
|
||||
});
|
||||
@@ -36,7 +36,7 @@ class AuthRepository extends BaseRepository {
|
||||
Future<LogoutResponseModel> logout(
|
||||
String email, String idGoogleSignIn) async {
|
||||
const String query =
|
||||
r'''mutation($emailParam:String!, $id_google_sign_in_Param:String!){ logoutAttendance(email:$emailParam, id_google_sign_in:$id_google_sign_in_Param){ staff_id nip name email phone_number token id_google_sign_in company_id company_name } }''';
|
||||
r'''mutation($emailParam:String!, $id_google_sign_in_Param:String!){ logoutAttendance(email:$emailParam, id_google_sign_in:$id_google_sign_in_Param){ status message } }''';
|
||||
|
||||
Map<String, dynamic> inpVariables = {
|
||||
"emailParam": email,
|
||||
|
||||
45
lib/repository/googleapis_repository.dart
Normal file
45
lib/repository/googleapis_repository.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import 'base_repository.dart';
|
||||
|
||||
class GoogleApisRepository extends BaseRepository {
|
||||
GoogleApisRepository({required super.graphql, required super.dio});
|
||||
|
||||
Future<String> getAddressFromCoordinates(
|
||||
String latitude, String longitude) async {
|
||||
final dio = Dio();
|
||||
const apiKey = "AIzaSyAVUr4Ku4O1HlSkK8n9KGnUyqvsXBL-yfs";
|
||||
final latitudeString = latitude.toString();
|
||||
final longitudeString = longitude.toString();
|
||||
const url =
|
||||
'https://maps.googleapis.com/maps/api/geocode/json';
|
||||
|
||||
try {
|
||||
final response = await dio.get(
|
||||
url,
|
||||
queryParameters: {
|
||||
'latlng': '$latitudeString,$longitudeString',
|
||||
'key': apiKey,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
// Mengambil compound_code dari hasil JSON
|
||||
final plusCode = data['plus_code'];
|
||||
if (plusCode != null && plusCode['compound_code'] != null) {
|
||||
return plusCode['compound_code'];
|
||||
} else {
|
||||
print('Alamat Tidak Ditemukan');
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
print('Failed to load data');
|
||||
return "";
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error: $e');
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
158
lib/screen/approval/approval_mockup.dart
Normal file
158
lib/screen/approval/approval_mockup.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
import 'package:absensi_sas/model/approval_detail_model.dart';
|
||||
import 'package:absensi_sas/model/approval_model.dart';
|
||||
|
||||
List<ApprovalModel> approvalCardList = [
|
||||
ApprovalModel(
|
||||
id: "1",
|
||||
name: "Alfianto Andy P",
|
||||
date: "21 Feb 2024",
|
||||
time: "08:03",
|
||||
typeID: "1",
|
||||
typename: "Presensi",
|
||||
presensitypeID: "1",
|
||||
presensiTypeName: "Clock In",
|
||||
address:
|
||||
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
|
||||
statusID: "",
|
||||
statusName: ""),
|
||||
ApprovalModel(
|
||||
id: "1",
|
||||
name: "Alfianto Andy P",
|
||||
date: "21 Feb 2024",
|
||||
time: "08:03",
|
||||
typeID: "1",
|
||||
typename: "Presensi",
|
||||
presensitypeID: "1",
|
||||
presensiTypeName: "Clock In",
|
||||
address:
|
||||
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
|
||||
statusID: "1",
|
||||
statusName: "Approve"),
|
||||
ApprovalModel(
|
||||
id: "2",
|
||||
name: "Alfianto Andy P",
|
||||
date: "21 Feb 2024",
|
||||
time: "17:03",
|
||||
typeID: "1",
|
||||
typename: "Presensi",
|
||||
presensitypeID: "2",
|
||||
presensiTypeName: "Clock Out",
|
||||
address:
|
||||
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
|
||||
statusID: "2",
|
||||
statusName: "Reject"),
|
||||
ApprovalModel(
|
||||
id: "3",
|
||||
name: "Hanan Askarim",
|
||||
date: "06 Jan 2024 - 06 Jan 2024",
|
||||
time: "0",
|
||||
typeID: "2",
|
||||
typename: "Cuti",
|
||||
statusID: "0",
|
||||
reasonType: "Cuti Tahunan",
|
||||
reasonDescription: "Kondangan",
|
||||
reasontypeID: "1",
|
||||
statusName: "0"),
|
||||
ApprovalModel(
|
||||
id: "4",
|
||||
name: "Hanan Askarim",
|
||||
date: "06 Jan 2024 - 06 Jan 2024",
|
||||
time: "0",
|
||||
typeID: "2",
|
||||
typename: "Cuti",
|
||||
statusID: "1",
|
||||
reasonType: "Cuti Tahunan",
|
||||
reasonDescription: "Tidur",
|
||||
reasontypeID: "1",
|
||||
statusName: "Approve"),
|
||||
ApprovalModel(
|
||||
id: "4",
|
||||
name: "Hanan Askarim",
|
||||
date: "06 Jan 2024 - 06 Jan 2024",
|
||||
time: "0",
|
||||
typeID: "2",
|
||||
typename: "Cuti",
|
||||
statusID: "2",
|
||||
reasonType: "Cuti Tahunan",
|
||||
reasonDescription: "Tidur",
|
||||
reasontypeID: "1",
|
||||
statusName: "Reject"),
|
||||
ApprovalModel(
|
||||
id: "5",
|
||||
name: "Stephen Kusumo",
|
||||
date: "06 Jan 2024",
|
||||
time: "17:00 - 19:00",
|
||||
typeID: "3",
|
||||
typename: "Lembur",
|
||||
statusID: "0",
|
||||
reasonType: "0",
|
||||
reasonDescription: "Menyelesaikan project Petty Cash",
|
||||
reasontypeID: "0",
|
||||
statusName: ""),
|
||||
ApprovalModel(
|
||||
id: "5",
|
||||
name: "Stephen Kusumo",
|
||||
date: "06 Jan 2024",
|
||||
time: "17:00 - 19:00",
|
||||
typeID: "3",
|
||||
typename: "Lembur",
|
||||
statusID: "1",
|
||||
reasonType: "0",
|
||||
reasonDescription: "Menyelesaikan project Petty Cash",
|
||||
reasontypeID: "0",
|
||||
statusName: "Approve"),
|
||||
ApprovalModel(
|
||||
id: "5",
|
||||
name: "Stephen Kusumo",
|
||||
date: "06 Jan 2024",
|
||||
time: "17:00 - 19:00",
|
||||
typeID: "3",
|
||||
typename: "Lembur",
|
||||
statusID: "2",
|
||||
reasonType: "0",
|
||||
reasonDescription: "Menyelesaikan project Petty Cash",
|
||||
reasontypeID: "0",
|
||||
statusName: "Reject"),
|
||||
];
|
||||
|
||||
List<ApprovalDetailModel> approvalDetailMockup = [
|
||||
ApprovalDetailModel(
|
||||
id: "1",
|
||||
name: "Alfianto Andy P",
|
||||
address:
|
||||
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
|
||||
date: "06 Feb 2024",
|
||||
time: "08:08",
|
||||
nip: "SS202308",
|
||||
lat: -7.539538,
|
||||
long: 110.798357,
|
||||
imagePath: "https://images4.alphacoders.com/262/262196.jpg",
|
||||
statusID: "1",
|
||||
statusName: "Approve"),
|
||||
ApprovalDetailModel(
|
||||
id: "2",
|
||||
name: "Alfianto Andy P",
|
||||
address:
|
||||
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
|
||||
date: "06 Feb 2024",
|
||||
time: "08:08",
|
||||
nip: "SS202308",
|
||||
lat: -7.539538,
|
||||
long: 110.798357,
|
||||
imagePath: "https://images3.alphacoders.com/147/147465.jpg",
|
||||
statusID: "2",
|
||||
statusName: "Reject"),
|
||||
ApprovalDetailModel(
|
||||
id: "3",
|
||||
name: "Alfianto Andy P",
|
||||
address:
|
||||
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
|
||||
date: "06 Feb 2024",
|
||||
time: "08:08",
|
||||
nip: "SS202308",
|
||||
lat: -7.539538,
|
||||
long: 110.798357,
|
||||
imagePath: "https://images4.alphacoders.com/146/146466.jpg",
|
||||
statusID: "0",
|
||||
statusName: ""),
|
||||
];
|
||||
159
lib/screen/approval/approval_screen.dart
Normal file
159
lib/screen/approval/approval_screen.dart
Normal file
@@ -0,0 +1,159 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/app/route.dart';
|
||||
import 'package:absensi_sas/model/approval_model.dart';
|
||||
|
||||
import 'package:absensi_sas/provider/approval_filter_provider.dart';
|
||||
import 'package:absensi_sas/screen/approval/approval_mockup.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_card_cuti.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_card_lembur.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_card_presensi.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_filter_chip_widget.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_filter_widget.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class ApprovalScreen extends HookConsumerWidget {
|
||||
const ApprovalScreen({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final approvalFilterTempProvider = ref.watch(approvalFilterProvider);
|
||||
final approvalTypeList = approvalFilterTempProvider.approvalTypeList;
|
||||
final scrollCtr = useScrollController();
|
||||
|
||||
final List<ApprovalModel> dataMockup = approvalCardList;
|
||||
|
||||
onTap(String tipeID) {
|
||||
if (tipeID == "1") {
|
||||
print("Tipe Id 1");
|
||||
Navigator.pushNamed(context, approvalDetailRoute);
|
||||
}
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
minimum: EdgeInsets.only(
|
||||
top: Constant.getActualYPhone(context: context, y: 30)),
|
||||
child: Scaffold(
|
||||
backgroundColor: Constant.textWhite,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
shadowColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
elevation: 0,
|
||||
leading: Container(
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: Icon(Icons.arrow_back_ios_new_rounded)),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
"Approval",
|
||||
style: Constant.title_screen(context: context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(context: context, x: 20)),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
color: Colors.white,
|
||||
child: Row(
|
||||
children: [
|
||||
ApprovalFilterWidget(),
|
||||
Expanded(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Container(
|
||||
width: constraints.maxWidth,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
...approvalTypeList.map(
|
||||
(e) => ApprovalFilterChipWidget(data: e))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(context: context, y: 27),
|
||||
),
|
||||
Expanded(child: Container(
|
||||
// color: Colors.red,
|
||||
child: LayoutBuilder(builder: (context, Constraints) {
|
||||
return Container(
|
||||
height: Constraints.maxWidth,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
print("Refresh");
|
||||
},
|
||||
child: ListView(
|
||||
controller: scrollCtr,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 28),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
"Februari 2024",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(left: 10),
|
||||
child: Divider(
|
||||
height: 10,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
...dataMockup.map((e) {
|
||||
switch (e.typeID) {
|
||||
case "1":
|
||||
return ApprovalCardPresensi(
|
||||
data: e,
|
||||
onTap: onTap,
|
||||
);
|
||||
case "2":
|
||||
return ApprovalCardCuti(
|
||||
data: e,
|
||||
);
|
||||
case "3":
|
||||
return ApprovalCardLembur(
|
||||
data: e,
|
||||
);
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
}).toList()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
200
lib/screen/approval/widget/approval_card_cuti.dart
Normal file
200
lib/screen/approval/widget/approval_card_cuti.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/model/approval_model.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart';
|
||||
import 'package:absensi_sas/widget/custom_button_approval.dart';
|
||||
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iconify_flutter/iconify_flutter.dart';
|
||||
import 'package:iconify_flutter/icons/heroicons.dart';
|
||||
|
||||
class ApprovalCardCuti extends StatelessWidget {
|
||||
const ApprovalCardCuti({super.key, required this.data});
|
||||
final ApprovalModel data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: Constant.getActualYPhone(context: context, y: 20)),
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
splashColor: Constant.bgOrange,
|
||||
overlayColor: MaterialStatePropertyAll(Constant.bgOrange),
|
||||
onTap: () {
|
||||
print("tapped");
|
||||
},
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.12),
|
||||
offset: Offset(0.0, 10), //(x,y)
|
||||
blurRadius: 4.0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
offset: Offset(0.0, 0), //(x,y)
|
||||
blurRadius: 2.0,
|
||||
),
|
||||
], color: Colors.white, borderRadius: BorderRadius.circular(16)),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
Constant.getActualXPhone(context: context, x: 20),
|
||||
vertical:
|
||||
Constant.getActualYPhone(context: context, y: 16)),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
data.name ?? "",
|
||||
style: Constant.body_16(context: context),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Constant.primaryOrange.withOpacity(0.2)),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 1)),
|
||||
child: Text(
|
||||
data.typename ?? "",
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(color: Constant.primaryOrange),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 8),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
EvaIcons.calendar,
|
||||
color: Constant.primaryOrange,
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
data.date ?? "",
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 8),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Icon(
|
||||
// ,
|
||||
// color: Constant.primaryOrange,
|
||||
// size: 14,
|
||||
// ),
|
||||
Iconify(
|
||||
Heroicons.chat_bubble_bottom_center_text_solid,
|
||||
color: Constant.primaryOrange,
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
|
||||
Flexible(
|
||||
child: Text(
|
||||
"${data.reasonType} ▪️ ${data.reasonDescription}",
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
Row(
|
||||
children: List.generate(
|
||||
1000 ~/ 10,
|
||||
(index) => Expanded(
|
||||
child: Container(
|
||||
color: index % 2 == 0
|
||||
? Colors.transparent
|
||||
: Constant.textLightGrey
|
||||
.withOpacity(0.24),
|
||||
height: 1,
|
||||
),
|
||||
)),
|
||||
),
|
||||
if (data.statusID != "1" && data.statusID != "2")
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
if (data.statusID != "1" && data.statusID != "2")
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomButtonWhite(
|
||||
btnText: "Reject",
|
||||
onPressed: () {
|
||||
ApprovalDialogConfirmation(
|
||||
context: context,
|
||||
confirmFunc: () {},
|
||||
textInformation: "Menolak ",
|
||||
staffName: data.name ?? "",
|
||||
typename: data.typename ?? "");
|
||||
}),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
),
|
||||
Expanded(
|
||||
child: CustomButtonOrange(
|
||||
btnText: "Approve",
|
||||
onPressed: () {
|
||||
ApprovalDialogConfirmation(
|
||||
context: context,
|
||||
confirmFunc: () {},
|
||||
textInformation: "Menyetujui ",
|
||||
staffName: data.name ?? "",
|
||||
typename: data.typename ?? "");
|
||||
}),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
AprrovalCardStatusWidget(
|
||||
statusID: data.statusID ?? "",
|
||||
statusName: data.statusName ?? '',
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
200
lib/screen/approval/widget/approval_card_lembur.dart
Normal file
200
lib/screen/approval/widget/approval_card_lembur.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/model/approval_model.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart';
|
||||
import 'package:absensi_sas/widget/custom_button_approval.dart';
|
||||
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iconify_flutter/iconify_flutter.dart';
|
||||
import 'package:iconify_flutter/icons/heroicons.dart';
|
||||
|
||||
class ApprovalCardLembur extends StatelessWidget {
|
||||
const ApprovalCardLembur({super.key, required this.data});
|
||||
final ApprovalModel data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: Constant.getActualYPhone(context: context, y: 20)),
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
splashColor: Constant.bgOrange,
|
||||
overlayColor: MaterialStatePropertyAll(Constant.bgOrange),
|
||||
onTap: () {
|
||||
print("tapped");
|
||||
},
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.12),
|
||||
offset: Offset(0.0, 10), //(x,y)
|
||||
blurRadius: 4.0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
offset: Offset(0.0, 0), //(x,y)
|
||||
blurRadius: 2.0,
|
||||
),
|
||||
], color: Colors.white, borderRadius: BorderRadius.circular(16)),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
Constant.getActualXPhone(context: context, x: 20),
|
||||
vertical:
|
||||
Constant.getActualYPhone(context: context, y: 16)),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
data.name ?? "",
|
||||
style: Constant.body_16(context: context),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Constant.bgSecondaryBlue),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 1)),
|
||||
child: Text(
|
||||
data.typename ?? "",
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(color: Constant.secondaryBlue),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 8),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
EvaIcons.calendar,
|
||||
color: Constant.textOrange,
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
data.date ?? "",
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 8),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Icon(
|
||||
// ,
|
||||
// color: Constant.textOrange,
|
||||
// size: 14,
|
||||
// ),
|
||||
Iconify(
|
||||
Heroicons.chat_bubble_bottom_center_text_solid,
|
||||
color: Constant.textOrange,
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
|
||||
Flexible(
|
||||
child: Text(
|
||||
data.reasonDescription ?? '',
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
Row(
|
||||
children: List.generate(
|
||||
1000 ~/ 10,
|
||||
(index) => Expanded(
|
||||
child: Container(
|
||||
color: index % 2 == 0
|
||||
? Colors.transparent
|
||||
: Constant.textLightGrey
|
||||
.withOpacity(0.24),
|
||||
height: 1,
|
||||
),
|
||||
)),
|
||||
),
|
||||
if (data.statusID != "1" && data.statusID != "2")
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
if (data.statusID != "1" && data.statusID != "2")
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomButtonWhite(
|
||||
btnText: "Reject",
|
||||
onPressed: () {
|
||||
ApprovalDialogConfirmation(
|
||||
context: context,
|
||||
confirmFunc: () {},
|
||||
textInformation: "Menolak ",
|
||||
staffName: data.name ?? "",
|
||||
typename: data.typename ?? "");
|
||||
}),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
),
|
||||
Expanded(
|
||||
child: CustomButtonOrange(
|
||||
btnText: "Approve",
|
||||
onPressed: () {
|
||||
ApprovalDialogConfirmation(
|
||||
context: context,
|
||||
confirmFunc: () {},
|
||||
textInformation: "Menyetujui ",
|
||||
staffName: data.name ?? "",
|
||||
typename: data.typename ?? "");
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AprrovalCardStatusWidget(
|
||||
statusID: data.statusID ?? "",
|
||||
statusName: data.statusName ?? '',
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
203
lib/screen/approval/widget/approval_card_presensi.dart
Normal file
203
lib/screen/approval/widget/approval_card_presensi.dart
Normal file
@@ -0,0 +1,203 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/model/approval_model.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart';
|
||||
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ApprovalCardPresensi extends StatelessWidget {
|
||||
const ApprovalCardPresensi(
|
||||
{super.key, required this.data, required this.onTap});
|
||||
final ApprovalModel data;
|
||||
final Function onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: Constant.getActualYPhone(context: context, y: 20)),
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
splashColor: Constant.bgOrange,
|
||||
// hoverColor: Colors.red,
|
||||
overlayColor: MaterialStatePropertyAll(Constant.bgOrange),
|
||||
|
||||
onTap: () {
|
||||
onTap(data.typeID);
|
||||
},
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.12),
|
||||
offset: Offset(0.0, 10), //(x,y)
|
||||
blurRadius: 4.0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
offset: Offset(0.0, 0), //(x,y)
|
||||
blurRadius: 2.0,
|
||||
),
|
||||
], color: Colors.white, borderRadius: BorderRadius.circular(16)),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
Constant.getActualXPhone(context: context, x: 20),
|
||||
vertical:
|
||||
Constant.getActualYPhone(context: context, y: 16)),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
data.name ?? "",
|
||||
style: Constant.body_16(context: context),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Constant.bgBlue),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 1)),
|
||||
child: Text(
|
||||
data.typename ?? "",
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(color: Constant.primaryBlue),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 8),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
EvaIcons.calendar,
|
||||
color: Constant.primaryOrange,
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
Text(
|
||||
data.date ?? "",
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 20),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
EvaIcons.clock,
|
||||
color: Constant.primaryOrange,
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
Text(
|
||||
data.time ?? "",
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 20),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'images/finger_tap_orange_botnav.png',
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 14),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
Text(
|
||||
data.presensiTypeName ?? "",
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 8),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
EvaIcons.pin,
|
||||
color: Constant.primaryOrange,
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
data.address ?? "",
|
||||
style: Constant.titleH2_400_14(context: context)
|
||||
.copyWith(color: Constant.textDarkGrey),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (data.statusID == "1" || data.statusID == "2")
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
if (data.statusID == "1" || data.statusID == "2")
|
||||
Row(
|
||||
children: List.generate(
|
||||
1000 ~/ 10,
|
||||
(index) => Expanded(
|
||||
child: Container(
|
||||
color: index % 2 == 0
|
||||
? Colors.transparent
|
||||
: Constant.textLightGrey
|
||||
.withOpacity(0.24),
|
||||
height: 1,
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AprrovalCardStatusWidget(
|
||||
statusID: data.statusID ?? "",
|
||||
statusName: data.statusName ?? "",
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
52
lib/screen/approval/widget/approval_card_status_widget.dart
Normal file
52
lib/screen/approval/widget/approval_card_status_widget.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AprrovalCardStatusWidget extends StatelessWidget {
|
||||
const AprrovalCardStatusWidget(
|
||||
{super.key, required this.statusID, required this.statusName});
|
||||
final String statusID;
|
||||
final String statusName;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (statusID) {
|
||||
case "1":
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Constant.bgGreen,
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16))),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(context: context, x: 20),
|
||||
vertical: Constant.getActualYPhone(context: context, y: 16)),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Text(
|
||||
statusName,
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(color: Constant.primaryGreen),
|
||||
),
|
||||
);
|
||||
case "2":
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Constant.bgRed,
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16))),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(context: context, x: 20),
|
||||
vertical: Constant.getActualYPhone(context: context, y: 16)),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Text(
|
||||
statusName,
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(color: Constant.primaryRed),
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
lib/screen/approval/widget/approval_dialog_confirmation.dart
Normal file
75
lib/screen/approval/widget/approval_dialog_confirmation.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/widget/custom_button_approval.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Future ApprovalDialogConfirmation(
|
||||
{required BuildContext context,
|
||||
required String typename,
|
||||
required String textInformation,
|
||||
required String staffName,
|
||||
required Function() confirmFunc}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerRight,
|
||||
end: Alignment.centerLeft,
|
||||
colors: [
|
||||
Color(0xff161C24).withOpacity(0.91),
|
||||
Color(0xff161C24).withOpacity(0.3)
|
||||
])),
|
||||
child: AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
shadowColor: Colors.white,
|
||||
title: Text(
|
||||
'Konfirmasi',
|
||||
style: Constant.titleH1_700_18(context: context),
|
||||
),
|
||||
content: Text.rich(TextSpan(
|
||||
text: "Apakah anda yakin untuk ",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w400),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: textInformation,
|
||||
style: Constant.body_16(context: context)),
|
||||
TextSpan(
|
||||
text: "${typename} ",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w400)),
|
||||
TextSpan(
|
||||
text: "${staffName} ?",
|
||||
style: Constant.body_16(context: context))
|
||||
])),
|
||||
actions: <Widget>[
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: CustomButtonOrange(
|
||||
btnText: "Yakin",
|
||||
onPressed: confirmFunc,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 3,
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: CustomButtonWhite(
|
||||
btnText: "Batal",
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
71
lib/screen/approval/widget/approval_filter_chip_widget.dart
Normal file
71
lib/screen/approval/widget/approval_filter_chip_widget.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/model/approval_type_model.dart';
|
||||
import 'package:absensi_sas/provider/approval_filter_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class ApprovalFilterChipWidget extends HookConsumerWidget {
|
||||
const ApprovalFilterChipWidget({
|
||||
super.key,
|
||||
required this.data,
|
||||
});
|
||||
final ApprovalTypeModel data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final currentApprovalFilter = ref.watch(approvalFilterProvider);
|
||||
handleOnTap(String value) {
|
||||
var tmp = currentApprovalFilter.selectedApprovalType.toList();
|
||||
var cek = tmp.contains(value);
|
||||
if (cek) {
|
||||
tmp.remove(value);
|
||||
} else {
|
||||
tmp.add(value);
|
||||
}
|
||||
ref.read(approvalFilterProvider.notifier).state = ApprovalFilterProvider(
|
||||
endDate: currentApprovalFilter.endDate,
|
||||
keyword: currentApprovalFilter.keyword,
|
||||
selectedApprovalType: tmp,
|
||||
startDate: currentApprovalFilter.startDate);
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
right: Constant.getActualXPhone(context: context, x: 16)),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
handleOnTap(data.value!);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
elevation: MaterialStatePropertyAll(0),
|
||||
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: currentApprovalFilter.selectedApprovalType
|
||||
.contains(data.value)
|
||||
? Constant.textOrange
|
||||
: Colors.grey.shade300,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16))),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (currentApprovalFilter.selectedApprovalType
|
||||
.contains(data.value) ==
|
||||
true) {
|
||||
return Constant.textOrange.withOpacity(0.1);
|
||||
}
|
||||
return Colors.white;
|
||||
}),
|
||||
overlayColor:
|
||||
MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)),
|
||||
foregroundColor: MaterialStatePropertyAll(Colors.white),
|
||||
shadowColor: MaterialStatePropertyAll(Colors.white),
|
||||
surfaceTintColor: MaterialStatePropertyAll(Colors.white)),
|
||||
child: Text(data.name ?? "",
|
||||
style: Constant.subtitle_600_14(context: context).copyWith(
|
||||
color: currentApprovalFilter.selectedApprovalType
|
||||
.contains(data.value)
|
||||
? Constant.textOrange
|
||||
: Colors.black)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
lib/screen/approval/widget/approval_filter_widget.dart
Normal file
69
lib/screen/approval/widget/approval_filter_widget.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
|
||||
import 'package:absensi_sas/screen/approval/widget/filter_bottom_sheet.dart';
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class ApprovalFilterWidget extends HookConsumerWidget {
|
||||
const ApprovalFilterWidget({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
right: Constant.getActualXPhone(context: context, x: 16)),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(32),
|
||||
topRight: Radius.circular(32))),
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
enableDrag: true,
|
||||
useSafeArea: true,
|
||||
// useRootNavigator: true,
|
||||
builder: (context) {
|
||||
return FilterBottomSheet();
|
||||
},
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
elevation: MaterialStatePropertyAll(0),
|
||||
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16))),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Colors.white;
|
||||
}),
|
||||
overlayColor:
|
||||
MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)),
|
||||
foregroundColor: MaterialStatePropertyAll(Colors.white),
|
||||
shadowColor: MaterialStatePropertyAll(Colors.white),
|
||||
surfaceTintColor: MaterialStatePropertyAll(Colors.white)),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FluentIcons.options_24_filled,
|
||||
size: 20,
|
||||
color: Colors.black,
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
"Filter",
|
||||
style: Constant.subtitle_600_14(context: context)
|
||||
.copyWith(color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
534
lib/screen/approval/widget/filter_bottom_sheet.dart
Normal file
534
lib/screen/approval/widget/filter_bottom_sheet.dart
Normal file
@@ -0,0 +1,534 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/model/approval_type_model.dart';
|
||||
import 'package:absensi_sas/provider/approval_filter_provider.dart';
|
||||
import 'package:absensi_sas/widget/custom_button_approval.dart';
|
||||
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class FilterBottomSheet extends HookConsumerWidget {
|
||||
const FilterBottomSheet({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final currApprovalTypeState = ref.read(approvalFilterProvider);
|
||||
final approvalTypeList = [
|
||||
ApprovalTypeModel(id: "1", name: "Presensi", isActive: false, value: "p"),
|
||||
ApprovalTypeModel(id: "2", name: "Cuti", isActive: true, value: "c"),
|
||||
ApprovalTypeModel(id: "3", name: "Lembur", isActive: false, value: "l"),
|
||||
];
|
||||
final staffFocuseNode = useFocusNode();
|
||||
final startDateFocusNode = useFocusNode();
|
||||
final endDateFocusNode = useFocusNode();
|
||||
final staffCtr =
|
||||
useTextEditingController(text: currApprovalTypeState.keyword);
|
||||
final startDateCtr = useTextEditingController(
|
||||
text: DateFormat('dd-MM-yyyy').format(currApprovalTypeState.startDate));
|
||||
final startDateState = useState(currApprovalTypeState.startDate);
|
||||
final endDateCtr = useTextEditingController(
|
||||
text: DateFormat('dd-MM-yyyy').format(currApprovalTypeState.endDate));
|
||||
final endDateState = useState(currApprovalTypeState.endDate);
|
||||
final dateKey = useState(0);
|
||||
|
||||
final selectedApprovalTypeState = useState<List<String>>(
|
||||
currApprovalTypeState.selectedApprovalType.toList());
|
||||
final checkboxKey = useState(1000);
|
||||
|
||||
selectApprovalTypeFunction(String value) {
|
||||
var tmp = selectedApprovalTypeState.value;
|
||||
var cek = tmp.contains(value);
|
||||
if (cek) {
|
||||
tmp.remove(value);
|
||||
} else {
|
||||
tmp.add(value);
|
||||
}
|
||||
selectedApprovalTypeState.value = tmp;
|
||||
checkboxKey.value = checkboxKey.value + 1;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom * 0.75),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(32), topRight: Radius.circular(32))),
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(context: context, x: 24)),
|
||||
height: MediaQuery.of(context).size.height * 0.75,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.1,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Filter",
|
||||
style: Constant.titleH1_700(context: context),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: Icon(Icons.close))
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.55,
|
||||
// color: Colors.green,
|
||||
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Staff",
|
||||
style: Constant.titleH1_700_18(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 16),
|
||||
),
|
||||
TextFormField(
|
||||
controller: staffCtr,
|
||||
focusNode: staffFocuseNode,
|
||||
style: Constant.titleH1_500_18(context: context),
|
||||
decoration: InputDecoration(
|
||||
focusColor: Constant.textOrange,
|
||||
// hoverColor: Constant.textOrange,
|
||||
suffixIcon: Icon(
|
||||
EvaIcons.searchOutline,
|
||||
color: staffFocuseNode.hasFocus
|
||||
? Constant.textOrange
|
||||
: Color(0xff212B36),
|
||||
),
|
||||
label: Text(
|
||||
"Cari Staff",
|
||||
style: Constant.titleH1_500_18(context: context)
|
||||
.copyWith(
|
||||
color: staffFocuseNode.hasFocus
|
||||
? Constant.textOrange
|
||||
: Colors.grey.shade400),
|
||||
),
|
||||
hintText: "Cari Staff",
|
||||
isDense: false,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 16),
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 14)),
|
||||
hintStyle:
|
||||
Constant.titleH1_500_18(context: context),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Constant.textOrange),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.grey.shade500, width: 1),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
border: OutlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Colors.grey.shade500),
|
||||
borderRadius: BorderRadius.circular(8))),
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Tanggal Awal",
|
||||
style: Constant.titleH1_700_18(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 20),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Tanggal Akhir",
|
||||
style: Constant.titleH1_700_18(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
key: ValueKey(dateKey.value),
|
||||
controller: startDateCtr,
|
||||
focusNode: startDateFocusNode,
|
||||
readOnly: true,
|
||||
style: Constant.titleH1_500_18(context: context),
|
||||
decoration: InputDecoration(
|
||||
focusColor: Constant.textOrange,
|
||||
// hoverColor: Constant.textOrange,
|
||||
suffixIcon: Icon(
|
||||
EvaIcons.calendar,
|
||||
color: startDateFocusNode.hasFocus
|
||||
? Constant.textOrange
|
||||
: Color(0xff212B36),
|
||||
),
|
||||
label: Text(
|
||||
"Tanggal Awal",
|
||||
style: Constant.titleH1_500_18(
|
||||
context: context)
|
||||
.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: startDateFocusNode.hasFocus
|
||||
? Constant.textOrange
|
||||
: Colors.grey.shade400),
|
||||
),
|
||||
hintText: "Tanggal Awal",
|
||||
isDense: false,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 16),
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 14)),
|
||||
hintStyle:
|
||||
Constant.titleH1_500_18(context: context)
|
||||
.copyWith(
|
||||
overflow: TextOverflow.ellipsis),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Constant.textOrange),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.grey.shade500,
|
||||
width: 1),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.grey.shade500),
|
||||
borderRadius: BorderRadius.circular(8))),
|
||||
onTap: () async {
|
||||
startDateFocusNode.requestFocus();
|
||||
dateKey.value = dateKey.value + 1;
|
||||
DateTime? pickedDate = await showDatePicker(
|
||||
helpText: "Tanggal Awal",
|
||||
// locale: Locale('id', 'ID'),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData().copyWith(
|
||||
datePickerTheme:
|
||||
DatePickerThemeData(
|
||||
dayBackgroundColor:
|
||||
MaterialStateProperty
|
||||
.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(
|
||||
MaterialState
|
||||
.selected)) {
|
||||
return Constant
|
||||
.textOrange;
|
||||
}
|
||||
return Colors.white;
|
||||
}),
|
||||
todayBackgroundColor:
|
||||
MaterialStateProperty
|
||||
.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(
|
||||
MaterialState
|
||||
.selected)) {
|
||||
return Constant
|
||||
.textOrange;
|
||||
}
|
||||
return Colors.white;
|
||||
}),
|
||||
todayBorder: BorderSide(
|
||||
style:
|
||||
BorderStyle.solid,
|
||||
color: Constant
|
||||
.textOrange,
|
||||
width: 1),
|
||||
headerBackgroundColor:
|
||||
Constant.textOrange,
|
||||
headerForegroundColor:
|
||||
Colors.white,
|
||||
surfaceTintColor:
|
||||
Colors.white,
|
||||
backgroundColor:
|
||||
Colors.white),
|
||||
primaryColor: Constant.textOrange,
|
||||
primaryColorDark:
|
||||
Constant.textOrange,
|
||||
primaryColorLight:
|
||||
Constant.textOrange),
|
||||
child: child!);
|
||||
},
|
||||
confirmText: "OK",
|
||||
cancelText: "Batal",
|
||||
context: context,
|
||||
initialDate: startDateState.value,
|
||||
firstDate: DateTime(1950),
|
||||
initialEntryMode:
|
||||
DatePickerEntryMode.calendarOnly,
|
||||
lastDate: DateTime(2100));
|
||||
|
||||
if (pickedDate != null) {
|
||||
print(
|
||||
pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
|
||||
String formattedDate =
|
||||
DateFormat('dd-MM-yyyy')
|
||||
.format(pickedDate);
|
||||
print(
|
||||
formattedDate); //formatted date output using intl package => 2021-03-16
|
||||
startDateCtr.text =
|
||||
formattedDate; //set output date to TextField value.
|
||||
startDateState.value = pickedDate;
|
||||
startDateFocusNode.requestFocus();
|
||||
} else {}
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 20),
|
||||
),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
key: ValueKey(dateKey.value),
|
||||
controller: endDateCtr,
|
||||
focusNode: endDateFocusNode,
|
||||
readOnly: true,
|
||||
style: Constant.titleH1_500_18(context: context),
|
||||
decoration: InputDecoration(
|
||||
focusColor: Constant.textOrange,
|
||||
// hoverColor: Constant.textOrange,
|
||||
suffixIcon: Icon(
|
||||
EvaIcons.calendar,
|
||||
color: endDateFocusNode.hasFocus
|
||||
? Constant.textOrange
|
||||
: Color(0xff212B36),
|
||||
),
|
||||
label: Text(
|
||||
"Tanggal Akhir",
|
||||
style: Constant.titleH1_500_18(
|
||||
context: context)
|
||||
.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: endDateFocusNode.hasFocus
|
||||
? Constant.textOrange
|
||||
: Colors.grey.shade400),
|
||||
),
|
||||
hintText: "Tanggal Akhir",
|
||||
isDense: false,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 16),
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 14)),
|
||||
hintStyle:
|
||||
Constant.titleH1_500_18(context: context)
|
||||
.copyWith(
|
||||
overflow: TextOverflow.ellipsis),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Constant.textOrange),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.grey.shade500,
|
||||
width: 1),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.grey.shade500),
|
||||
borderRadius: BorderRadius.circular(8))),
|
||||
onTap: () async {
|
||||
endDateFocusNode.requestFocus();
|
||||
dateKey.value = dateKey.value + 1;
|
||||
DateTime? pickedDate = await showDatePicker(
|
||||
helpText: "Tanggal Akhir",
|
||||
// locale: Locale('id', 'ID'),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData().copyWith(
|
||||
datePickerTheme:
|
||||
DatePickerThemeData(
|
||||
dayBackgroundColor:
|
||||
MaterialStateProperty
|
||||
.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(
|
||||
MaterialState
|
||||
.selected)) {
|
||||
return Constant
|
||||
.textOrange;
|
||||
}
|
||||
return Colors.white;
|
||||
}),
|
||||
todayBackgroundColor:
|
||||
MaterialStateProperty
|
||||
.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(
|
||||
MaterialState
|
||||
.selected)) {
|
||||
return Constant
|
||||
.textOrange;
|
||||
}
|
||||
return Colors.white;
|
||||
}),
|
||||
todayBorder: BorderSide(
|
||||
style:
|
||||
BorderStyle.solid,
|
||||
color: Constant
|
||||
.textOrange,
|
||||
width: 1),
|
||||
headerBackgroundColor:
|
||||
Constant.textOrange,
|
||||
headerForegroundColor:
|
||||
Colors.white,
|
||||
surfaceTintColor:
|
||||
Colors.white,
|
||||
backgroundColor:
|
||||
Colors.white),
|
||||
primaryColor: Constant.textOrange,
|
||||
primaryColorDark:
|
||||
Constant.textOrange,
|
||||
primaryColorLight:
|
||||
Constant.textOrange),
|
||||
child: child!);
|
||||
},
|
||||
confirmText: "OK",
|
||||
cancelText: "Batal",
|
||||
context: context,
|
||||
initialDate: startDateState.value,
|
||||
firstDate: DateTime(1950),
|
||||
initialEntryMode:
|
||||
DatePickerEntryMode.calendarOnly,
|
||||
lastDate: DateTime(2100));
|
||||
|
||||
if (pickedDate != null) {
|
||||
print(
|
||||
pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
|
||||
String formattedDate =
|
||||
DateFormat('dd-MM-yyyy')
|
||||
.format(pickedDate);
|
||||
print(
|
||||
formattedDate); //formatted date output using intl package => 2021-03-16
|
||||
endDateCtr.text =
|
||||
formattedDate; //set output date to TextField value.
|
||||
endDateState.value = pickedDate;
|
||||
endDateFocusNode.requestFocus();
|
||||
} else {}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 16),
|
||||
),
|
||||
// Text(
|
||||
// ref
|
||||
// .watch(approvalFilterProvider)
|
||||
// .selectedApprovalType
|
||||
// .toString(),
|
||||
// style: Constant.titleH1_700_18(context: context),
|
||||
// ),
|
||||
Text(
|
||||
"Approval",
|
||||
style: Constant.titleH1_700_18(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 16),
|
||||
),
|
||||
...approvalTypeList.map(
|
||||
(e) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
value: selectedApprovalTypeState.value
|
||||
.contains(e.value),
|
||||
onChanged: (val) {
|
||||
selectApprovalTypeFunction(e.value!);
|
||||
},
|
||||
),
|
||||
Text(
|
||||
e.name ?? "",
|
||||
style: Constant.titleH1_700_18(context: context)
|
||||
.copyWith(fontWeight: FontWeight.normal),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.1,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomButtonWhite(
|
||||
btnText: "Reset",
|
||||
onPressed: () {
|
||||
ref.read(approvalFilterProvider.notifier).state =
|
||||
ApprovalFilterProvider(
|
||||
startDate: DateTime.now(),
|
||||
endDate: DateTime.now(),
|
||||
keyword: "",
|
||||
selectedApprovalType: []);
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(context: context, x: 24),
|
||||
),
|
||||
Expanded(
|
||||
child: CustomButtonOrange(
|
||||
btnText: "Simpan",
|
||||
onPressed: () {
|
||||
ref.read(approvalFilterProvider.notifier).state =
|
||||
ApprovalFilterProvider(
|
||||
endDate: endDateState.value,
|
||||
keyword: staffCtr.text,
|
||||
selectedApprovalType:
|
||||
selectedApprovalTypeState.value,
|
||||
startDate: startDateState.value);
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
368
lib/screen/approval_detail/approval_detail_screen.dart
Normal file
368
lib/screen/approval_detail/approval_detail_screen.dart
Normal file
@@ -0,0 +1,368 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/model/approval_detail_model.dart';
|
||||
import 'package:absensi_sas/screen/approval/approval_mockup.dart';
|
||||
import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart';
|
||||
import 'package:absensi_sas/widget/custom_button_approval.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class ApprovalDetailScreen extends HookConsumerWidget {
|
||||
const ApprovalDetailScreen({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final loading = useState(true);
|
||||
final data = useState<ApprovalDetailModel>(
|
||||
ApprovalDetailModel(imagePath: "", lat: 0, long: 0));
|
||||
init() {
|
||||
loading.value = true;
|
||||
var randIdx = Random().nextInt(approvalDetailMockup.length);
|
||||
var tmp = approvalDetailMockup[randIdx];
|
||||
Timer(Duration(seconds: 2), () {
|
||||
data.value = tmp;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
init();
|
||||
return;
|
||||
});
|
||||
return () {};
|
||||
}, []);
|
||||
return SafeArea(
|
||||
minimum: EdgeInsets.only(
|
||||
top: Constant.getActualYPhone(context: context, y: 30)),
|
||||
child: Scaffold(
|
||||
backgroundColor: Constant.textWhite,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
shadowColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
elevation: 0,
|
||||
leading: Container(
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: Icon(Icons.arrow_back_ios_new_rounded)),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
"Detail",
|
||||
style: Constant.title_screen(context: context),
|
||||
),
|
||||
),
|
||||
body: loading.value
|
||||
? Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: Container(
|
||||
color: Colors.white,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
Constant.getActualXPhone(context: context, x: 20)),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
init();
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 36),
|
||||
),
|
||||
if (data.value.statusID == "1" ||
|
||||
data.value.statusID == "2")
|
||||
Row(
|
||||
children: [
|
||||
Spacer(),
|
||||
if (data.value.statusID == "1")
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Constant.bgGreen),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 2)),
|
||||
child: Text(
|
||||
"Approve",
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(
|
||||
color: Constant.primaryGreen),
|
||||
),
|
||||
),
|
||||
if (data.value.statusID == "2")
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Constant.bgRed),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constant.getActualXPhone(
|
||||
context: context, x: 8),
|
||||
vertical: Constant.getActualYPhone(
|
||||
context: context, y: 2)),
|
||||
child: Text(
|
||||
"Reject",
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(color: Constant.primaryRed),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (data.value.statusID == "1" ||
|
||||
data.value.statusID == "2")
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 8),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text("Nama : ",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
color: Constant.textDarkGrey,
|
||||
fontWeight: FontWeight.w400)),
|
||||
),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: Text(data.value.name ?? "",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w400)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text("NIP : ",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
color: Constant.textDarkGrey,
|
||||
fontWeight: FontWeight.w400)),
|
||||
),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: Text(data.value.nip ?? "",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w400)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text("Tanggal : ",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
color: Constant.textDarkGrey,
|
||||
fontWeight: FontWeight.w400)),
|
||||
),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: Text(data.value.date ?? "",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w400)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text("Jam : ",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
color: Constant.textDarkGrey,
|
||||
fontWeight: FontWeight.w400)),
|
||||
),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: Text(data.value.time ?? "",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w400)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text("Alamat : ",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
color: Constant.textDarkGrey,
|
||||
fontWeight: FontWeight.w400)),
|
||||
),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: Text(data.value.address ?? "",
|
||||
style: Constant.body_16(context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w400)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 16),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 400),
|
||||
child: FlutterMap(
|
||||
options: MapOptions(
|
||||
center: LatLng(data.value.lat ?? 0,
|
||||
data.value.long ?? 0),
|
||||
zoom: 16,
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'com.example.app',
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: LatLng(-7.539538, 110.798357),
|
||||
child: const Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.red,
|
||||
size: 50,
|
||||
),
|
||||
)
|
||||
// Marker(
|
||||
// point: LatLng(lat, long),
|
||||
// width: 100,
|
||||
// height: 100,
|
||||
// builder: (context) => const Icon(
|
||||
// Icons.location_on,
|
||||
// color: Colors.red,
|
||||
// size: 50,
|
||||
// )),
|
||||
],
|
||||
),
|
||||
]),
|
||||
),
|
||||
if (data.value.imagePath != "" &&
|
||||
data.value.imagePath != null)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 16, bottom: 48),
|
||||
child: Image.network(
|
||||
data.value.imagePath!,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Text(
|
||||
"Error load image : ${error.toString()}",
|
||||
style: Constant.body_12(context: context)
|
||||
.copyWith(color: Constant.primaryRed),
|
||||
);
|
||||
},
|
||||
loadingBuilder:
|
||||
(context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Container(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(loadingProgress.expectedTotalBytes !=
|
||||
null
|
||||
? ((loadingProgress.cumulativeBytesLoaded /
|
||||
loadingProgress
|
||||
.expectedTotalBytes!) *
|
||||
100)
|
||||
.round()
|
||||
.toString() +
|
||||
" %"
|
||||
: ""),
|
||||
LinearProgressIndicator(
|
||||
value: loadingProgress
|
||||
.expectedTotalBytes !=
|
||||
null
|
||||
? loadingProgress
|
||||
.cumulativeBytesLoaded /
|
||||
loadingProgress
|
||||
.expectedTotalBytes!
|
||||
: null,
|
||||
),
|
||||
],
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
if (data.value.statusID != "1" &&
|
||||
data.value.statusID != "2")
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomButtonWhite(
|
||||
btnText: "Reject",
|
||||
onPressed: () {
|
||||
ApprovalDialogConfirmation(
|
||||
context: context,
|
||||
typename: "presensi ",
|
||||
textInformation: "menolak ",
|
||||
staffName: data.value.name ?? "",
|
||||
confirmFunc: () {});
|
||||
}),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
),
|
||||
Expanded(
|
||||
child: CustomButtonOrange(
|
||||
btnText: "Approve",
|
||||
onPressed: () {
|
||||
ApprovalDialogConfirmation(
|
||||
context: context,
|
||||
typename: "presensi ",
|
||||
textInformation: "menyetujui ",
|
||||
staffName: data.value.name ?? "",
|
||||
confirmFunc: () {});
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
75
lib/screen/home/googleapis_provider.dart
Normal file
75
lib/screen/home/googleapis_provider.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
import 'package:absensi_sas/repository/googleapis_repository.dart';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../provider/dio_provider.dart';
|
||||
import '../../provider/graphql_provider.dart';
|
||||
import '../../repository/base_repository.dart';
|
||||
|
||||
abstract class GoogleApisProviderState extends Equatable {
|
||||
final DateTime date;
|
||||
const GoogleApisProviderState(this.date);
|
||||
@override
|
||||
List<Object?> get props => [date];
|
||||
}
|
||||
|
||||
class GoogleApisProviderStateInit extends GoogleApisProviderState {
|
||||
GoogleApisProviderStateInit() : super(DateTime.now());
|
||||
}
|
||||
|
||||
class GoogleApisProviderStateLoading extends GoogleApisProviderState {
|
||||
GoogleApisProviderStateLoading() : super(DateTime.now());
|
||||
}
|
||||
|
||||
class GoogleApisProviderStateError extends GoogleApisProviderState {
|
||||
final String message;
|
||||
GoogleApisProviderStateError({
|
||||
required this.message,
|
||||
}) : super(DateTime.now());
|
||||
}
|
||||
|
||||
class GoogleApisProviderStateDone extends GoogleApisProviderState {
|
||||
final String model;
|
||||
GoogleApisProviderStateDone({
|
||||
required this.model,
|
||||
}) : super(DateTime.now());
|
||||
}
|
||||
|
||||
//notifier
|
||||
class GoogleApisProviderNotifier extends StateNotifier<GoogleApisProviderState> {
|
||||
final Ref ref;
|
||||
GoogleApisProviderNotifier({
|
||||
required this.ref,
|
||||
}) : super(GoogleApisProviderStateInit());
|
||||
|
||||
void googleApisProvider(
|
||||
String latitude,
|
||||
String longitude,
|
||||
) async {
|
||||
try {
|
||||
state = GoogleApisProviderStateLoading();
|
||||
final graphql = ref.read(graphqlProvider(''));
|
||||
final dio = ref.read(dioProvider);
|
||||
final resp = await GoogleApisRepository(graphql: graphql, dio: dio)
|
||||
.getAddressFromCoordinates(
|
||||
latitude,
|
||||
longitude
|
||||
);
|
||||
state = GoogleApisProviderStateDone(model: resp);
|
||||
} catch (e) {
|
||||
if (e is BaseRepositoryException) {
|
||||
print(e.message);
|
||||
state = GoogleApisProviderStateError(message: e.message ?? "");
|
||||
} else {
|
||||
state = GoogleApisProviderStateError(message: e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// provider
|
||||
final googleApisProviderProvider =
|
||||
StateNotifierProvider<GoogleApisProviderNotifier, GoogleApisProviderState>(
|
||||
(ref) => GoogleApisProviderNotifier(ref: ref),
|
||||
);
|
||||
@@ -1,13 +1,19 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:absensi_sas/provider/current_rekap_kehadiran_home_provider.dart';
|
||||
import 'package:absensi_sas/widget/custom_google_map_widget.dart';
|
||||
import 'package:dart_nominatim/dart_nominatim.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
// import 'package:geocoding/geocoding.dart';
|
||||
// import 'package:geolocator/geolocator.dart';
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:location/location.dart';
|
||||
// import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
|
||||
import '../../app/googleapis_location.dart';
|
||||
import '../../app/route.dart';
|
||||
import '../../provider/current_check_distance_provider.dart';
|
||||
import '../../provider/current_check_jam_presensi_provider.dart';
|
||||
@@ -41,6 +47,8 @@ class HomeScreen extends HookConsumerWidget {
|
||||
// ref.watch(currentUserGoogleProvider);
|
||||
|
||||
final googleSignIn = ref.watch(googleSignInProvider);
|
||||
Location location = new Location();
|
||||
LocationData _locationData;
|
||||
|
||||
useEffect(() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
@@ -94,9 +102,10 @@ class HomeScreen extends HookConsumerWidget {
|
||||
try {
|
||||
isLoadingProsesCheckDistance.value = true;
|
||||
// Mendapatkan posisi pengguna
|
||||
LocationPermission permission = await Geolocator.requestPermission();
|
||||
// LocationPermission permission = await Geolocator.requestPermission();
|
||||
final permission = await location.hasPermission();
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
if (permission == PermissionStatus.denied) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
SanckbarWidget(context, 'Izin lokasi ditolak', snackbarType.error);
|
||||
// Handle jika pengguna menolak izin lokasi
|
||||
@@ -104,25 +113,29 @@ class HomeScreen extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
// Position position = await Geolocator.getCurrentPosition(
|
||||
// desiredAccuracy: LocationAccuracy.high);
|
||||
|
||||
location.changeSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
interval: 1000,
|
||||
distanceFilter: 5,
|
||||
);
|
||||
_locationData = await location.getLocation();
|
||||
|
||||
// positionLatitude.value = position.latitude.toString();
|
||||
// positionLongitude.value = position.longitude.toString();
|
||||
|
||||
positionLatitude.value = _locationData.latitude.toString();
|
||||
positionLongitude.value = _locationData.longitude.toString();
|
||||
|
||||
// Mendapatkan alamat dari posisi
|
||||
List<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
position.latitude, position.longitude);
|
||||
final address = await positionToAddressGoogleApis(
|
||||
LatLng(_locationData.latitude!, _locationData.longitude!));
|
||||
|
||||
if (placemarks.isNotEmpty) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
Placemark placemark = placemarks.first;
|
||||
// String address =
|
||||
// "${placemark.thoroughfare}, ${placemark.locality}, ${placemark.administrativeArea}, ${placemark.country},";
|
||||
|
||||
String address =
|
||||
"${placemark.street}, ${placemark.subLocality}, ${placemark.subAdministrativeArea}, ${placemark.postalCode}";
|
||||
print("Alamat: $address");
|
||||
|
||||
positionLatitude.value = position.latitude.toString();
|
||||
positionLongitude.value = position.longitude.toString();
|
||||
if (address != "") {
|
||||
positionLatitude.value = _locationData.latitude.toString();
|
||||
positionLongitude.value = _locationData.longitude.toString();
|
||||
|
||||
// panggil check distance provider
|
||||
ref.read(checkDistanceProvider.notifier).checkDistance(
|
||||
@@ -145,10 +158,12 @@ class HomeScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
Future<void> requestLocationPermission() async {
|
||||
var status = await Permission.location.request();
|
||||
// var status = await Permission.location.request();
|
||||
final status = await location.serviceEnabled();
|
||||
isLoadingProsesCheckDistance.value = true;
|
||||
if (status.isGranted) {
|
||||
if (status) {
|
||||
// Izin diberikan, lanjutkan dengan mendapatkan lokasi
|
||||
// print('izin diberikan');
|
||||
getAddressFromLocation();
|
||||
} else {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
@@ -308,7 +323,16 @@ class HomeScreen extends HookConsumerWidget {
|
||||
)
|
||||
: FloatingActionButton(
|
||||
onPressed: () async {
|
||||
await requestLocationPermission();
|
||||
// await requestLocationPermission();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => CustomGoogleMapWidget(
|
||||
positionLastLatitudeParam: positionLatitude,
|
||||
positionLastLongitudeParam: positionLongitude,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
backgroundColor: Color(0xFFFFFFFF),
|
||||
shape: CircleBorder(),
|
||||
@@ -720,6 +744,69 @@ class HomeScreen extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 18),
|
||||
),
|
||||
|
||||
//Menu
|
||||
Container(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 98),
|
||||
// color: Colors.amber,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color.fromRGBO(145, 158, 171, 0.20),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: Constant.getActualXPhone(
|
||||
context: context, x: 12),
|
||||
right: Constant.getActualXPhone(
|
||||
context: context, x: 12),
|
||||
top: Constant.getActualYPhone(
|
||||
context: context, y: 8),
|
||||
bottom: Constant.getActualYPhone(
|
||||
context: context, y: 8),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, approvalRoute);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
child: Image(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 50),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 50),
|
||||
image: AssetImage(
|
||||
'images/btn_approval.png'),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 8),
|
||||
),
|
||||
Text(
|
||||
'Approval',
|
||||
style: Constant.titleH2_600_14(
|
||||
context: context)
|
||||
.copyWith(
|
||||
color: Constant.textDarkGrey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -790,7 +877,9 @@ class HomeScreen extends HookConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
// '24 hari',
|
||||
varCurrentRekapKehadiranProvider?.kehadiran ?? "",
|
||||
varCurrentRekapKehadiranProvider
|
||||
?.kehadiran ??
|
||||
"",
|
||||
style: Constant.subtitle_600_14(
|
||||
context: context)
|
||||
.copyWith(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
// import 'package:geocoding/geocoding.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -89,39 +89,39 @@ class HomeScreenV1 extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
// Position position = await Geolocator.getCurrentPosition(
|
||||
// desiredAccuracy: LocationAccuracy.high);
|
||||
|
||||
// Mendapatkan alamat dari posisi
|
||||
List<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
position.latitude, position.longitude);
|
||||
// // Mendapatkan alamat dari posisi
|
||||
// List<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
// position.latitude, position.longitude);
|
||||
|
||||
if (placemarks.isNotEmpty) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
Placemark placemark = placemarks.first;
|
||||
// String address =
|
||||
// "${placemark.thoroughfare}, ${placemark.locality}, ${placemark.administrativeArea}, ${placemark.country},";
|
||||
// if (placemarks.isNotEmpty) {
|
||||
// isLoadingProsesCheckDistance.value = false;
|
||||
// Placemark placemark = placemarks.first;
|
||||
// // String address =
|
||||
// // "${placemark.thoroughfare}, ${placemark.locality}, ${placemark.administrativeArea}, ${placemark.country},";
|
||||
|
||||
String address =
|
||||
"${placemark.street}, ${placemark.subLocality}, ${placemark.subAdministrativeArea}, ${placemark.postalCode}";
|
||||
print("Alamat: $address");
|
||||
// String address =
|
||||
// "${placemark.street}, ${placemark.subLocality}, ${placemark.subAdministrativeArea}, ${placemark.postalCode}";
|
||||
// print("Alamat: $address");
|
||||
|
||||
positionLatitude.value = position.latitude.toString();
|
||||
positionLongitude.value = position.longitude.toString();
|
||||
// positionLatitude.value = position.latitude.toString();
|
||||
// positionLongitude.value = position.longitude.toString();
|
||||
|
||||
// panggil check distance provider
|
||||
ref.read(checkDistanceProvider.notifier).checkDistance(
|
||||
selectedUser?.model.staffId ?? "",
|
||||
selectedUser?.model.companyId ?? "",
|
||||
positionLatitude.value,
|
||||
positionLongitude.value,
|
||||
);
|
||||
} else {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
SanckbarWidget(
|
||||
context, 'Tidak dapat menemukan alamat.', snackbarType.error);
|
||||
print("Tidak dapat menemukan alamat.");
|
||||
}
|
||||
// // panggil check distance provider
|
||||
// ref.read(checkDistanceProvider.notifier).checkDistance(
|
||||
// selectedUser?.model.staffId ?? "",
|
||||
// selectedUser?.model.companyId ?? "",
|
||||
// positionLatitude.value,
|
||||
// positionLongitude.value,
|
||||
// );
|
||||
// } else {
|
||||
// isLoadingProsesCheckDistance.value = false;
|
||||
// SanckbarWidget(
|
||||
// context, 'Tidak dapat menemukan alamat.', snackbarType.error);
|
||||
// print("Tidak dapat menemukan alamat.");
|
||||
// }
|
||||
} catch (e) {
|
||||
print("Error: $e");
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
|
||||
@@ -43,20 +43,27 @@ class LoginScreen extends HookConsumerWidget {
|
||||
final authModel = AuthModel(
|
||||
token: xmodel["token"],
|
||||
model: StaffModel(
|
||||
companyId: xmodel["model"]["companyId"],
|
||||
companyName: xmodel["model"]["companyName"],
|
||||
companyId: xmodel["model"]["company_id"],
|
||||
companyName: xmodel["model"]["company_name"],
|
||||
email: xmodel["model"]["email"],
|
||||
staffId: xmodel["model"]["staffId"],
|
||||
idGoogleSignIn: xmodel['model']['idGoogleSignIn'],
|
||||
staffId: xmodel["model"]["staff_id"],
|
||||
idGoogleSignIn: xmodel['model']['id_google_sign_in'],
|
||||
name: xmodel['model']['name'],
|
||||
nip: xmodel['model']['nip'],
|
||||
phoneNumber: xmodel["model"]["phoneNumber"],
|
||||
phoneNumber: xmodel["model"]["phone_number"],
|
||||
token: xmodel["model"]["token"],
|
||||
),
|
||||
);
|
||||
|
||||
// ref.read(currentUserProvider.notifier).state = authModel;
|
||||
// NEW
|
||||
// if (xmodel != null) {
|
||||
// ref.read(loginProvider.notifier).login(
|
||||
// authModel.model.email ?? "",
|
||||
// authModel.model.idGoogleSignIn ?? "",
|
||||
// );
|
||||
// }
|
||||
|
||||
// OLD
|
||||
await googleSignIn.signInSilently();
|
||||
googleSignIn.onCurrentUserChanged.listen((account) {
|
||||
// ref
|
||||
@@ -140,7 +147,7 @@ class LoginScreen extends HookConsumerWidget {
|
||||
});
|
||||
googleSignIn.signInSilently();
|
||||
|
||||
// kalau sudah pernah login
|
||||
// kalau sudah pernah login
|
||||
if (googleSignIn.currentUser?.email != null) {
|
||||
googleSignIn.signInSilently();
|
||||
// ref.read(currentUserGoogleProvider.notifier).update(
|
||||
|
||||
356
lib/screen/login/login_screen_v1.dart
Normal file
356
lib/screen/login/login_screen_v1.dart
Normal file
@@ -0,0 +1,356 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:absensi_sas/screen/login/login_provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:google_sign_in/google_sign_in.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 '../../provider/google_login_provider.dart';
|
||||
|
||||
import '../../widget/sankbar_widget.dart';
|
||||
|
||||
class LoginScreenV1 extends HookConsumerWidget {
|
||||
const LoginScreenV1({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// inisialisasi
|
||||
// ref.watch itu sama spt memantau state terus menerus
|
||||
// ref.read itu memantau namun hny 1x saja
|
||||
// GoogleSignInAccount? currentUserGoogle =
|
||||
// ref.watch(currentUserGoogleProvider);
|
||||
final googleSignIn = ref.watch(googleSignInProvider);
|
||||
|
||||
final isLoading = useState(false);
|
||||
// final errorMessage = useState("");
|
||||
final isSuccess = useState(false);
|
||||
|
||||
useEffect(() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
// final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0";
|
||||
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: StaffModel(
|
||||
companyId: xmodel["model"]["companyId"],
|
||||
companyName: xmodel["model"]["companyName"],
|
||||
email: xmodel["model"]["email"],
|
||||
staffId: xmodel["model"]["staffId"],
|
||||
idGoogleSignIn: xmodel['model']['idGoogleSignIn'],
|
||||
name: xmodel['model']['name'],
|
||||
nip: xmodel['model']['nip'],
|
||||
phoneNumber: xmodel["model"]["phoneNumber"],
|
||||
token: xmodel["model"]["token"],
|
||||
),
|
||||
);
|
||||
|
||||
// ref.read(currentUserProvider.notifier).state = authModel;
|
||||
|
||||
await googleSignIn.signInSilently();
|
||||
googleSignIn.onCurrentUserChanged.listen((account) {
|
||||
// ref
|
||||
// .read(currentUserGoogleProvider.notifier)
|
||||
// .update((state) => account);
|
||||
ref.read(currentUserProvider.notifier).state = authModel;
|
||||
ref.read(currentPageProvider.state).update((state) => 0);
|
||||
|
||||
if (account != null &&
|
||||
ref.read(currentUserProvider)?.model.staffId == "0") {
|
||||
// Lakukan login
|
||||
ref.read(loginProvider.notifier).login(
|
||||
account.email,
|
||||
account.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// kalau sudah pernah login
|
||||
if (googleSignIn.currentUser?.email != null) {
|
||||
googleSignIn.signInSilently();
|
||||
// ref.read(currentUserGoogleProvider.notifier).update(
|
||||
// (state) => googleSignIn.currentUser,
|
||||
// );
|
||||
ref.read(loginProvider.notifier).login(
|
||||
googleSignIn.currentUser?.email ?? "",
|
||||
googleSignIn.currentUser?.id ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
// ref.read(currentUserProvider.notifier).state = authModel;
|
||||
|
||||
// Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
// homeRoute,
|
||||
// (route) => false,
|
||||
// );
|
||||
});
|
||||
return () {};
|
||||
}, []);
|
||||
|
||||
// LISTEN PROVIDER
|
||||
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 = "";
|
||||
// });
|
||||
SanckbarWidget(context, next.message, snackbarType.warning);
|
||||
} else if (next is LoginStateDone) {
|
||||
print("Login Done");
|
||||
isLoading.value = false;
|
||||
isSuccess.value = true;
|
||||
ref.read(currentPageProvider.state).update((state) => 0);
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(homeRoute, (route) => false);
|
||||
}
|
||||
});
|
||||
|
||||
// fungsi untuk sync ke google mail
|
||||
Future<void> handleSignInGmail() async {
|
||||
try {
|
||||
// auth ke email
|
||||
await googleSignIn.signIn();
|
||||
googleSignIn.onCurrentUserChanged.listen((account) async {
|
||||
// ref
|
||||
// .read(currentUserGoogleProvider.notifier)
|
||||
// .update((state) => account);
|
||||
|
||||
// Check jika account tidak null dan belum login
|
||||
if (account != null &&
|
||||
ref.read(currentUserProvider)?.model.staffId == "0") {
|
||||
// Lakukan login
|
||||
ref.read(loginProvider.notifier).login(
|
||||
account.email,
|
||||
account.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
googleSignIn.signInSilently();
|
||||
|
||||
// kalau sudah pernah login
|
||||
if (googleSignIn.currentUser?.email != null) {
|
||||
googleSignIn.signInSilently();
|
||||
// ref.read(currentUserGoogleProvider.notifier).update(
|
||||
// (state) => googleSignIn.currentUser,
|
||||
// );
|
||||
ref.read(loginProvider.notifier).login(
|
||||
googleSignIn.currentUser?.email ?? "",
|
||||
googleSignIn.currentUser?.id ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
// Navigator.of(context)
|
||||
// .pushNamedAndRemoveUntil(homeRoute, (route) => false);
|
||||
} catch (error) {
|
||||
if (kDebugMode) {
|
||||
print(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(context: context, y: 100),
|
||||
),
|
||||
// (currentUserGoogle != null)
|
||||
// ? Container(
|
||||
// child: ListTile(
|
||||
// leading: GoogleUserCircleAvatar(
|
||||
// identity: currentUserGoogle,
|
||||
// ),
|
||||
// title: Text(
|
||||
// currentUserGoogle.displayName ?? "",
|
||||
// ),
|
||||
// subtitle: Text(
|
||||
// currentUserGoogle.email,
|
||||
// ),
|
||||
// trailing: IconButton(
|
||||
// icon: Icon(Icons.logout_outlined),
|
||||
// onPressed: () async {
|
||||
// await googleSignIn.disconnect();
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// :
|
||||
// Logo Landscape
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: Constant.getActualYPhone(context: context, y: 120),
|
||||
left: Constant.getActualXPhone(context: context, x: 91),
|
||||
right: Constant.getActualXPhone(context: context, x: 90),
|
||||
// bottom: Constant.getActualYPhone(context: context, y: y)
|
||||
),
|
||||
child: Container(
|
||||
width: Constant.getActualXPhone(context: context, x: 209),
|
||||
height: Constant.getActualYPhone(context: context, y: 70),
|
||||
decoration: BoxDecoration(
|
||||
// color: Colors.green,
|
||||
image: DecorationImage(
|
||||
// fit: BoxFit.contain,
|
||||
image: AssetImage('images/logo_sismedika_landscape.png'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(context: context, y: 100),
|
||||
),
|
||||
|
||||
// title
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Selamat Datang',
|
||||
style: Constant.titleH1_700(context: context)
|
||||
.copyWith(color: Constant.textBlack),
|
||||
),
|
||||
Container(
|
||||
width: Constant.getActualXPhone(context: context, x: 24),
|
||||
height: Constant.getActualYPhone(context: context, y: 24),
|
||||
// color: Colors.redAccent,
|
||||
decoration: BoxDecoration(
|
||||
// color: Colors.green,
|
||||
image: DecorationImage(
|
||||
// fit: BoxFit.contain,
|
||||
image: AssetImage('images/emoji_handshake.png'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(context: context, y: 7),
|
||||
),
|
||||
|
||||
// title grey
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Silahkan masuk untuk mengakses akun Anda',
|
||||
style: Constant.titleH2_600(context: context).copyWith(
|
||||
color: Constant.textLightGrey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(context: context, y: 10),
|
||||
),
|
||||
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// await googleSignIn.disconnect();
|
||||
// },
|
||||
// child: Text('logout'),
|
||||
// ),
|
||||
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(context: context, y: 64),
|
||||
),
|
||||
|
||||
// button login
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: Constant.getActualXPhone(context: context, x: 23),
|
||||
right: Constant.getActualXPhone(context: context, x: 23),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: Constant.getActualXPhone(context: context, x: 344),
|
||||
height: Constant.getActualYPhone(context: context, y: 48),
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await handleSignInGmail();
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
(isLoading.value)
|
||||
? SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 24),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Constant.textTrueBlack,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 24),
|
||||
decoration: BoxDecoration(
|
||||
// color: Colors.green,
|
||||
image: DecorationImage(
|
||||
// fit: BoxFit.contain,
|
||||
image: AssetImage(
|
||||
'images/icon_google.png'),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 2),
|
||||
),
|
||||
Text(
|
||||
'Continue with Google',
|
||||
style: Constant.logintitle_700(
|
||||
context: context)
|
||||
.copyWith(
|
||||
color: Constant.textBlack,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateColor.resolveWith(
|
||||
(st) => Colors.white,
|
||||
),
|
||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
side: BorderSide(
|
||||
color: Color.fromRGBO(145, 158, 171, 0.32),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
225
lib/screen/presensi/camera_page.dart
Normal file
225
lib/screen/presensi/camera_page.dart
Normal file
@@ -0,0 +1,225 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:absensi_sas/widget/sankbar_widget.dart';
|
||||
import 'package:camera/camera.dart';
|
||||
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 '../../provider/camera_controller_provider.dart';
|
||||
|
||||
class CameraPage extends HookConsumerWidget {
|
||||
const CameraPage({
|
||||
Key? key,
|
||||
required this.filePathParam,
|
||||
required this.fileDataBase64Param,
|
||||
}) : super(key: key);
|
||||
|
||||
final ValueNotifier<String> filePathParam;
|
||||
final ValueNotifier<String> fileDataBase64Param;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final controller = ref.watch(cameraControllerProvider);
|
||||
// final imagePath = useState<String?>("");
|
||||
// final fileDataBase64 = useState<String?>("");
|
||||
|
||||
// final fileData = useState<XFile?>(null);
|
||||
final filePath = useState<String>("");
|
||||
final fileDataBase64 = useState<String>("");
|
||||
final cameraController = useState<CameraController?>(null);
|
||||
|
||||
useEffect(() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
try {
|
||||
final cameras = await availableCameras();
|
||||
if (cameras.isNotEmpty) {
|
||||
final controller =
|
||||
CameraController(cameras[0], ResolutionPreset.max);
|
||||
await controller.initialize();
|
||||
cameraController.value = controller;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error initializing camera: $e');
|
||||
}
|
||||
});
|
||||
return () {};
|
||||
}, []);
|
||||
|
||||
Future<void> initializeCamera() async {
|
||||
try {
|
||||
final cameras = await availableCameras();
|
||||
if (cameras.isNotEmpty) {
|
||||
final controller = CameraController(cameras[0], ResolutionPreset.max);
|
||||
await controller.initialize();
|
||||
cameraController.value = controller;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error initializing camera: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> takePicture(
|
||||
// ValueNotifier<XFile?> imageFile,
|
||||
ValueNotifier<String> base64ImageString,
|
||||
ValueNotifier<String> imagePath,
|
||||
) async {
|
||||
// final flag = ValueNotifier<bool>(false);
|
||||
if (cameraController.value != null) {
|
||||
try {
|
||||
final image = await cameraController.value!.takePicture();
|
||||
final bytes = await image.readAsBytes();
|
||||
String base64Image = base64Encode(bytes);
|
||||
|
||||
// imageFile.value = image;
|
||||
base64ImageString.value = base64Image;
|
||||
imagePath.value = image.path;
|
||||
|
||||
fileDataBase64Param.value = base64Image;
|
||||
filePathParam.value = image.path;
|
||||
|
||||
// final shared = await SharedPreferences.getInstance();
|
||||
// shared.setString("base64Image", base64Image);
|
||||
} catch (e) {
|
||||
print('Error taking picture: $e');
|
||||
// flag.value = false;
|
||||
// RespErr(flag: false, message: 'Error taking picture: $e');
|
||||
}
|
||||
} else {
|
||||
print('CameraController is not initialized.');
|
||||
}
|
||||
}
|
||||
|
||||
processFoto(BuildContext context) async {
|
||||
await takePicture(
|
||||
// fileData,
|
||||
fileDataBase64,
|
||||
filePath,
|
||||
);
|
||||
|
||||
print("fileDataBase64 : ${fileDataBase64.value}");
|
||||
|
||||
if (fileDataBase64.value != "") {
|
||||
// ref.read(imgPhotoWebProvider.notifier).state = fileDataBase64.value;
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
print('Error: Image result is null');
|
||||
}
|
||||
|
||||
// proses base64
|
||||
// final shared = await SharedPreferences.getInstance();
|
||||
// final imageBase64 = shared.getString("base64Image");
|
||||
|
||||
// if (imageBase64 != "") {
|
||||
// fileDataBase64.value = imageBase64;
|
||||
// ref.read(imgPhotoWebProvider.notifier).state = imageBase64;
|
||||
// Navigator.of(context).pop();
|
||||
// } else {
|
||||
// print('Error: Image result is null');
|
||||
// }
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Take Photo'),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: controller == null || !controller.value.isInitialized
|
||||
? const CircularProgressIndicator()
|
||||
: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
// const SizedBox(height: 50),
|
||||
Expanded(
|
||||
child: AspectRatio(
|
||||
aspectRatio: controller.value.aspectRatio,
|
||||
child: CameraPreview(controller),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 20),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
fileDataBase64Param.value = "";
|
||||
filePathParam.value = "";
|
||||
// cameraController.dispose();
|
||||
// await controller.dispose();
|
||||
// await initializeCamera();
|
||||
},
|
||||
child: const Text("Clear Foto"),
|
||||
),
|
||||
|
||||
Spacer(),
|
||||
ElevatedButton(
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pushNamed(homeRoute);
|
||||
// },
|
||||
onPressed: () async {
|
||||
processFoto(context);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateColor.resolveWith(
|
||||
(st) => (fileDataBase64.value != "")
|
||||
? Constant.textDarkGrey
|
||||
: Constant.textOrange),
|
||||
shape:
|
||||
MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(
|
||||
color: Constant.textOrange,
|
||||
),
|
||||
),
|
||||
),
|
||||
shadowColor:
|
||||
MaterialStateProperty.all(Color(0xffff48423d)),
|
||||
elevation: MaterialStateProperty.all(4.0),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
(fileDataBase64.value != "")
|
||||
? SizedBox(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 32),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Constant.textOrange,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Process Photo',
|
||||
style: Constant.titleH1_500_18(
|
||||
context: context)
|
||||
.copyWith(
|
||||
color: Constant.textWhite,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// if (imagePath.value.isNotEmpty)
|
||||
// Container(
|
||||
// width: 300,
|
||||
// height: 300,
|
||||
// child: Image.network(imagePath.value),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
92
lib/screen/presensi/camera_page_v1.dart
Normal file
92
lib/screen/presensi/camera_page_v1.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'dart:io';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CameraPageV1 extends StatefulWidget {
|
||||
const CameraPageV1({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CameraPageV1> createState() => _CameraPageState();
|
||||
}
|
||||
|
||||
class _CameraPageState extends State<CameraPageV1> {
|
||||
CameraController? controller;
|
||||
String imagePath = "";
|
||||
List<CameraDescription>? cameras;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initializeCamera();
|
||||
}
|
||||
|
||||
Future<void> initializeCamera() async {
|
||||
// Initialize cameras
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
try {
|
||||
cameras = await availableCameras();
|
||||
if (cameras != null && cameras!.isNotEmpty) {
|
||||
// Use the first available camera
|
||||
controller = CameraController(cameras![0], ResolutionPreset.max);
|
||||
await controller?.initialize();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error initializing camera: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (controller == null || !controller!.value.isInitialized) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 50),
|
||||
Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
child: AspectRatio(
|
||||
aspectRatio: controller!.value.aspectRatio,
|
||||
child: CameraPreview(controller!),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
final image = await controller!.takePicture();
|
||||
setState(() {
|
||||
imagePath = image.path;
|
||||
});
|
||||
} catch (e) {
|
||||
print('Error taking picture: $e');
|
||||
}
|
||||
},
|
||||
child: Text("Take Photo"),
|
||||
),
|
||||
if (imagePath.isNotEmpty)
|
||||
Container(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: Image.file(File(imagePath)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
lib/screen/presensi/googleapis_provider.dart
Normal file
69
lib/screen/presensi/googleapis_provider.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../provider/dio_provider.dart';
|
||||
import '../../provider/graphql_provider.dart';
|
||||
import '../../repository/base_repository.dart';
|
||||
import '../../repository/googleapis_repository.dart';
|
||||
|
||||
// 3. state provider
|
||||
final googleApisProvider =
|
||||
StateNotifierProvider<GoogleApisNotifier, GoogleApisState>(
|
||||
(ref) => GoogleApisNotifier(ref: ref));
|
||||
|
||||
// 2. notifier
|
||||
class GoogleApisNotifier extends StateNotifier<GoogleApisState> {
|
||||
final Ref ref;
|
||||
GoogleApisNotifier({required this.ref}) : super(GoogleApisStateInit());
|
||||
|
||||
void getAddressGoogleApis(
|
||||
{required String latitude, required String longitude}) async {
|
||||
try {
|
||||
state = GoogleApisStateLoading();
|
||||
final graphql = ref.read(graphqlProvider(''));
|
||||
final dio = ref.read(dioProvider);
|
||||
final resp = await GoogleApisRepository(graphql: graphql, dio: dio).getAddressFromCoordinates(
|
||||
latitude,
|
||||
longitude,
|
||||
);
|
||||
|
||||
// print(resp);
|
||||
state = GoogleApisStateDone(model: resp);
|
||||
} catch (e) {
|
||||
if (e is BaseRepositoryException) {
|
||||
state = GoogleApisStateError(message: e.message ?? "");
|
||||
} else {
|
||||
state = GoogleApisStateError(message: e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. state
|
||||
abstract class GoogleApisState extends Equatable {
|
||||
final DateTime date;
|
||||
const GoogleApisState(this.date);
|
||||
@override
|
||||
List<Object?> get props => [date];
|
||||
}
|
||||
|
||||
class GoogleApisStateInit extends GoogleApisState {
|
||||
GoogleApisStateInit() : super(DateTime.now());
|
||||
}
|
||||
|
||||
class GoogleApisStateLoading extends GoogleApisState {
|
||||
GoogleApisStateLoading() : super(DateTime.now());
|
||||
}
|
||||
|
||||
class GoogleApisStateError extends GoogleApisState {
|
||||
final String message;
|
||||
GoogleApisStateError({
|
||||
required this.message,
|
||||
}) : super(DateTime.now());
|
||||
}
|
||||
|
||||
class GoogleApisStateDone extends GoogleApisState {
|
||||
final String? model;
|
||||
GoogleApisStateDone({
|
||||
required this.model,
|
||||
}) : super(DateTime.now());
|
||||
}
|
||||
@@ -1,25 +1,46 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:absensi_sas/provider/current_check_jam_presensi_provider.dart';
|
||||
import 'package:absensi_sas/screen/presensi/check_distance_provider.dart';
|
||||
import 'package:absensi_sas/screen/presensi/check_presensi_jam_provider.dart';
|
||||
import 'package:absensi_sas/app/googleapis_location.dart';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:camera_web/camera_web.dart';
|
||||
import 'dart:io';
|
||||
import 'package:absensi_sas/repository/googleapis_repository.dart';
|
||||
import 'package:absensi_sas/screen/presensi/presensi_clock_in_provider.dart';
|
||||
import 'package:absensi_sas/widget/real_date.dart';
|
||||
import 'package:absensi_sas/widget/sankbar_widget.dart';
|
||||
import 'package:absensi_sas/screen/presensi/presensi_clock_out_provider.dart';
|
||||
import 'package:absensi_sas/widget/custom_drawer.dart';
|
||||
import 'package:dart_nominatim/dart_nominatim.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
// import 'package:geocoding/geocoding.dart';
|
||||
// import 'package:geolocator/geolocator.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image_picker_web/image_picker_web.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:location/location.dart';
|
||||
import 'package:mobkit_dashed_border/mobkit_dashed_border.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
// import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import '../../app/constant.dart';
|
||||
import '../../app/route.dart';
|
||||
import '../../provider/camera_controller_provider.dart';
|
||||
import '../../provider/current_check_distance_provider.dart';
|
||||
import '../../provider/current_check_jam_presensi_provider.dart';
|
||||
import '../../provider/current_user_provider.dart';
|
||||
import '../../widget/custom_drawer.dart';
|
||||
import '../../provider/google_login_provider.dart';
|
||||
import '../../widget/custom_dialog_presensi_selfie_sukses.dart';
|
||||
import '../../widget/custom_google_map_widget.dart';
|
||||
import '../../widget/real_date.dart';
|
||||
import '../../widget/real_time.dart';
|
||||
import 'presensi_clock_out_provider.dart';
|
||||
import '../../widget/sankbar_widget.dart';
|
||||
import 'camera_page.dart';
|
||||
import 'check_distance_provider.dart';
|
||||
import 'check_presensi_jam_provider.dart';
|
||||
import 'googleapis_provider.dart';
|
||||
import 'dart:io' as io;
|
||||
|
||||
class PresensiScreen extends HookConsumerWidget {
|
||||
const PresensiScreen({super.key});
|
||||
@@ -36,15 +57,32 @@ class PresensiScreen extends HookConsumerWidget {
|
||||
|
||||
final positionLatitude = useState<String>("");
|
||||
final positionLongitude = useState<String>("");
|
||||
|
||||
final currLatProvider = ref.read(currentLatitudeProvider);
|
||||
final currLongProvider = ref.read(currentLongitudeProvider);
|
||||
|
||||
// final tTransactionCurrentDistance = useState<String>("NULL");
|
||||
|
||||
Location location = new Location();
|
||||
LocationData _locationData;
|
||||
|
||||
Future<void> getAddressFromLocation() async {
|
||||
try {
|
||||
isLoadingAddressUserLocation.value = true;
|
||||
// Mendapatkan posisi pengguna
|
||||
LocationPermission permission = await Geolocator.requestPermission();
|
||||
// LocationPermission permission = await Geolocator.requestPermission();
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
// if (permission == LocationPermission.denied) {
|
||||
// isLoadingAddressUserLocation.value = false;
|
||||
// SanckbarWidget(context, 'Izin lokasi ditolak', snackbarType.error);
|
||||
// // Handle jika pengguna menolak izin lokasi
|
||||
// print("Izin lokasi ditolak");
|
||||
// return;
|
||||
// }
|
||||
|
||||
final permission = await location.hasPermission();
|
||||
|
||||
if (permission == PermissionStatus.denied) {
|
||||
isLoadingAddressUserLocation.value = false;
|
||||
SanckbarWidget(context, 'Izin lokasi ditolak', snackbarType.error);
|
||||
// Handle jika pengguna menolak izin lokasi
|
||||
@@ -52,30 +90,45 @@ class PresensiScreen extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
// Position position = await Geolocator.getCurrentPosition(
|
||||
// desiredAccuracy: LocationAccuracy.high);
|
||||
|
||||
location.changeSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
interval: 1000,
|
||||
distanceFilter: 5,
|
||||
);
|
||||
_locationData = await location.getLocation();
|
||||
|
||||
// Mendapatkan alamat dari posisi
|
||||
List<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
position.latitude, position.longitude);
|
||||
// final address = await positionToAddressGoogleApis(
|
||||
// LatLng(_locationData.latitude!, _locationData.longitude!));
|
||||
|
||||
final address = await positionToAddressGoogleApis(
|
||||
LatLng(currLatProvider, currLongProvider));
|
||||
|
||||
if (positionLongitude.value.isEmpty && positionLatitude.value.isEmpty) {
|
||||
if (placemarks.isNotEmpty) {
|
||||
if (address != "") {
|
||||
isLoadingAddressUserLocation.value = false;
|
||||
Placemark placemark = placemarks.first;
|
||||
|
||||
// ref.read(googleApisProvider.notifier).getAddressGoogleApis(
|
||||
// latitude: _locationData.latitude.toString(),
|
||||
// longitude: _locationData.longitude.toString(),
|
||||
// );
|
||||
|
||||
ref.read(googleApisProvider.notifier).getAddressGoogleApis(
|
||||
latitude: currLatProvider.toString(),
|
||||
longitude: currLongProvider.toString(),
|
||||
);
|
||||
|
||||
// String address =
|
||||
// "${placemark.thoroughfare}, ${placemark.locality}, ${placemark.administrativeArea}, ${placemark.country},";
|
||||
|
||||
String address =
|
||||
"${placemark.street}, ${placemark.subLocality}, ${placemark.subAdministrativeArea}, ${placemark.postalCode}";
|
||||
print("Alamat: $address");
|
||||
// positionLatitude.value = _locationData.latitude.toString();
|
||||
// positionLongitude.value = _locationData.longitude.toString();
|
||||
|
||||
positionLatitude.value = position.latitude.toString();
|
||||
positionLongitude.value = position.longitude.toString();
|
||||
|
||||
if (address != "") {
|
||||
currentAddressUserLocation.value = address;
|
||||
}
|
||||
positionLatitude.value = currLatProvider.toString();
|
||||
positionLongitude.value = currLongProvider.toString();
|
||||
} else {
|
||||
isLoadingAddressUserLocation.value = false;
|
||||
SanckbarWidget(
|
||||
@@ -105,9 +158,10 @@ class PresensiScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
Future<void> requestLocationPermission() async {
|
||||
var status = await Permission.location.request();
|
||||
// var status = await Permission.location.request();
|
||||
final status = await location.serviceEnabled();
|
||||
isLoadingAddressUserLocation.value = true;
|
||||
if (status.isGranted) {
|
||||
if (status) {
|
||||
// Izin diberikan, lanjutkan dengan mendapatkan lokasi
|
||||
getAddressFromLocation();
|
||||
} else {
|
||||
@@ -118,6 +172,19 @@ class PresensiScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// googleApis address
|
||||
ref.listen(googleApisProvider, (prev, next) {
|
||||
if (next is GoogleApisStateLoading) {
|
||||
isLoadingProsesCheckDistance.value = true;
|
||||
} else if (next is GoogleApisStateError) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
SanckbarWidget(context, next.message, snackbarType.warning);
|
||||
} else if (next is GoogleApisStateDone) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
currentAddressUserLocation.value = next.model ?? "0";
|
||||
}
|
||||
});
|
||||
|
||||
// check distance provider
|
||||
ref.listen(checkDistanceProvider, (prev, next) {
|
||||
print('status check distance ' + next.toString());
|
||||
@@ -461,20 +528,34 @@ class PresensiScreen extends HookConsumerWidget {
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
getAddressFromLocation();
|
||||
// getAddressFromLocation();
|
||||
|
||||
// refresh location
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => CustomGoogleMapWidget(
|
||||
positionLastLatitudeParam:
|
||||
positionLatitude,
|
||||
positionLastLongitudeParam:
|
||||
positionLongitude,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// jika sudah dapat latitude dan logitude baru panggil check distance provider
|
||||
if (positionLatitude.value.isNotEmpty &&
|
||||
positionLongitude.value.isNotEmpty) {
|
||||
// panggil check distance provider
|
||||
ref
|
||||
.read(checkDistanceProvider.notifier)
|
||||
.checkDistance(
|
||||
selectedUser?.model.staffId ?? "",
|
||||
selectedUser?.model.companyId ?? "",
|
||||
positionLatitude.value,
|
||||
positionLongitude.value,
|
||||
);
|
||||
}
|
||||
// if (positionLatitude.value.isNotEmpty &&
|
||||
// positionLongitude.value.isNotEmpty) {
|
||||
// // panggil check distance provider
|
||||
// ref
|
||||
// .read(checkDistanceProvider.notifier)
|
||||
// .checkDistance(
|
||||
// selectedUser?.model.staffId ?? "",
|
||||
// selectedUser?.model.companyId ?? "",
|
||||
// positionLatitude.value,
|
||||
// positionLongitude.value,
|
||||
// );
|
||||
// }
|
||||
},
|
||||
child: Container(
|
||||
width: Constant.getActualXPhone(
|
||||
|
||||
@@ -1,31 +1,45 @@
|
||||
import 'dart:convert';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:camera_web/camera_web.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:absensi_sas/repository/googleapis_repository.dart';
|
||||
import 'package:absensi_sas/screen/presensi/presensi_clock_in_provider.dart';
|
||||
import 'package:absensi_sas/screen/presensi/presensi_clock_out_provider.dart';
|
||||
import 'package:absensi_sas/widget/custom_drawer.dart';
|
||||
import 'package:dart_nominatim/dart_nominatim.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
// import 'package:geocoding/geocoding.dart';
|
||||
// import 'package:geolocator/geolocator.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image_picker_web/image_picker_web.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:location/location.dart';
|
||||
import 'package:mobkit_dashed_border/mobkit_dashed_border.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
// import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import '../../app/constant.dart';
|
||||
import '../../app/googleapis_location.dart';
|
||||
import '../../app/route.dart';
|
||||
import '../../provider/camera_controller_provider.dart';
|
||||
import '../../provider/current_check_distance_provider.dart';
|
||||
import '../../provider/current_check_jam_presensi_provider.dart';
|
||||
import '../../provider/current_user_provider.dart';
|
||||
import '../../provider/google_login_provider.dart';
|
||||
import '../../widget/custom_dialog_presensi_selfie_sukses.dart';
|
||||
import '../../widget/custom_google_map_widget.dart';
|
||||
import '../../widget/real_date.dart';
|
||||
import '../../widget/real_time.dart';
|
||||
import '../../widget/sankbar_widget.dart';
|
||||
import 'camera_page.dart';
|
||||
import 'check_distance_provider.dart';
|
||||
import 'check_presensi_jam_provider.dart';
|
||||
import 'presensi_selfie_upload_area.dart';
|
||||
import 'googleapis_provider.dart';
|
||||
import 'dart:io' as io;
|
||||
|
||||
class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
@@ -41,17 +55,25 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
ref.watch(currentCheckJamPresensiProvider);
|
||||
final selectedUser = ref.watch(currentUserProvider);
|
||||
|
||||
final currLatProvider = ref.read(currentLatitudeProvider);
|
||||
final currLongProvider = ref.read(currentLongitudeProvider);
|
||||
|
||||
final positionLatitude = useState<String>("");
|
||||
final positionLongitude = useState<String>("");
|
||||
// final tTransactionCurrentDistance = useState<String>("NULL");
|
||||
|
||||
final fileData = useState<XFile?>(null);
|
||||
final fileDataBase64 = useState<String>("");
|
||||
final filePath = useState<String>("");
|
||||
final isImage = useState(false);
|
||||
final fileEkstension = useState("");
|
||||
final fileSize = useState(0);
|
||||
final fileName = useState("");
|
||||
|
||||
Location location = new Location();
|
||||
LocationData _locationData;
|
||||
|
||||
// final imgPhotoWeb = ref.read(imgPhotoWebProvider);
|
||||
|
||||
getBase64() async {
|
||||
// List<int> imageBytes = await fileData.value?.readAsBytes();
|
||||
if (fileData.value != null) {
|
||||
@@ -71,38 +93,26 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
pickImage() async {
|
||||
final XFile? pickedFile = await _picker.pickImage(
|
||||
source: ImageSource.camera,
|
||||
// maxWidth set untuk width image
|
||||
maxWidth: 640,
|
||||
// maxHeight set untuk width image
|
||||
maxHeight: 480);
|
||||
if (pickedFile != null) {
|
||||
final tmpFile = FilePickerResult([
|
||||
PlatformFile(
|
||||
name: pickedFile.name,
|
||||
size: await pickedFile.length(),
|
||||
path: pickedFile.path,
|
||||
)
|
||||
]);
|
||||
if (await pickedFile.length() > 10000000) {
|
||||
SanckbarWidget(context, "File tidak boleh lebih dari 10 MB",
|
||||
snackbarType.warning);
|
||||
} else {
|
||||
fileData.value = pickedFile;
|
||||
isImage.value = true;
|
||||
fileEkstension.value = tmpFile.files.single.extension ?? "";
|
||||
DateTime now = new DateTime.now();
|
||||
fileName.value =
|
||||
"IMG-${now.year}${now.month}${now.day}.${tmpFile.files.single.extension ?? ''}";
|
||||
}
|
||||
|
||||
// final Directory appDocumentsDir =
|
||||
// await getApplicationDocumentsDirectory();
|
||||
// print(appDocumentsDir);
|
||||
// await pickedFile!.saveTo(appDocumentsDir.path);
|
||||
await getBase64();
|
||||
pickImage() async {
|
||||
if (kIsWeb) {
|
||||
// final shared = await SharedPreferences.getInstance();
|
||||
// final imageBase64 = shared.getString("base64Image");
|
||||
// final imagePathNew = shared.getString("imagePath");
|
||||
try {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => CameraPage(
|
||||
fileDataBase64Param: fileDataBase64,
|
||||
filePathParam: filePath,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
SanckbarWidget(
|
||||
context, "Failed to take picture", snackbarType.warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,9 +173,10 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
try {
|
||||
isLoadingAddressUserLocation.value = true;
|
||||
// Mendapatkan posisi pengguna
|
||||
LocationPermission permission = await Geolocator.requestPermission();
|
||||
// LocationPermission permission = await Geolocator.requestPermission();
|
||||
final permission = await location.hasPermission();
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
if (permission == PermissionStatus.denied) {
|
||||
isLoadingAddressUserLocation.value = false;
|
||||
SanckbarWidget(context, 'Izin lokasi ditolak', snackbarType.error);
|
||||
// Handle jika pengguna menolak izin lokasi
|
||||
@@ -173,30 +184,53 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
// Position position = await Geolocator.getCurrentPosition(
|
||||
// locationSettings: LocationSettings(
|
||||
// accuracy: LocationAccuracy.high,
|
||||
// ),
|
||||
// );
|
||||
|
||||
location.changeSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
interval: 1000,
|
||||
distanceFilter: 5,
|
||||
);
|
||||
_locationData = await location.getLocation();
|
||||
|
||||
// Mendapatkan alamat dari posisi
|
||||
List<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
position.latitude, position.longitude);
|
||||
// final address = await positionToAddressGoogleApis(
|
||||
// LatLng(_locationData.latitude!, _locationData.longitude!));
|
||||
|
||||
final address = await positionToAddressGoogleApis(
|
||||
LatLng(currLatProvider, currLongProvider));
|
||||
|
||||
// final address = await positionToAddress(
|
||||
// LatLng(-7.5666203, 110.8083376),
|
||||
// );
|
||||
|
||||
// Mendapatkan alamat dari posisi
|
||||
if (positionLongitude.value.isEmpty && positionLatitude.value.isEmpty) {
|
||||
if (placemarks.isNotEmpty) {
|
||||
if (address != "") {
|
||||
isLoadingAddressUserLocation.value = false;
|
||||
Placemark placemark = placemarks.first;
|
||||
|
||||
// ref.read(googleApisProvider.notifier).getAddressGoogleApis(
|
||||
// latitude: _locationData.latitude.toString(),
|
||||
// longitude: _locationData.longitude.toString(),
|
||||
// );
|
||||
|
||||
ref.read(googleApisProvider.notifier).getAddressGoogleApis(
|
||||
latitude: currLatProvider.toString(),
|
||||
longitude: currLongProvider.toString(),
|
||||
);
|
||||
|
||||
// String address =
|
||||
// "${placemark.thoroughfare}, ${placemark.locality}, ${placemark.administrativeArea}, ${placemark.country},";
|
||||
|
||||
String address =
|
||||
"${placemark.street}, ${placemark.subLocality}, ${placemark.subAdministrativeArea}, ${placemark.postalCode}";
|
||||
print("Alamat: $address");
|
||||
// positionLatitude.value = _locationData.latitude.toString();
|
||||
// positionLongitude.value = _locationData.longitude.toString();
|
||||
|
||||
positionLatitude.value = position.latitude.toString();
|
||||
positionLongitude.value = position.longitude.toString();
|
||||
|
||||
if (address != "") {
|
||||
currentAddressUserLocation.value = address;
|
||||
}
|
||||
positionLatitude.value = currLatProvider.toString();
|
||||
positionLongitude.value = currLongProvider.toString();
|
||||
} else {
|
||||
isLoadingAddressUserLocation.value = false;
|
||||
SanckbarWidget(
|
||||
@@ -226,9 +260,10 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
Future<void> requestLocationPermission() async {
|
||||
var status = await Permission.location.request();
|
||||
// var status = await PermissionStatus.location.request();
|
||||
final status = await location.serviceEnabled();
|
||||
isLoadingAddressUserLocation.value = true;
|
||||
if (status.isGranted) {
|
||||
if (status) {
|
||||
// Izin diberikan, lanjutkan dengan mendapatkan lokasi
|
||||
getAddressFromLocation();
|
||||
} else {
|
||||
@@ -261,6 +296,19 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
}
|
||||
});
|
||||
|
||||
// googleApis address
|
||||
ref.listen(googleApisProvider, (prev, next) {
|
||||
if (next is GoogleApisStateLoading) {
|
||||
isLoadingProsesCheckDistance.value = true;
|
||||
} else if (next is GoogleApisStateError) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
SanckbarWidget(context, next.message, snackbarType.warning);
|
||||
} else if (next is GoogleApisStateDone) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
currentAddressUserLocation.value = next.model ?? "0";
|
||||
}
|
||||
});
|
||||
|
||||
// check jam presensi
|
||||
ref.listen(checkPresensiJamProvider, (prev, next) {
|
||||
if (next is CheckPresensiJamStateLoading) {
|
||||
@@ -508,7 +556,7 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
// 'Home Screen',
|
||||
'Presensi Selfie',
|
||||
'Presensi Selfie ${positionLatitude.value} , ${positionLongitude.value}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Constant.textBlack,
|
||||
@@ -626,20 +674,34 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
getAddressFromLocation();
|
||||
// getAddressFromLocation();
|
||||
|
||||
// refresh location
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => CustomGoogleMapWidget(
|
||||
positionLastLatitudeParam:
|
||||
positionLatitude,
|
||||
positionLastLongitudeParam:
|
||||
positionLongitude,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// jika sudah dapat latitude dan logitude baru panggil check distance provider
|
||||
if (positionLatitude.value.isNotEmpty &&
|
||||
positionLongitude.value.isNotEmpty) {
|
||||
// panggil check distance provider
|
||||
ref
|
||||
.read(checkDistanceProvider.notifier)
|
||||
.checkDistance(
|
||||
selectedUser?.model.staffId ?? "",
|
||||
selectedUser?.model.companyId ?? "",
|
||||
positionLatitude.value,
|
||||
positionLongitude.value,
|
||||
);
|
||||
}
|
||||
// if (positionLatitude.value.isNotEmpty &&
|
||||
// positionLongitude.value.isNotEmpty) {
|
||||
// // panggil check distance provider
|
||||
// ref
|
||||
// .read(checkDistanceProvider.notifier)
|
||||
// .checkDistance(
|
||||
// selectedUser?.model.staffId ?? "",
|
||||
// selectedUser?.model.companyId ?? "",
|
||||
// positionLatitude.value,
|
||||
// positionLongitude.value,
|
||||
// );
|
||||
// }
|
||||
},
|
||||
child: Container(
|
||||
width: Constant.getActualXPhone(
|
||||
@@ -722,13 +784,53 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
? Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: PresensiSelfieUploadAreaWidget(
|
||||
isLoading: isLoadingAddressUserLocation,
|
||||
isImage: isImage,
|
||||
fileData: fileData,
|
||||
fileDataBase64: fileDataBase64,
|
||||
pickFile: pickFile,
|
||||
pickImage: pickImage,
|
||||
: InkWell(
|
||||
onTap: () {
|
||||
pickImage();
|
||||
},
|
||||
child: Container(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 390),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context,
|
||||
y: isImage.value ? 200 : 83),
|
||||
decoration: BoxDecoration(
|
||||
color: Constant.bgUploadFile,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon(
|
||||
// Icons.upload_outlined,
|
||||
// color: Constant.textOrange,
|
||||
// ),
|
||||
|
||||
Image.asset(
|
||||
'images/camera_selfie.png', // Path gambar untuk "Check In"
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 24),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(
|
||||
context: context,
|
||||
y: 4,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Upload File',
|
||||
style: Constant.titleH2_400_12(
|
||||
context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Constant.textOrange),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
// gambar icon presensi clock out
|
||||
@@ -739,18 +841,75 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
? Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: PresensiSelfieUploadAreaWidget(
|
||||
isLoading: isLoadingAddressUserLocation,
|
||||
isImage: isImage,
|
||||
fileData: fileData,
|
||||
fileDataBase64: fileDataBase64,
|
||||
pickFile: pickFile,
|
||||
pickImage: pickImage,
|
||||
: InkWell(
|
||||
onTap: () {
|
||||
pickImage();
|
||||
},
|
||||
child: Container(
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 390),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context,
|
||||
y: isImage.value ? 200 : 83),
|
||||
decoration: BoxDecoration(
|
||||
color: Constant.bgUploadFile,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon(
|
||||
// Icons.upload_outlined,
|
||||
// color: Constant.textOrange,
|
||||
// ),
|
||||
|
||||
Image.asset(
|
||||
'images/camera_selfie.png', // Path gambar untuk "Check In"
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 24),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(
|
||||
context: context,
|
||||
y: 4,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Upload File',
|
||||
style: Constant.titleH2_400_12(
|
||||
context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Constant.textOrange),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
// gambar selfie
|
||||
if (fileDataBase64.value.isNotEmpty) ...[
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(context: context, y: 40),
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height:
|
||||
Constant.getActualYPhone(context: context, y: 200),
|
||||
child: Image.network(
|
||||
filePath.value,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Spacer(),
|
||||
|
||||
// button clock in dan clock out
|
||||
@@ -830,9 +989,8 @@ class PresensiSelfieScreen extends HookConsumerWidget {
|
||||
token,
|
||||
"Clock In");
|
||||
|
||||
// print(
|
||||
// fileDataBase64.value,
|
||||
// );
|
||||
print(
|
||||
"proses imageX: ${fileDataBase64.value}");
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
|
||||
205
lib/screen/presensi/presensi_selfie_upload_area_web.dart
Normal file
205
lib/screen/presensi/presensi_selfie_upload_area_web.dart
Normal file
@@ -0,0 +1,205 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../app/constant.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
import '../../provider/camera_controller_provider.dart';
|
||||
|
||||
class PresensiSelfieUploadAreaWebWidget extends HookConsumerWidget {
|
||||
const PresensiSelfieUploadAreaWebWidget({
|
||||
super.key,
|
||||
required this.isImage,
|
||||
required this.fileData,
|
||||
required this.fileDataBase64,
|
||||
required this.pickFile,
|
||||
required this.pickImage,
|
||||
required this.isLoading,
|
||||
});
|
||||
|
||||
final ValueNotifier<bool> isImage;
|
||||
final ValueNotifier<bool> isLoading;
|
||||
final ValueNotifier<XFile?> fileData;
|
||||
final ValueNotifier<String> fileDataBase64;
|
||||
final Function pickImage;
|
||||
final Function pickFile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final imgPhotoWeb = ref.read(imgPhotoWebProvider);
|
||||
|
||||
return InkWell(
|
||||
onTap: !isLoading.value
|
||||
? () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Container(
|
||||
height: Constant.getActualYPhone(context: context, y: 200),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Column(
|
||||
// children: [
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
// Navigator.pop(context);
|
||||
// pickFile();
|
||||
// },
|
||||
// icon: Icon(
|
||||
// Icons.folder_copy_rounded,
|
||||
// size: 50,
|
||||
// color: Constant.textOrange,
|
||||
// )),
|
||||
// Text(
|
||||
// "Browse a file",
|
||||
// style: Constant.titleH2_400_12(context: context)
|
||||
// .copyWith(
|
||||
// fontWeight: FontWeight.w600,
|
||||
// color: Constant.textBlack,
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
pickImage();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.add_a_photo_rounded,
|
||||
size: 50,
|
||||
color: Constant.textOrange,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 10),
|
||||
),
|
||||
Text(
|
||||
"Take a picture",
|
||||
style: Constant.titleH2_400_12(context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Constant.textBlack,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
children: [
|
||||
Container(
|
||||
width: Constant.getActualXPhone(context: context, x: 390),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: isImage.value ? 200 : 83),
|
||||
decoration: BoxDecoration(
|
||||
color: Constant.bgUploadFile,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
final String? mime = lookupMimeType(fileData.value?.path ?? "");
|
||||
return Semantics(
|
||||
label: 'image_picker_example_picked_image',
|
||||
child: (mime != null
|
||||
? (mime.startsWith('image/'))
|
||||
? Image.network(
|
||||
// image: AssetImage(photo.value!.path),
|
||||
fileData.value!.path,
|
||||
frameBuilder: (context, child, frame,
|
||||
wasSynchronouslyLoaded) {
|
||||
return (wasSynchronouslyLoaded)
|
||||
? Center(
|
||||
child: Text("Loadinga"),
|
||||
)
|
||||
: Container(
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
errorBuilder: (BuildContext context, Object error,
|
||||
StackTrace? stackTrace) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'This image type is not supported'));
|
||||
},
|
||||
)
|
||||
: Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.file_present_rounded,
|
||||
size: 40,
|
||||
color: Constant.textOrange,
|
||||
),
|
||||
Text(
|
||||
fileData.value?.name ?? '',
|
||||
style:
|
||||
Constant.titleH2_400(context: context),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon(
|
||||
// Icons.upload_outlined,
|
||||
// color: Constant.textOrange,
|
||||
// ),
|
||||
|
||||
Image.asset(
|
||||
'images/camera_selfie.png', // Path gambar untuk "Check In"
|
||||
width: Constant.getActualXPhone(
|
||||
context: context, x: 24),
|
||||
height: Constant.getActualYPhone(
|
||||
context: context, y: 24),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualYPhone(
|
||||
context: context,
|
||||
y: 4,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Upload File',
|
||||
style: Constant.titleH2_400_12(context: context)
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Constant.textOrange),
|
||||
)
|
||||
],
|
||||
)));
|
||||
}),
|
||||
),
|
||||
if (fileData.value != null && !isLoading.value)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
fileData.value = null;
|
||||
fileDataBase64.value = '';
|
||||
isImage.value = false;
|
||||
},
|
||||
icon: Icon(Icons.cancel_outlined)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
72
lib/test_flutter_web_map.dart
Normal file
72
lib/test_flutter_web_map.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'dart:js' as js;
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'provider/location_provider.dart';
|
||||
|
||||
class TestFlutterWebMap extends HookConsumerWidget {
|
||||
const TestFlutterWebMap({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final location = ref.watch(locationProvider);
|
||||
final refreshKey = useState<int>(0);
|
||||
|
||||
useEffect(() {
|
||||
void fetchData() {
|
||||
final uri = Uri.base;
|
||||
final queryParams = uri.queryParameters;
|
||||
|
||||
if (queryParams.isNotEmpty) {
|
||||
ref.read(locationProvider.notifier).state = {
|
||||
'status': queryParams['status'],
|
||||
'message': queryParams['message'],
|
||||
'latitude': queryParams['latitude'] != null
|
||||
? double.tryParse(queryParams['latitude']!)
|
||||
: null,
|
||||
'longitude': queryParams['longitude'] != null
|
||||
? double.tryParse(queryParams['longitude']!)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
js.context.callMethod('getLocationAndSend');
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
return () {};
|
||||
}, [refreshKey.value]);
|
||||
|
||||
void _refreshData() {
|
||||
refreshKey.value++; // Trigger useEffect to run again
|
||||
}
|
||||
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Flutter Web Google Maps'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
SizedBox(height: 20), // Add some spacing
|
||||
Text(
|
||||
'Latitude: ${location['latitude']?.toString() ?? 'Loading...'}'),
|
||||
Text(
|
||||
'Longitude: ${location['longitude']?.toString() ?? 'Loading...'}'),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _refreshData,
|
||||
child: Icon(Icons.refresh),
|
||||
tooltip: 'Refresh Location',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
|
||||
class TestMap extends StatefulWidget {
|
||||
const TestMap({super.key});
|
||||
@@ -104,37 +103,37 @@ class _TestMapState extends State<TestMap> {
|
||||
Map<String, dynamic> posx = await determinePosition();
|
||||
print('${posx}');
|
||||
|
||||
if (posx['error'] == false) {
|
||||
Position position = posx['position'];
|
||||
List<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
position.latitude, position.longitude);
|
||||
print('placemark : ${placemarks}');
|
||||
// if (posx['error'] == false) {
|
||||
// Position position = posx['position'];
|
||||
// List<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
// position.latitude, position.longitude);
|
||||
// print('placemark : ${placemarks}');
|
||||
|
||||
double jarakx = await Geolocator.distanceBetween(-7.5350973,
|
||||
110.7921524, position.latitude, position.longitude);
|
||||
// double jarakx = await Geolocator.distanceBetween(-7.5350973,
|
||||
// 110.7921524, position.latitude, position.longitude);
|
||||
|
||||
setState(() {
|
||||
jarak = jarakx.toString() + " meter";
|
||||
longitude = position.longitude.toString();
|
||||
latitude = position.latitude.toString();
|
||||
addressFromCoordinat = placemarks[0].street.toString() +
|
||||
" , " +
|
||||
placemarks[0].subLocality.toString() +
|
||||
" , " +
|
||||
placemarks[0].locality.toString() +
|
||||
" , " +
|
||||
placemarks[0].postalCode.toString() +
|
||||
" , " +
|
||||
placemarks[0].country.toString();
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
longitude = "";
|
||||
addressFromCoordinat = "";
|
||||
jarak = "";
|
||||
latitude = posx['message'].toString();
|
||||
});
|
||||
}
|
||||
// setState(() {
|
||||
// jarak = jarakx.toString() + " meter";
|
||||
// longitude = position.longitude.toString();
|
||||
// latitude = position.latitude.toString();
|
||||
// addressFromCoordinat = placemarks[0].street.toString() +
|
||||
// " , " +
|
||||
// placemarks[0].subLocality.toString() +
|
||||
// " , " +
|
||||
// placemarks[0].locality.toString() +
|
||||
// " , " +
|
||||
// placemarks[0].postalCode.toString() +
|
||||
// " , " +
|
||||
// placemarks[0].country.toString();
|
||||
// });
|
||||
// } else {
|
||||
// setState(() {
|
||||
// longitude = "";
|
||||
// addressFromCoordinat = "";
|
||||
// jarak = "";
|
||||
// latitude = posx['message'].toString();
|
||||
// });
|
||||
// }
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
|
||||
101
lib/test_map_x.dart
Normal file
101
lib/test_map_x.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class TestMapX extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final mapController = useState<GoogleMapController?>(null);
|
||||
final currentPosition = useState<LatLng?>(null);
|
||||
final isLocationSet = useState<bool>(false);
|
||||
final customIcon = useState<BitmapDescriptor>(BitmapDescriptor.defaultMarker);
|
||||
|
||||
final center = const LatLng(-7.566957, 110.8080284);
|
||||
|
||||
useEffect(() {
|
||||
Future<void> _loadCustomMarker() async {
|
||||
final icon = await BitmapDescriptor.asset(
|
||||
const ImageConfiguration(size: Size(100, 100)),
|
||||
'images/custom_marker1.png',
|
||||
);
|
||||
customIcon.value = icon;
|
||||
}
|
||||
|
||||
_loadCustomMarker();
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
Future<void> _getCurrentLocation() async {
|
||||
try {
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) return;
|
||||
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.deniedForever ||
|
||||
permission == LocationPermission.denied) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
|
||||
currentPosition.value = LatLng(position.latitude, position.longitude);
|
||||
isLocationSet.value = true;
|
||||
|
||||
if (mapController.value != null) {
|
||||
mapController.value!.animateCamera(
|
||||
CameraUpdate.newLatLng(currentPosition.value!),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Google Maps Flutter Web'),
|
||||
backgroundColor: Colors.green[700],
|
||||
),
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
GoogleMap(
|
||||
onMapCreated: (controller) => mapController.value = controller,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: center,
|
||||
zoom: 11.0,
|
||||
),
|
||||
markers: isLocationSet.value && currentPosition.value != null
|
||||
? {
|
||||
Marker(
|
||||
markerId: MarkerId('current_location'),
|
||||
position: currentPosition.value!,
|
||||
icon: customIcon.value,
|
||||
infoWindow: InfoWindow(
|
||||
title: 'Posisi : ${currentPosition.value!.latitude}, ${currentPosition.value!.longitude}',
|
||||
snippet:
|
||||
'${currentPosition.value!.latitude}, ${currentPosition.value!.longitude}',
|
||||
),
|
||||
),
|
||||
}
|
||||
: {},
|
||||
),
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
child: ElevatedButton(
|
||||
onPressed: _getCurrentLocation,
|
||||
child: Text('Current Location'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
77
lib/widget/custom_button_approval.dart
Normal file
77
lib/widget/custom_button_approval.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:absensi_sas/app/constant.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomButtonOrange extends StatelessWidget {
|
||||
const CustomButtonOrange({super.key, required this.btnText, this.onPressed});
|
||||
final Function()? onPressed;
|
||||
final String btnText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(boxShadow: [
|
||||
BoxShadow(
|
||||
color: Constant.textOrange.withOpacity(0.24),
|
||||
spreadRadius: 0,
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 8), // changes position of shadow
|
||||
),
|
||||
]),
|
||||
child: ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
elevation: MaterialStatePropertyAll(0),
|
||||
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Constant.textOrange,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8))),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Constant.textOrange;
|
||||
}),
|
||||
overlayColor:
|
||||
MaterialStatePropertyAll(Colors.white.withOpacity(0.3)),
|
||||
foregroundColor: MaterialStatePropertyAll(Constant.textOrange),
|
||||
shadowColor: MaterialStatePropertyAll(Constant.textOrange),
|
||||
surfaceTintColor: MaterialStatePropertyAll(Constant.textOrange)),
|
||||
onPressed: onPressed,
|
||||
child: Text(
|
||||
btnText,
|
||||
style: Constant.body_14(context: context)
|
||||
.copyWith(color: Colors.white, fontWeight: FontWeight.w700),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomButtonWhite extends StatelessWidget {
|
||||
const CustomButtonWhite({super.key, required this.btnText, this.onPressed});
|
||||
final Function()? onPressed;
|
||||
final String btnText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Text(
|
||||
btnText,
|
||||
style: Constant.body_14(context: context)
|
||||
.copyWith(color: Colors.black, fontWeight: FontWeight.w700),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
elevation: MaterialStatePropertyAll(0),
|
||||
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8))),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Colors.white;
|
||||
}),
|
||||
overlayColor:
|
||||
MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)),
|
||||
foregroundColor: MaterialStatePropertyAll(Colors.white),
|
||||
shadowColor: MaterialStatePropertyAll(Colors.white),
|
||||
surfaceTintColor: MaterialStatePropertyAll(Colors.white)),
|
||||
);
|
||||
}
|
||||
}
|
||||
201
lib/widget/custom_google_map_widget.dart
Normal file
201
lib/widget/custom_google_map_widget.dart
Normal file
@@ -0,0 +1,201 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:geolocator/geolocator.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 '../provider/google_login_provider.dart';
|
||||
import '../screen/presensi/check_distance_provider.dart';
|
||||
import 'sankbar_widget.dart';
|
||||
|
||||
class CustomGoogleMapWidget extends HookConsumerWidget {
|
||||
const CustomGoogleMapWidget({
|
||||
super.key,
|
||||
required this.positionLastLatitudeParam,
|
||||
required this.positionLastLongitudeParam,
|
||||
});
|
||||
|
||||
final ValueNotifier<String> positionLastLatitudeParam;
|
||||
final ValueNotifier<String> positionLastLongitudeParam;
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final mapController = useState<GoogleMapController?>(null);
|
||||
final currentPosition = useState<LatLng?>(null);
|
||||
final isLocationSet = useState<bool>(false);
|
||||
final customIcon =
|
||||
useState<BitmapDescriptor>(BitmapDescriptor.defaultMarker);
|
||||
|
||||
final center = LatLng(
|
||||
Constant.positionLatitudeAwal,
|
||||
Constant.positionLongitudeAwal,
|
||||
);
|
||||
|
||||
final selectedUser = ref.read(currentUserProvider);
|
||||
final isLoadingProsesCheckDistance = useState<bool>(false);
|
||||
|
||||
useEffect(() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0";
|
||||
if (staffID == "0") {
|
||||
//not login
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(loginRoute, (route) => true);
|
||||
|
||||
// Navigator.popAndPushNamed(context, loginRoute);
|
||||
return;
|
||||
}
|
||||
});
|
||||
return () {};
|
||||
}, []);
|
||||
|
||||
useEffect(() {
|
||||
Future<void> loadCustomMarker() async {
|
||||
final icon = await BitmapDescriptor.asset(
|
||||
const ImageConfiguration(size: Size(100, 100)),
|
||||
'images/custom_marker1.png',
|
||||
);
|
||||
customIcon.value = icon;
|
||||
}
|
||||
|
||||
loadCustomMarker();
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
Future<void> getCurrentLocation(context) async {
|
||||
try {
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) return;
|
||||
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.deniedForever ||
|
||||
permission == LocationPermission.denied) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
|
||||
currentPosition.value = LatLng(position.latitude, position.longitude);
|
||||
isLocationSet.value = true;
|
||||
|
||||
if (mapController.value != null) {
|
||||
mapController.value!.animateCamera(
|
||||
CameraUpdate.newLatLng(currentPosition.value!),
|
||||
);
|
||||
|
||||
// change current position
|
||||
final currentLat = currentPosition.value!.latitude;
|
||||
final currentLong = currentPosition.value!.longitude;
|
||||
|
||||
// update parameter di home
|
||||
positionLastLatitudeParam.value = currentLat.toString();
|
||||
positionLastLongitudeParam.value = currentLong.toString();
|
||||
|
||||
// update ke state provider
|
||||
ref.read(currentLatitudeProvider.notifier).state = currentLat;
|
||||
ref.read(currentLongitudeProvider.notifier).state = currentLong;
|
||||
|
||||
print("LATITUDE PRESENSI ${currentLat.toString()}");
|
||||
print("LONGITUDE PRESENSI ${currentLong.toString()}");
|
||||
|
||||
// check distance
|
||||
ref.read(checkDistanceProvider.notifier).checkDistance(
|
||||
selectedUser?.model.staffId ?? "",
|
||||
selectedUser?.model.companyId ?? "",
|
||||
currentLat.toString(),
|
||||
currentLong.toString(),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// check distance provider
|
||||
ref.listen(checkDistanceProvider, (prev, next) {
|
||||
if (next is CheckDistanceStateLoading) {
|
||||
isLoadingProsesCheckDistance.value = true;
|
||||
} else if (next is CheckDistanceStateError) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
SanckbarWidget(context, next.message, snackbarType.warning);
|
||||
} else if (next is CheckDistanceStateDone) {
|
||||
isLoadingProsesCheckDistance.value = false;
|
||||
|
||||
if (next.model.selfie == "TRUE") {
|
||||
ref.read(currentPageProvider.notifier).update((state) => 99);
|
||||
// Navigator.of(context).pop();
|
||||
Navigator.of(context).restorablePushNamed(presensiSelfieRoute);
|
||||
} else {
|
||||
if (next.model.selfie == "FALSE") {
|
||||
ref.read(currentPageProvider.notifier).update((state) => 99);
|
||||
Navigator.of(context).restorablePushNamed(presensiRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Constant.textWhite,
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
// 'Home Screen',
|
||||
'User Location',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Constant.textBlack,
|
||||
),
|
||||
),
|
||||
backgroundColor: Constant.textWhite,
|
||||
iconTheme: IconThemeData(
|
||||
color: Constant.textBlack,
|
||||
),
|
||||
// elevation: 1.0,
|
||||
elevation: 0.5,
|
||||
),
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
GoogleMap(
|
||||
onMapCreated: (controller) => mapController.value = controller,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: center,
|
||||
zoom: 11.0,
|
||||
),
|
||||
markers: isLocationSet.value && currentPosition.value != null
|
||||
? {
|
||||
Marker(
|
||||
markerId: MarkerId('current_location'),
|
||||
position: currentPosition.value!,
|
||||
icon: customIcon.value,
|
||||
infoWindow: InfoWindow(
|
||||
title:
|
||||
'Posisi : ${currentPosition.value!.latitude}, ${currentPosition.value!.longitude}',
|
||||
snippet:
|
||||
'${currentPosition.value!.latitude}, ${currentPosition.value!.longitude}',
|
||||
),
|
||||
),
|
||||
}
|
||||
: {},
|
||||
),
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
getCurrentLocation(context);
|
||||
},
|
||||
child: Text('Current Location'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,16 @@ import Foundation
|
||||
|
||||
import file_selector_macos
|
||||
import geolocator_apple
|
||||
import google_sign_in_ios
|
||||
import location
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||
LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
||||
518
pubspec.lock
518
pubspec.lock
@@ -1,22 +1,6 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "61.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.0"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -57,6 +41,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
camera:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: camera
|
||||
sha256: "26ff41045772153f222ffffecba711a206f670f5834d40ebf5eed3811692f167"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.0+2"
|
||||
camera_android_camerax:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_android_camerax
|
||||
sha256: "7cd93578ad201dcc6bb5810451fb00d76a86bab9b68dceb68b8cbd7038ac5846"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.8+3"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_avfoundation
|
||||
sha256: "7c28969a975a7eb2349bc2cb2dfe3ad218a33dba9968ecfb181ce08c87486655"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.17+3"
|
||||
camera_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_platform_interface
|
||||
sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.8.0"
|
||||
camera_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_web
|
||||
sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -93,10 +117,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.18.0"
|
||||
colorful_iconify_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: colorful_iconify_flutter
|
||||
sha256: "5ebc63d10c3e97279849162b415fda8e71b7ff24ddd663539d7328279dae9c1d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -109,10 +141,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5"
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3+7"
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -145,14 +177,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
dart_nominatim:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dart_nominatim
|
||||
sha256: "5f1d72733f5dd6cf4d32bae707fda4bc6ef1a61ddbd3b24e373d66b52a2ae5a9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8"
|
||||
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.6"
|
||||
version: "5.6.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -161,6 +209,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
eva_icons_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: eva_icons_flutter
|
||||
sha256: "6d48a10b93590ab83eb092bee5adacdeb14f3d83f527a4b9e4092c363d56e2a8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -225,6 +281,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
fluentui_system_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluentui_system_icons
|
||||
sha256: abe7c343e2151e0ad6544653e0b6601686b993bc436ccde72b88cea677db0c0a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.226"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -238,6 +310,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.6"
|
||||
flutter_image_compress:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_image_compress
|
||||
sha256: "37f1b26399098e5f97b74c1483f534855e7dff68ead6ddaccf747029fb03f29f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -258,10 +338,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_map
|
||||
sha256: cda8d72135b697f519287258b5294a57ce2f2a5ebf234f0e406aad4dc14c9399
|
||||
sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
version: "6.2.1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -278,6 +358,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
flutter_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -288,110 +376,118 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
geocoding:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geocoding
|
||||
sha256: e1dc0ac56666d9ed1d5a9ae5543ce9eb5986db6209cc7600103487d09192059c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
geocoding_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geocoding_android
|
||||
sha256: "609db1d71bc364dd9d0616f72a41c01e0c74f3a3807efb85e0d5a67e57baf50f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
geocoding_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geocoding_ios
|
||||
sha256: "8f79e380abb640ef4d88baee8bb65390058c802601158d0813dc990b36b189d2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
geocoding_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geocoding_platform_interface
|
||||
sha256: "8848605d307d844d89937cdb4b8ad7dfa880552078f310fa24d8a460f6dddab4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: e946395fc608842bb2f6c914807e9183f86f3cb787f6b8f832753e5251036f02
|
||||
sha256: "149876cc5207a0f5daf4fdd3bfcf0a0f27258b3fe95108fa084f527ad0568f1b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.0"
|
||||
version: "12.0.0"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: "741579fa6c9e412984d2bdb2fbaa54e3c3f7587c60aeacfe6e058358a11f40f8"
|
||||
sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
version: "4.6.1"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: "16cdb6b8d3685d3e07a7e54e88e97106f4fae9e496191f33e8bb389cce14f198"
|
||||
sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.4"
|
||||
version: "2.3.7"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "6c8d494d6948757c56720b778af742f6973f31fca1f702a7539b8917e4a2468a"
|
||||
sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
version: "4.2.4"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: "59083f7e0871b78299918d92bf930a14377f711d2d1156c558cd5ebae6c20d58"
|
||||
sha256: "7a22f400d831f924a89d931ba126a10e6b8b437f31e6b9311320435f3e1571bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "4.0.0"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: a92fae29779d5c6dc60e8411302f5221ade464968fe80a36d330e80a71f087af
|
||||
sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "0.2.3"
|
||||
google_identity_services_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_identity_services_web
|
||||
sha256: "000b7a31e1fa17ee04b6c0553a2b2ea18f9f9352e4dcc0c9fcc785cf10f2484e"
|
||||
sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
version: "0.3.1+4"
|
||||
google_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps
|
||||
sha256: "463b38e5a92a05cde41220a11fd5eef3847031fef3e8cf295ac76ec453246907"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
google_maps_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_maps_flutter
|
||||
sha256: "2e302fa3aaf4e2a297f0342d83ebc5e8e9f826e9a716aef473fe7f404ec630a7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
google_maps_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_android
|
||||
sha256: "60a005bf1ba8d178144e442f6e2d734b0ffc2cc800a05415388472f934ad6d6a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.14.4"
|
||||
google_maps_flutter_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_ios
|
||||
sha256: "3a484846fc56f15e47e3de1f5ea80a7ff2b31721d2faa88f390f3b3cf580c953"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
google_maps_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_platform_interface
|
||||
sha256: "4f6930fd668bf5d40feb2695d5695dbc0c35e5542b557a34ad35be491686d2ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
google_maps_flutter_web:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_maps_flutter_web
|
||||
sha256: ff39211bd25d7fad125d19f757eba85bd154460907cd4d135e07e3d0f98a4130
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.10"
|
||||
google_sign_in:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_sign_in
|
||||
sha256: "8f8b94880f2753ccb796744259da529674e49b9af2e372abf6978c590c0ebfef"
|
||||
sha256: "0b8787cb9c1a68ad398e8010e8c8766bfa33556d2ab97c439fb4137756d7308f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.6"
|
||||
version: "6.2.1"
|
||||
google_sign_in_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -404,10 +500,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_sign_in_ios
|
||||
sha256: "81495441405c138e3c638f5097bebaa0db644567b3976e08944cfb8926ff2e6d"
|
||||
sha256: a058c9880be456f21e2e8571c1126eaacd570bdc5b6c6d9d15aea4bdf22ca9fe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.6.5"
|
||||
version: "5.7.6"
|
||||
google_sign_in_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -420,26 +516,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_sign_in_web
|
||||
sha256: b48263e47f9493ba4120ccdfffe7412549ee297e82b97be9b8fa16ea8919ffbe
|
||||
sha256: "042805a21127a85b0dc46bba98a37926f17d2439720e8a459d27045d8ef68055"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.0+4"
|
||||
version: "0.12.4+2"
|
||||
gql:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gql
|
||||
sha256: "0bdd22c3a9464970ae590559e4f0568769b219dda9e94cb10c4cf999a3e263f7"
|
||||
sha256: "8ecd3585bb9e40d671aa58f52575d950670f99e5ffab18e2b34a757e071a6693"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1-alpha+1705114622973"
|
||||
version: "1.0.1-alpha+1717789143880"
|
||||
gql_dedupe_link:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gql_dedupe_link
|
||||
sha256: e5359dd0c7a38f95e2b12f6ab305989a4e30028e4032825c8e9f610150999c69
|
||||
sha256: "10bee0564d67c24e0c8bd08bd56e0682b64a135e58afabbeed30d85d5e9fea96"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4-alpha+1705114623057"
|
||||
version: "2.0.4-alpha+1715521079596"
|
||||
gql_error_link:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -460,18 +556,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gql_http_link
|
||||
sha256: "1f922eed1b7078fdbfd602187663026f9f659fe9a9499e2207b5d5e01617f658"
|
||||
sha256: ef6ad24d31beb5a30113e9b919eec20876903cc4b0ee0d31550047aaaba7d5dd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1+1"
|
||||
version: "1.1.0"
|
||||
gql_link:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gql_link
|
||||
sha256: "63941513a688d856546f0c3218e7ad94d47fc6e04662dcdb06de92a4cde2d7db"
|
||||
sha256: "70fd5b5cbcc50601679f4b9fea3bcc994e583f59cfec7e1fec11113074b1a565"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1-alpha+1705114622987"
|
||||
version: "1.0.1-alpha+1717789143896"
|
||||
gql_transform_link:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -484,10 +580,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: graphql
|
||||
sha256: d066e53446166c12537458386b507f7426f2b8801ebafc184576aab3cbc64d56
|
||||
sha256: "62f31433ba194eda7b81a812a83c3d9560766cec5ac0210ea4a3e677c91b8df4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.0-beta.7"
|
||||
version: "5.2.0-beta.8"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -516,10 +612,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -528,6 +624,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
iconify_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: iconify_flutter
|
||||
sha256: a2d5d36a8381331c8d4e0a6b71378fca51bd6d15e38192568a30c0a91133a9af
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -540,10 +644,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd"
|
||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.7"
|
||||
version: "1.1.2"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -588,10 +692,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
|
||||
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.3"
|
||||
version: "2.10.0"
|
||||
image_picker_web:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker_web
|
||||
sha256: b5cf4faf66714f17b3e86b37a39d19743603163a08b968b28cadfc5df1dc2b75
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -612,10 +724,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
version: "0.7.1"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -628,10 +740,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: latlong2
|
||||
sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b"
|
||||
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.0"
|
||||
version: "0.9.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -648,38 +784,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
location:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: location
|
||||
sha256: "37ffdadcd4b1498b769824f45ebb4de8ed46663a4a67ac27b33a590ee486579f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
location_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: location_platform_interface
|
||||
sha256: "2ecde6bb0f88032b0bbbde37e18975b4468711dd92619c2235cc0c0ee93b4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
location_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: location_web
|
||||
sha256: "924da8436db7ded5eef92a7ef3ae6aa3715831a93965376c91738f586302350e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
logger:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logger
|
||||
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
|
||||
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2+1"
|
||||
version: "2.4.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.15.0"
|
||||
mgrs_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -720,22 +880,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
version: "1.9.0"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_drawing
|
||||
sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -788,42 +956,50 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8"
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
version: "11.3.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e
|
||||
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.1.0"
|
||||
version: "12.0.12"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
version: "9.4.5"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+2"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
||||
sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.12.0"
|
||||
version: "4.2.2"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -840,14 +1016,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.0"
|
||||
pigeon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pigeon
|
||||
sha256: "5a79fd0b10423f6b5705525e32015597f861c31220b522a67d1e6b580da96719"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -868,10 +1036,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.3"
|
||||
version: "3.9.1"
|
||||
polylabel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -888,14 +1056,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -912,6 +1072,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
sanitize_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sanitize_html
|
||||
sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -977,10 +1145,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -993,10 +1161,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.11.1"
|
||||
state_notifier:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1009,10 +1177,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1033,10 +1209,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.7.2"
|
||||
top_snackbar_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1065,10 +1241,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
|
||||
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.2"
|
||||
version: "4.4.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1117,14 +1293,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.17"
|
||||
watcher:
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
name: vm_service
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "14.2.5"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1174,5 +1358,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.6 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
||||
30
pubspec.yaml
30
pubspec.yaml
@@ -36,9 +36,11 @@ dependencies:
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
geolocator: ^10.1.0
|
||||
geocoding: ^2.1.1
|
||||
dio: ^4.0.6
|
||||
# geolocator: ^10.1.0
|
||||
geolocator: ^12.0.0
|
||||
# geocoding: ^2.1.1
|
||||
# dio: ^4.0.6
|
||||
dio: ^5.3.3
|
||||
flutter_riverpod: ^1.0.4
|
||||
flutter_hooks: ^0.18.5+1
|
||||
hooks_riverpod: ^1.0.4
|
||||
@@ -46,21 +48,37 @@ dependencies:
|
||||
shared_preferences: ^2.0.1
|
||||
flutter_map: ^6.1.0
|
||||
latlong2: ^0.9.0
|
||||
google_sign_in: ^6.1.6
|
||||
# google_sign_in: ^6.1.6
|
||||
google_sign_in: ^6.2.1
|
||||
crypton: ^2.2.1
|
||||
path_provider: ^2.0.2
|
||||
intl: ^0.17.0
|
||||
graphql: ^5.1.3
|
||||
top_snackbar_flutter: ^3.1.0
|
||||
permission_handler: ^11.0.0
|
||||
# permission_handler: ^11.0.0
|
||||
permission_handler: ^11.3.1
|
||||
mobkit_dashed_border: ^0.0.5
|
||||
file_picker: ^6.1.1
|
||||
open_file: ^3.3.2
|
||||
image_picker: ^1.0.7
|
||||
# image_picker: ^1.0.7
|
||||
image_picker: ^1.1.2
|
||||
video_player: ^2.7.2
|
||||
photo_view: ^0.14.0
|
||||
mime: ^1.0.4
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
iconify_flutter: ^0.0.5
|
||||
fluentui_system_icons: ^1.1.226
|
||||
eva_icons_flutter: ^3.1.0
|
||||
image_picker_web: ^4.0.0
|
||||
dart_nominatim: ^1.0.1
|
||||
location: ^6.0.2
|
||||
# camera_web: ^0.3.5
|
||||
camera: ^0.11.0+2
|
||||
flutter_image_compress: ^1.1.0
|
||||
path: ^1.8.3
|
||||
# google_maps_flutter: ^2.2.6
|
||||
google_maps_flutter: ^2.9.0
|
||||
google_maps_flutter_web: ^0.5.10
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
100
web/index.html
100
web/index.html
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
@@ -24,36 +25,93 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="absensi_sas_flutter">
|
||||
<!-- google sign in browser -->
|
||||
<meta name="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"
|
||||
content="856240587825-klh0dfjc44bovajg1rpq5vbvs4g7rh5j.apps.googleusercontent.com">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
||||
<title>absensi_sas_flutter</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<script>
|
||||
// The value below is injected by flutter build, do not touch.
|
||||
var serviceWorkerVersion = null;
|
||||
</script>
|
||||
<!-- This script adds the flutter initialization JS code -->
|
||||
<script src="flutter.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener('load', function(ev) {
|
||||
// Download main.dart.js
|
||||
_flutter.loader.loadEntrypoint({
|
||||
serviceWorker: {
|
||||
serviceWorkerVersion: serviceWorkerVersion,
|
||||
<!-- <script>
|
||||
|
||||
window.flutterLocationCallback = (data) => {
|
||||
console.log('Received data from Flutter:', data);
|
||||
window.flutterLocationData = data;
|
||||
};
|
||||
|
||||
function getCurrentLocation(callback) {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
position => callback(position.coords.latitude, position.coords.longitude),
|
||||
error => console.error('Geolocation error:', error)
|
||||
);
|
||||
} else {
|
||||
console.error("Geolocation is not supported by this browser.");
|
||||
}
|
||||
}
|
||||
|
||||
function sendLocationToServer(latitude, longitude) {
|
||||
fetch('https://devone.aplikasi.web.id/absensi/apiGetUserLocation.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
onEntrypointLoaded: function(engineInitializer) {
|
||||
engineInitializer.initializeEngine().then(function(appRunner) {
|
||||
appRunner.runApp();
|
||||
});
|
||||
}
|
||||
body: `latitude=${latitude}&longitude=${longitude}`
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Server response:', data);
|
||||
// if (window.flutterLocationCallback) {
|
||||
// window.flutterLocationCallback(data);
|
||||
// } else {
|
||||
// console.error('flutterLocationCallback is not defined');
|
||||
// }
|
||||
|
||||
const queryString = new URLSearchParams(data).toString();
|
||||
const newUrl = `${window.location.origin}${window.location.pathname}?${queryString}`;
|
||||
|
||||
// Update the URL with query parameters
|
||||
window.history.replaceState(null, '', newUrl);
|
||||
})
|
||||
.catch(error => console.error('Request failed', error));
|
||||
}
|
||||
|
||||
function getLocationAndSend() {
|
||||
getCurrentLocation((latitude, longitude) => {
|
||||
sendLocationToServer(latitude, longitude);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
|
||||
</script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
<!-- <script>
|
||||
// var API_KEY = "AIzaSyCiN7EeJsUpXVLQKFfrj3sE5OTKebjpzek";
|
||||
// var API_KEY = "AIzaSyCztj0X7erH-uqotFfe_Yd-Nt0oBJu6FeM";
|
||||
|
||||
var API_KEY = "AIzaSyAVUr4Ku4O1HlSkK8n9KGnUyqvsXBL-yfs";
|
||||
var script = document.createElement('script');
|
||||
script.src = "https://maps.googleapis.com/maps/api/js?key=" + API_KEY;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// tambahan
|
||||
// window.onload = function () {
|
||||
// getLocationAndSend();
|
||||
// };
|
||||
// tambahan
|
||||
</script> -->
|
||||
|
||||
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAVUr4Ku4O1HlSkK8n9KGnUyqvsXBL-yfs"></script>
|
||||
<script src ="https://maps.googleapis.com/maps/api/js?key=AIzaSyAVUr4Ku4O1HlSkK8n9KGnUyqvsXBL-yfs&callback=initMap&v=weekly&libraries=marker" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
59
web/index_backup_23_08_2024.html
Normal file
59
web/index_backup_23_08_2024.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="absensi_sas_flutter">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>absensi_sas_flutter</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<script>
|
||||
// The value below is injected by flutter build, do not touch.
|
||||
var serviceWorkerVersion = null;
|
||||
</script>
|
||||
<!-- This script adds the flutter initialization JS code -->
|
||||
<script src="flutter.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener('load', function(ev) {
|
||||
// Download main.dart.js
|
||||
_flutter.loader.loadEntrypoint({
|
||||
serviceWorker: {
|
||||
serviceWorkerVersion: serviceWorkerVersion,
|
||||
},
|
||||
onEntrypointLoaded: function(engineInitializer) {
|
||||
engineInitializer.initializeEngine().then(function(appRunner) {
|
||||
appRunner.runApp();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user