From 77b7a133e46f93edd4eb687ecf346df684d40ec9 Mon Sep 17 00:00:00 2001 From: sindhu Date: Fri, 21 Feb 2025 03:45:08 +0700 Subject: [PATCH] step 3 : login proses --- android/app/build.gradle | 6 +- images/logo.png | Bin 0 -> 10695 bytes images/splashatas.png | Bin 0 -> 3241 bytes images/splashbawah.png | Bin 0 -> 3276 bytes images/vektoratas.png | Bin 0 -> 1729 bytes lib/app/app_extension.dart | 5 + lib/app/constant.dart | 101 ++++++ lib/app/route.dart | 57 ++++ lib/main.dart | 138 ++------ lib/model/auth_model.dart | 83 +++++ lib/provider/auth_provider.dart | 0 lib/provider/current_user_provider.dart | 4 + lib/provider/dio_provider.dart | 4 + lib/repository/auth_repository.dart | 24 ++ lib/repository/base_repository.dart | 106 ++++++ lib/screen/home/home_screen.dart | 66 ++++ lib/screen/login/login_provider.dart | 89 +++++ lib/screen/login/login_screen.dart | 421 ++++++++++++++++++++++++ lib/screen/splash/splash_screen.dart | 113 +++++++ pubspec.yaml | 8 +- 20 files changed, 1111 insertions(+), 114 deletions(-) create mode 100644 images/logo.png create mode 100644 images/splashatas.png create mode 100644 images/splashbawah.png create mode 100644 images/vektoratas.png create mode 100644 lib/app/app_extension.dart create mode 100644 lib/app/constant.dart create mode 100644 lib/app/route.dart create mode 100644 lib/model/auth_model.dart create mode 100644 lib/provider/auth_provider.dart create mode 100644 lib/provider/current_user_provider.dart create mode 100644 lib/provider/dio_provider.dart create mode 100644 lib/repository/auth_repository.dart create mode 100644 lib/repository/base_repository.dart create mode 100644 lib/screen/home/home_screen.dart create mode 100644 lib/screen/login/login_provider.dart create mode 100644 lib/screen/login/login_screen.dart create mode 100644 lib/screen/splash/splash_screen.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index e542ec3..d581526 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,7 +7,8 @@ plugins { android { namespace = "com.example.fluttervoice2text" - compileSdk = flutter.compileSdkVersion + // compileSdk = flutter.compileSdkVersion + compileSdk = 35 ndkVersion = flutter.ndkVersion compileOptions { @@ -24,7 +25,8 @@ android { applicationId = "com.example.fluttervoice2text" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + // minSdk = flutter.minSdkVersion + minSdk 23 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3d175e98af69c372e3a26934429a1ab524f24cd0 GIT binary patch literal 10695 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 literal 0 HcmV?d00001 diff --git a/images/splashatas.png b/images/splashatas.png new file mode 100644 index 0000000000000000000000000000000000000000..7adff617cc58df4406347aab4ee091cd50984842 GIT binary patch literal 3241 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 b+$?v0#qdGR6Oti!sqgg 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 KMNUMnLSTYxDQ#E) literal 0 HcmV?d00001 diff --git a/images/vektoratas.png b/images/vektoratas.png new file mode 100644 index 0000000000000000000000000000000000000000..f4260354436083795994429a372ae1038f3827fe GIT binary patch literal 1729 zcmW-id0bOh7RTR9URItONRXg|1lbHAzS0(^qX9xA1Q7y3GROcvD4_5G5!oY@N21Y+ zSXNoY5D?i!py0}mv+JLhx%x!?HucxhHbhk^ct57S04KyOse#4qfa%OS1JEX3z@ zPRE^1UkfnCDPNnNNm+_Z5U(`;KWV9L{f~b>eU+u@S-16gm6iH_=Bczjc~gc0YQJPh zm(5uDy_U7FvmB>IiPZY-8(F~_PMglDz8U)p{};ECE(|vGxGq8SQ6TWE=acIlUVrVZ z7=CasjGDIY)&8F*IQypbc!EQx{fled#i~=D2rwC#>e%}poqiYFb0^o|bv%StQN(R+ zvsSwyz-WyBV_A3|8=V~CaE&A&`EhOWkB*kH{1iEEXR?~oL4*5P;m+6NZ@*$J)<2ai z^!kC=d(G!VRyb|#EBAv0pf`RQ3Lz5*ePPSkeSZ^xKe^B9s%VdVMK=Dv0H>;!YPy8RoiWt>+%Ao3N~8v<|C<>aMGme6|Hm%RtmXD~YbuN62jrv7x$wzVV`DZ6( z-neqyp2!#UBr~RgXh9i&?tU(wvqp}go*JWT+!0>Hc&^ZXO7(zMhee9DPbZ`7KRwmd zY5q&Lyy`}1PevL)_>cNaeYlrbLs9+s-hmUSI51DYQ)UFn@;&6X~@OFbyx*F66$-5(_*1sh? z_ogq?fFU0+M*}sPF$di^gkwrVj5GT)TQM%hg2_L!?C)AA$F4#6QR?!APsYdSsxp+7 zmfwX$?GodNEGGFf8`a~0<}~=|V@9hv2c*+rc!bgF!~qKpV9gVTsz8qQk%>W{5Rw$u ztTU4A+_rP>Dw+o3xg(3{^IM8HK4C$lblU#@yoM{L3@4ZaQuJZtz&U*5)58xL&+_Hi zd5Y}38c(dTW|>ROgsYgJnle~`Uc#JF3o)*g4rh;&uJ8qbEk*W?8gHer#%_>h`r7E0 zHO8UHcohla z34p>Mt(Y1R9H+bb7QnFDI|4f=?~AFX~pgvTA#d9u}v!nn*7yPYd~qN zl{DJ!%Q{MMjf_Op_=3Mr`7buF9@~Y_*}eoCKRjW%zqdP40JaYI3wnp62y6#K;7;0H zip|>&N#&gaTnI%Lc-)de+{+9PFJ>^aQF2)WP>P{sglB9oI6wrOiiTO4(GCjk0 zLyND{|A7Qr5}1J#ItEQLBT5?p)NPz34G?0AE2gl>6O=gm=$K1S5FNt`$|Md9bW)3+ z0PkY9@&$L}b$R~3XHdith generateRoute(RouteSettings settings) { + // splash screen + if (settings.name == splashRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)), + child: SplashScreen(), + ); + }); + } + + // login screen + if (settings.name == loginRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)), + child: LoginScreen(), + ); + }); + } + + // home screen + if (settings.name == homeRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)), + child: HomeScreen(), + ); + }); + } + + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + padding: const EdgeInsets.all(0), + textScaler: TextScaler.linear(1.0)), + child: SplashScreen(), + ); + }); + } +} diff --git a/lib/main.dart b/lib/main.dart index 8e94089..a4e15a3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,125 +1,45 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'app/route.dart'; void main() { - runApp(const MyApp()); + WidgetsFlutterBinding.ensureInitialized(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitUp, + ]).then( + (value) => runApp( + const ProviderScope( + child: MyApp(), + ), + ), + ); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'Voice To Text', 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 purple 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.blue, ), - 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. + scrollBehavior: const MaterialScrollBehavior().copyWith( + dragDevices: { + PointerDeviceKind.mouse, + PointerDeviceKind.touch, + PointerDeviceKind.stylus, + PointerDeviceKind.unknown, + }, + ), + debugShowCheckedModeBanner: false, + initialRoute: splashRoute, + onGenerateRoute: AppRoute.generateRoute, ); } } diff --git a/lib/model/auth_model.dart b/lib/model/auth_model.dart new file mode 100644 index 0000000..d935922 --- /dev/null +++ b/lib/model/auth_model.dart @@ -0,0 +1,83 @@ +class AuthModel { + final String token; + final String host; + final UserModel model; + + AuthModel({ + required this.host, + required this.token, + required this.model, + }); + + Map toJson() { + return { + 'host':host, + 'token': token, + 'model': model.toJson(), + }; + } +} + +class UserModel { + final String userId; + final String username; + final String groupDashboard; + final String defaultSampleStationId; + final String staffName; + final String isCourier; + final String timeAutoLogout; + final String ip; + final String agent; + final String version; + final String lastLogin; + final int satelliteId; + + UserModel({ + required this.userId, + required this.username, + required this.groupDashboard, + required this.defaultSampleStationId, + required this.staffName, + required this.isCourier, + required this.timeAutoLogout, + required this.ip, + required this.agent, + required this.version, + required this.lastLogin, + required this.satelliteId, + }); + + factory UserModel.fromJson(Map json) { + return UserModel( + userId: json['M_UserID'] ?? '', + username: json['M_UserUsername'] ?? '', + groupDashboard: json['M_UserGroupDashboard'] ?? '', + defaultSampleStationId: json['M_UserDefaultT_SampleStationID'] ?? '', + staffName: json['M_StaffName'] ?? '', + isCourier: json['is_courier'] ?? '', + timeAutoLogout: json['time_autologout'] ?? '', + ip: json['ip'] ?? '', + agent: json['agent'] ?? '', + version: json['version'] ?? '', + lastLogin: json['last-login'] ?? '', + satelliteId: json['M_SatelliteID'] ?? 0, + ); + } + + Map toJson() { + return { + 'M_UserID': userId, + 'M_UserUsername': username, + 'M_UserGroupDashboard': groupDashboard, + 'M_UserDefaultT_SampleStationID': defaultSampleStationId, + 'M_StaffName': staffName, + 'is_courier': isCourier, + 'time_autologout': timeAutoLogout, + 'ip': ip, + 'agent': agent, + 'version': version, + 'last-login': lastLogin, + 'M_SatelliteID': satelliteId, + }; + } +} diff --git a/lib/provider/auth_provider.dart b/lib/provider/auth_provider.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/provider/current_user_provider.dart b/lib/provider/current_user_provider.dart new file mode 100644 index 0000000..e358c65 --- /dev/null +++ b/lib/provider/current_user_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../model/auth_model.dart'; + +final currentUserProvider = StateProvider((ref) => null); \ No newline at end of file diff --git a/lib/provider/dio_provider.dart b/lib/provider/dio_provider.dart new file mode 100644 index 0000000..7596edf --- /dev/null +++ b/lib/provider/dio_provider.dart @@ -0,0 +1,4 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final dioProvider = Provider((ref) => Dio()); \ No newline at end of file diff --git a/lib/repository/auth_repository.dart b/lib/repository/auth_repository.dart new file mode 100644 index 0000000..935d84c --- /dev/null +++ b/lib/repository/auth_repository.dart @@ -0,0 +1,24 @@ +import '../model/auth_model.dart'; +import 'base_repository.dart'; + +class AuthRepository extends BaseRepository { + AuthRepository({required super.dio}); + + Future login({ + required String username, + required String host, + required String password, + }) async { + final param = {"username": username, "password": password}; + // final service = "${Constant.baseUrl}xauth/login"; + final service = "http://$host/one-api/v1/system/auth/login"; + final resp = await post(param: param, service: service); + + final result = AuthModel( + host: host, + token: resp["data"]["token"], + model: UserModel.fromJson(resp["data"]["user"]), + ); + return result; + } +} diff --git a/lib/repository/base_repository.dart b/lib/repository/base_repository.dart new file mode 100644 index 0000000..a3496a2 --- /dev/null +++ b/lib/repository/base_repository.dart @@ -0,0 +1,106 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; + +abstract class BaseRepository { + final Dio dio; + BaseRepository({required this.dio}); + + // POST PAKE ContentType JSON + Future> post({ + required Map param, + required String service, + String? token, + }) async { + try { + final response = await dio.post( + // Constant.baseUrl + service, + service, + data: jsonEncode(param), + options: Options( + headers: token != null + ? { + HttpHeaders.contentTypeHeader: "application/json", + HttpHeaders.authorizationHeader: "Bearer $token", + } + : { + HttpHeaders.contentTypeHeader: "application/json", + }, + contentType: "application/json", + ), + ); + if (response.statusCode != 200) { + throw BaseRepositoryException( + message: "Invalid Http Response ${response.statusCode}", + ); + } + Map jsonData = jsonDecode(response.data); + if (jsonData["status"] != "OK") { + throw BaseRepositoryException( + message: jsonData["message"], + ); + } else { + return jsonData; + } + } on DioException catch (e) { + throw BaseRepositoryException(message: e.message ?? ""); + } on SocketException catch (e) { + throw BaseRepositoryException(message: e.message); + } on BaseRepositoryException catch (e) { + throw BaseRepositoryException(message: e.message); + } + } + + // GET Pake Content Type JSON + Future> get({ + // required Map param, + required String service, + String? token, + }) async { + try { + final response = await dio.get( + // Constant.baseUrl + service, + service, + // data: jsonEncode(param), + options: Options( + headers: token != null + ? { + HttpHeaders.contentTypeHeader: "application/json", + HttpHeaders.authorizationHeader: "Bearer $token", + } + : { + HttpHeaders.contentTypeHeader: "application/json", + }, + contentType: "application/json", + ), + ); + if (response.statusCode != 200) { + throw BaseRepositoryException( + message: "Invalid Http Response ${response.statusCode}", + ); + } + Map jsonData = jsonDecode(response.data); + if (jsonData["status"] != "OK") { + throw BaseRepositoryException( + message: jsonData["message"], + ); + } else { + return jsonData; + } + } on DioException catch (e) { + throw BaseRepositoryException(message: e.message ?? ""); + } on SocketException catch (e) { + throw BaseRepositoryException(message: e.message); + } on BaseRepositoryException catch (e) { + throw BaseRepositoryException(message: e.message); + } + } +} + +class BaseRepositoryException implements Exception { + final String message; + BaseRepositoryException({ + required this.message, + }); +} \ No newline at end of file diff --git a/lib/screen/home/home_screen.dart b/lib/screen/home/home_screen.dart new file mode 100644 index 0000000..ab34012 --- /dev/null +++ b/lib/screen/home/home_screen.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../app/constant.dart'; +import '../../app/route.dart'; +import '../../provider/current_user_provider.dart'; + +class HomeScreen extends HookConsumerWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ + SystemUiOverlay.bottom, + ]); + + final currentUser = ref.watch(currentUserProvider); + // final username = currentUser?.model.username ?? "-"; + final host = currentUser?.host ?? ""; + final isLoading = useState(false); + final userId = currentUser?.model.userId ?? ""; + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final userID = currentUser?.model.userId ?? "0"; + if (userID == "0") { + // not logged in + Navigator.of(context) + .pushNamedAndRemoveUntil(loginRoute, (route) => false); + return; + } + }); + return () {}; + }, [currentUser]); + + // listRiwayProvider + return GestureDetector( + onTap: () { + FocusManager.instance.primaryFocus!.unfocus(); + }, + child: Scaffold( + resizeToAvoidBottomInset: true, + backgroundColor: Constant.bgGrey, + body: Column( + children: [ + // atas + Image.asset( + 'images/vektoratas.png', + width: double.infinity, + fit: BoxFit.cover, + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 34, + ), + ), + Text(currentUser?.model.username ?? ""), + ], + ), + ), + ); + } +} diff --git a/lib/screen/login/login_provider.dart b/lib/screen/login/login_provider.dart new file mode 100644 index 0000000..9aa8638 --- /dev/null +++ b/lib/screen/login/login_provider.dart @@ -0,0 +1,89 @@ +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../app/constant.dart'; +import '../../model/auth_model.dart'; +import '../../provider/current_user_provider.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/auth_repository.dart'; +import '../../repository/base_repository.dart'; + +// 3. state provider +final loginProvider = StateNotifierProvider( + (ref) => LoginNotifier(ref: ref)); + +// 2. notifier +class LoginNotifier extends StateNotifier { + final Ref ref; + LoginNotifier({required this.ref}) : super(LoginStateInit()); + void login({ + required String username, + required String host, + required String password, + }) async { + try { + state = LoginStateLoading(); + final resp = await AuthRepository( + dio: ref.read(dioProvider), + ).login( + username: username, + host: host, + password: password, + ); + + // print(resp); + state = LoginStateDone(model: resp); + //Simpan ke token + final shared = await SharedPreferences.getInstance(); + final token = jsonEncode({ + "host":host, + "date": DateTime.now().toString(), + "model": resp.model, + "token": resp.token + }); + await shared.setString(Constant.bearerName, token); + ref.read(currentUserProvider.notifier).state = resp; + + // print(shared.getString(Constant.bearerName)); + } catch (e) { + if (e is BaseRepositoryException) { + state = LoginStateError(message: e.message); + } else { + state = LoginStateError(message: e.toString()); + } + } + } +} + +// 1. state +abstract class LoginState extends Equatable { + final DateTime date; + const LoginState(this.date); + @override + List get props => [date]; +} + +class LoginStateInit extends LoginState { + LoginStateInit() : super(DateTime.now()); +} + +class LoginStateLoading extends LoginState { + LoginStateLoading() : super(DateTime.now()); +} + +class LoginStateError extends LoginState { + final String message; + LoginStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class LoginStateDone extends LoginState { + final AuthModel model; + LoginStateDone({ + required this.model, + }) : super(DateTime.now()); +} diff --git a/lib/screen/login/login_screen.dart b/lib/screen/login/login_screen.dart new file mode 100644 index 0000000..e22a1d5 --- /dev/null +++ b/lib/screen/login/login_screen.dart @@ -0,0 +1,421 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:toastification/toastification.dart'; +import '../../app/route.dart'; + +import '../../app/constant.dart'; +import '../../provider/current_user_provider.dart'; +import 'login_provider.dart'; + +class LoginScreen extends HookConsumerWidget { + const LoginScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ + SystemUiOverlay.bottom, + ]); + + final usernameCtr = useTextEditingController( + text: "", + ); + + final passwordCtr = useTextEditingController( + text: "", + ); + + final hostCtr = useTextEditingController( + text: "", + ); + + final isLoading = useState(false); + final isSuccess = useState(false); + + ToastificationItem? toastItem; + + void showLongToast( + String title, + String message, + String typeToast, + Duration waktu, + ) { + if (typeToast == "success") { + toastItem = toastification.show( + context: context, + title: Text(title), + description: Text(message), + autoCloseDuration: waktu, + type: ToastificationType.success, + ); + } else if (typeToast == "error") { + toastItem = toastification.show( + context: context, + title: Text(title), + description: Text(message), + autoCloseDuration: waktu, + type: ToastificationType.error, + style: ToastificationStyle.fillColored, + ); + } + } + + void hideToast() { + if (toastItem != null) { + toastification.dismissAll(); + } + } + + // proses login + ref.listen(loginProvider, (prev, next) { + if (next is LoginStateLoading) { + isLoading.value = true; + } else if (next is LoginStateError) { + isLoading.value = false; + // errorMessage.value = next.message; + showLongToast( + 'Error', + next.message, + 'error', + Duration(seconds: 3), + ); + } else if (next is LoginStateDone) { + hideToast(); + isLoading.value = false; + isSuccess.value = true; + ref.read(currentUserProvider.notifier).state = next.model; + Navigator.of(context) + .pushNamedAndRemoveUntil(homeRoute, (route) => false); + } + }); + + void login() { + if (usernameCtr.text.isEmpty || + passwordCtr.text.isEmpty || + hostCtr.text.isEmpty) { + showLongToast( + 'Error', + 'Inputan wajib diisi', + 'error', + Duration(seconds: 3), + ); + } else { + // print('proses login'); + ref.read(loginProvider.notifier).login( + username: usernameCtr.text, + host: hostCtr.text, + password: passwordCtr.text, + ); + } + } + + return GestureDetector( + onTap: () { + FocusManager.instance.primaryFocus!.unfocus(); + }, + child: Scaffold( + resizeToAvoidBottomInset: true, + backgroundColor: Constant.textWhite, + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // atas + Image.asset( + 'images/vektoratas.png', + width: double.infinity, + fit: BoxFit.cover, + ), + + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 40, + ), + ), + // konten didalamnya + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Selamat Datang', + style: Constant.title_700(context: context).copyWith( + color: Constant.textBlack, + ), + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Silahkan masuk untuk mengakses akun Anda', + style: Constant.title_400(context: context).copyWith( + color: Constant.textBlack, + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 64, + ), + ), + + // inputan + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Username', + style: Constant.title_400(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 16, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: TextField( + controller: usernameCtr, + decoration: InputDecoration( + hintText: "Masukkan Username", + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Password', + style: Constant.title_400(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 16, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: TextField( + controller: passwordCtr, + obscureText: true, + decoration: InputDecoration( + hintText: "Masukkan Password", + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Host', + style: Constant.title_400(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 16, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: TextField( + controller: hostCtr, + decoration: InputDecoration( + hintText: "Masukkan Host", + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 64, + ), + ), + // inputan + + // button login + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: SizedBox( + width: double.infinity, + height: Constant.getActualYPhone( + context: context, + y: 48, + ), + child: ElevatedButton( + onPressed: () async { + (isLoading.value || (isSuccess.value == true)) + ? null + : login(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Constant.bgButton, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 8, + shadowColor: Constant.bgButton.withOpacity(0.24), + ), + child: Text( + (isLoading.value) ? 'Loading...' : 'LOGIN', + style: Constant.titleButton500(context: context).copyWith( + color: Constant.textWhite, + ), + ), + ), + ), + ), + // konten didalamnya + + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 60, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screen/splash/splash_screen.dart b/lib/screen/splash/splash_screen.dart new file mode 100644 index 0000000..64bfa2b --- /dev/null +++ b/lib/screen/splash/splash_screen.dart @@ -0,0 +1,113 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../app/constant.dart'; +import '../../app/route.dart'; +import '../../model/auth_model.dart'; +import '../../provider/current_user_provider.dart'; + +class SplashScreen extends HookConsumerWidget { + const SplashScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ + SystemUiOverlay.bottom, + ]); + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final shared = await SharedPreferences.getInstance(); + final bearerString = shared.getString(Constant.bearerName); + + if (bearerString == null || bearerString == "null") { + Timer(const Duration(seconds: 3), () { + Navigator.of(context).pushNamedAndRemoveUntil( + loginRoute, + (route) => false, + ); + }); + return; + } + + final xmodel = jsonDecode(bearerString); + if (xmodel == null) return; + + final authModel = AuthModel( + host: xmodel["host"], + token: xmodel["token"], + model: UserModel( + userId: xmodel["model"]['M_UserID'], + username: xmodel["model"]['M_UserUsername'], + groupDashboard: xmodel["model"]['M_UserGroupDashboard'], + defaultSampleStationId: xmodel["model"] + ['M_UserDefaultT_SampleStationID'], + staffName: xmodel["model"]['M_StaffName'], + isCourier: xmodel["model"]['is_courier'], + timeAutoLogout: xmodel["model"]['time_autologout'], + ip: xmodel["model"]['ip'], + agent: xmodel["model"]['agent'], + version: xmodel["model"]['version'], + lastLogin: xmodel["model"]['last-login'], + satelliteId: xmodel["model"]['M_SatelliteID'], + ), + ); + + ref.read(currentUserProvider.notifier).state = authModel; + + Timer(const Duration(seconds: 3), () { + Navigator.of(context).pushNamedAndRemoveUntil( + homeRoute, + (route) => false, + ); + }); + }); + return () {}; + }, []); + + return Scaffold( + backgroundColor: Constant.textWhite, + body: Column( + children: [ + // Bagian atas + Align( + alignment: Alignment.topRight, + child: SizedBox( + width: Constant.getActualXPhone(context: context, x: 246), + child: Image.asset( + 'images/splashatas.png', + fit: BoxFit.fitWidth, + ), + ), + ), + Spacer(), + // Logo di tengah + Center( + child: Image.asset( + 'images/logo.png', + width: Constant.getActualXPhone(context: context, x: 164), + ), + ), + Spacer(), + // Bagian bawah + Align( + alignment: Alignment.bottomLeft, + child: SizedBox( + width: Constant.getActualXPhone(context: context, x: 246), + child: Image.asset( + 'images/splashbawah.png', + fit: BoxFit.fitWidth, + ), + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 5dcd897..3d18441 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,9 +73,11 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - images/logo.png + - images/splashatas.png + - images/splashbawah.png + - images/vektoratas.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images