diff --git a/android/app/build.gradle b/android/app/build.gradle index 22c7ca9..c95be58 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -27,7 +27,8 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { namespace "com.example.absensi_sas_flutter" - compileSdkVersion flutter.compileSdkVersion + // compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { @@ -69,4 +70,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation platform('com.google.firebase:firebase-bom:32.7.0') } diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..42861c9 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,47 @@ +{ + "project_info": { + "project_number": "144623481644", + "project_id": "absensisasflutter1", + "storage_bucket": "absensisasflutter1.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:144623481644:android:a3095914ebb628c3137289", + "android_client_info": { + "package_name": "com.example.absensi_sas_flutter" + } + }, + "oauth_client": [ + { + "client_id": "144623481644-idkdin499id9pgtf641qigp565tls3ck.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.example.absensi_sas_flutter", + "certificate_hash": "eb90aa1109a494da4a21790a7feafae5b7c89046" + } + }, + { + "client_id": "144623481644-pg07qfg4n7rnfdmq03gtlilcb0dq2kar.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAJKBcqLWpET5bwanTaUbPWEXlu0KwewH4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "144623481644-pg07qfg4n7rnfdmq03gtlilcb0dq2kar.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6726e19..8925ec9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,7 @@ + + + generateRoute(RouteSettings settings) { + + // test flutter map + if (settings.name == testFlutterMapRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)), + child: TestFlutterMap(), + ); + }); + } + + // splash screen + if (settings.name == splashRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)), + child: SplashScreen(), + ); + }); + } + + // login + if (settings.name == loginRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)), + child: LoginScreen(), + ); + }); + } + + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: const EdgeInsets.all(0)), + child: LoginScreen(), + ); + }); + } +} diff --git a/lib/main.dart b/lib/main.dart index dda5554..0de820a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import '../app/route.dart'; +import '../test_map.dart'; void main() { runApp(const MyApp()); @@ -11,115 +13,19 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + debugShowCheckedModeBanner: false, + title: 'Absensi SAS', + // theme: ThemeData( + // colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + // useMaterial3: true, + // ), theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a blue toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, + primarySwatch: Colors.orange, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + // home: TestMap(), + initialRoute: loginRoute, + // initialRoute: testFlutterMapRoute, + onGenerateRoute: AppRoute.generateRoute, ); } } diff --git a/lib/screen/login/login_screen.dart b/lib/screen/login/login_screen.dart new file mode 100644 index 0000000..3d268c9 --- /dev/null +++ b/lib/screen/login/login_screen.dart @@ -0,0 +1,213 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; + +import '../../app/constant.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + GoogleSignInAccount? _currentUser; + final GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + 'email', + 'https://www.googleapis.com/auth/contacts.readonly', + ], + ); + + @override + void initState() { + _googleSignIn.onCurrentUserChanged.listen((account) { + setState(() { + _currentUser = account; + }); + }); + _googleSignIn.signInSilently(); + super.initState(); + } + + Future _handleSignIn() async { + try { + await _googleSignIn.signIn(); + } catch (error) { + if (kDebugMode) { + print(error); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: Constant.getActualYPhone(context: context, y: 100), + ), + _currentUser != null + ? Container( + child: ListTile( + leading: GoogleUserCircleAvatar( + identity: _currentUser!, + ), + title: Text( + _currentUser!.displayName ?? "", + ), + subtitle: Text( + _currentUser!.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: 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 _handleSignIn(); + }, + child: 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( + borderRadius: BorderRadius.circular(24), + side: BorderSide( + color: Color.fromRGBO(145, 158, 171, 0.32), + ), + ), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screen/splash/splash_screen.dart b/lib/screen/splash/splash_screen.dart new file mode 100644 index 0000000..5c79600 --- /dev/null +++ b/lib/screen/splash/splash_screen.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import '../../app/constant.dart'; + +class SplashScreen extends StatelessWidget { + const SplashScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: ListView( + children: [ + Container( + // width: Constant.getActualXPhone(context: context, x: 390), + height: Constant.getActualYPhone(context: context, y: 844), + decoration: BoxDecoration( + color: Colors.blue, // Ganti dengan warna dan gambar yang sesuai + image: DecorationImage( + fit: BoxFit.fill, + image: AssetImage( + 'images/background_splash.png'), // Ganti dengan path gambar yang diinginkan + ), + ), + child: Align( + alignment: Alignment.center, + child: Container( + width: Constant.getActualXPhone(context: context, x: 200), + height: Constant.getActualYPhone(context: context, y: 200), + decoration: BoxDecoration( + // color: Colors.green, + image: DecorationImage( + // fit: BoxFit.contain, + image: AssetImage('images/logo_sismedika_rectangle.png'), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/test_flutter_map.dart b/lib/test_flutter_map.dart new file mode 100644 index 0000000..ea5b2ae --- /dev/null +++ b/lib/test_flutter_map.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; + +class TestFlutterMap extends StatefulWidget { + const TestFlutterMap({super.key}); + + @override + State createState() => _TestFlutterMapState(); +} + +class _TestFlutterMapState extends State { + late final MapController _mapController; + double _rotation = 0; + + static const _london = LatLng(51.5, -0.09); + static const _paris = LatLng(48.8566, 2.3522); + static const _dublin = LatLng(53.3498, -6.2603); + + static const _kantor = LatLng(-7.538761, 110.795699); + + static const _markers = [ + Marker( + width: 10, + height: 10, + point: _kantor, + child: FlutterLogo( + key: ValueKey('green'), + ), + ), + ]; + + @override + void initState() { + super.initState(); + _mapController = MapController(); + } + + @override + Widget build(BuildContext context) { + return FlutterMap( + mapController: _mapController, + options: MapOptions( + initialCenter: LatLng(-7.538761, 110.795699), + initialZoom: 5, + maxZoom: 20, + minZoom: 3, + ), + children: [ + // openStreetMapTileLayer, + TileLayer( + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + subdomains: ['a', 'b', 'c'] + // Use the recommended flutter_map_cancellable_tile_provider package to + // support the cancellation of loading tiles. + // tileProvider: , + ), + const MarkerLayer( + markers: _markers, + ), + ], + ); + } +} diff --git a/lib/test_map.dart b/lib/test_map.dart new file mode 100644 index 0000000..fbca97c --- /dev/null +++ b/lib/test_map.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:geocoding/geocoding.dart'; + +class TestMap extends StatefulWidget { + const TestMap({super.key}); + + @override + State createState() => _TestMapState(); +} + +class _TestMapState extends State { + /// Determine the current position of the device. + /// + /// When the location services are not enabled or permissions + /// are denied the `Future` will return an error. + Future> determinePosition() async { + bool serviceEnabled; + LocationPermission permission; + + // Test if location services are enabled. + serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + // Location services are not enabled don't continue + // accessing the position and request users of the + // App to enable the location services. + return Future.error('Location services are disabled.'); + } + + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + // Permissions are denied, next time you could try + // requesting permissions again (this is also where + // Android's shouldShowRequestPermissionRationale + // returned true. According to Android guidelines + // your App should show an explanatory UI now. + // return Future.error('Location permissions are denied'); + + return { + "message": "Ijinkan Lokasi di HP", + "error": true, + }; + } + } + + if (permission == LocationPermission.deniedForever) { + // Permissions are denied forever, handle appropriately. + // return Future.error( + // 'Location permissions are permanently denied, we cannot request permissions.'); + return { + "message": "Ijinkan Lokasi di HP", + "error": true, + }; + } + + // When we reach here, permissions are granted and we can + // continue accessing the position of the device. + Position position = await Geolocator.getCurrentPosition(); + + return { + "position": position, + "message": "Berhasil mendapatkan posisi", + "error": false + }; + } + + String latitude = ""; + String longitude = ""; + String addressFromCoordinat = ""; + String jarak = ""; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Test Map'), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (latitude.isNotEmpty && longitude.isNotEmpty) + ? Text('Coordinat Akhir: $latitude , $longitude') + : SizedBox.shrink(), + (addressFromCoordinat.isNotEmpty) + ? Text('Address : $addressFromCoordinat') + : SizedBox.shrink(), + (jarak.isNotEmpty) ? Text('Jarak : $jarak') : SizedBox.shrink(), + SizedBox( + height: 10, + ), + Text('SFA Klodran : -7.5350973, 110.7921524'), + SizedBox( + height: 10, + ), + ElevatedButton( + child: Text('Determine Position'), + onPressed: () async { + print('ABSENSI'); + Map posx = await determinePosition(); + print('${posx}'); + + if (posx['error'] == false) { + Position position = posx['position']; + List placemarks = await placemarkFromCoordinates( + position.latitude, position.longitude); + print('placemark : ${placemarks}'); + + 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(); + }); + } + }, + ), + SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: () { + setState(() { + latitude = ""; + longitude = ""; + addressFromCoordinat = ""; + jarak = ""; + }); + }, + child: Text('Clear'), + ), + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index fa5696f..59f3ded 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,8 +70,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg + assets: + - images/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see