fix&feat:fixing auth and secure token also initian for userpin preparation

This commit is contained in:
pahmiudahgede 2025-05-02 19:14:46 +07:00
parent 4af31d867e
commit de39c9d7fd
15 changed files with 317 additions and 96 deletions

View File

@ -1,23 +1,71 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class ApiService {
final String baseUrl = dotenv.get('BASE_URL');
final String apiKey = dotenv.get('API_KEY');
final FlutterSecureStorage _secureStorage = FlutterSecureStorage();
static const Map<String, String> _headers = {
'Content-Type': 'application/json',
};
Future<String?> _getAuthToken() async {
return await _secureStorage.read(key: 'token');
}
Future<Map<String, String>> _getHeaders() async {
final token = await _getAuthToken();
return {
..._headers,
'x-api-key': apiKey,
if (token != null) 'Authorization': 'Bearer $token',
};
}
Map<String, dynamic> _processResponse(http.Response response) {
if (response.body.isEmpty) {
throw Exception('Empty response body');
}
try {
final responseJson = jsonDecode(response.body);
switch (response.statusCode) {
case 200:
return responseJson;
case 400:
throw BadRequestException(
'Bad request. The server could not process your request.',
);
case 401:
throw UnauthorizedException(
'Unauthorized. Please check your credentials.',
);
case 404:
throw NotFoundException(
'Not found. The requested resource could not be found.',
);
case 500:
throw ServerException(
'Internal server error. Please try again later.',
);
default:
throw Exception('Failed with status code: ${response.statusCode}');
}
} catch (e) {
throw Exception('Error parsing response: $e');
}
}
Future<Map<String, dynamic>> get(String endpoint) async {
try {
final headers = await _getHeaders();
final url = Uri.parse('$baseUrl$endpoint');
final response = await http.get(
url,
headers: {..._headers, 'API_KEY': apiKey},
);
final response = await http.get(url, headers: headers);
return _processResponse(response);
} catch (e) {
@ -27,26 +75,24 @@ class ApiService {
}
}
Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> body) async {
Future<Map<String, dynamic>> post(
String endpoint,
Map<String, dynamic> body,
) async {
try {
final headers = await _getHeaders();
final url = Uri.parse('$baseUrl$endpoint');
// Debugging URL dan Body Request
debugPrint('Request URL: $url');
debugPrint('Request Body: ${jsonEncode(body)}');
debugPrint('API_KEY: $apiKey');
final response = await http.post(
url,
headers: {
..._headers, // Menggunakan _headers untuk Content-Type
'x-api-key': apiKey, // Pastikan API_KEY dimasukkan dengan benar di sini
},
headers: headers,
body: jsonEncode(body),
);
debugPrint('Response: ${response.body}'); // Debugging Response
debugPrint('Response: ${response.body}');
return _processResponse(response);
} catch (e) {
debugPrint('Error during API request: $e');
@ -61,15 +107,22 @@ class ApiService {
Map<String, dynamic> body,
) async {
try {
final headers = await _getHeaders();
final url = Uri.parse('$baseUrl$endpoint');
debugPrint('Request URL: $url');
debugPrint('Request Body: ${jsonEncode(body)}');
final response = await http.put(
url,
headers: {..._headers, 'API_KEY': apiKey},
headers: headers,
body: jsonEncode(body),
);
debugPrint('Response: ${response.body}');
return _processResponse(response);
} catch (e) {
debugPrint('Error during API request: $e');
throw NetworkException(
'Failed to connect to the server. Please check your internet connection.',
);
@ -81,15 +134,22 @@ class ApiService {
Map<String, dynamic> body,
) async {
try {
final headers = await _getHeaders();
final url = Uri.parse('$baseUrl$endpoint');
debugPrint('Request URL: $url');
debugPrint('Request Body: ${jsonEncode(body)}');
final response = await http.patch(
url,
headers: {..._headers, 'API_KEY': apiKey},
headers: headers,
body: jsonEncode(body),
);
debugPrint('Response: ${response.body}');
return _processResponse(response);
} catch (e) {
debugPrint('Error during API request: $e');
throw NetworkException(
'Failed to connect to the server. Please check your internet connection.',
);
@ -98,11 +158,9 @@ class ApiService {
Future<Map<String, dynamic>> delete(String endpoint) async {
try {
final headers = await _getHeaders();
final url = Uri.parse('$baseUrl$endpoint');
final response = await http.delete(
url,
headers: {..._headers, 'API_KEY': apiKey},
);
final response = await http.delete(url, headers: headers);
return _processResponse(response);
} catch (e) {
@ -111,29 +169,6 @@ class ApiService {
);
}
}
Map<String, dynamic> _processResponse(http.Response response) {
switch (response.statusCode) {
case 200:
return jsonDecode(response.body);
case 400:
throw BadRequestException(
'Bad request. The server could not process your request.',
);
case 401:
throw UnauthorizedException(
'Unauthorized. Please check your credentials.',
);
case 404:
throw NotFoundException(
'Not found. The requested resource could not be found.',
);
case 500:
throw ServerException('Internal server error. Please try again later.');
default:
throw Exception('Failed with status code: ${response.statusCode}');
}
}
}
class NetworkException implements Exception {

View File

@ -18,13 +18,6 @@ final router = GoRouter(
builder: (context, state) => OnboardingPageScreen(),
),
GoRoute(path: '/login', builder: (context, state) => LoginScreen()),
GoRoute(
path: '/navigasi',
builder: (context, state) {
dynamic data = state.extra;
return NavigationPage(data: data);
},
),
GoRoute(
path: '/verif-otp',
builder: (context, state) {
@ -32,6 +25,17 @@ final router = GoRouter(
return VerifotpScreen(phone: phone!);
},
),
GoRoute(
path: '/inputpin',
builder: (context, state) => OnboardingPageScreen(),
),
GoRoute(
path: '/navigasi',
builder: (context, state) {
dynamic data = state.extra;
return NavigationPage(data: data);
},
),
GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
GoRoute(path: '/activity', builder: (context, state) => ActivityScreen()),
GoRoute(

View File

@ -1,40 +1,27 @@
import 'package:rijig_mobile/core/api_services.dart';
import 'package:rijig_mobile/model/response_model.dart';
class AuthModel {
final int status;
final String message;
AuthModel({required this.status, required this.message});
factory AuthModel.fromJson(Map<String, dynamic> json) {
return AuthModel(
status: json['meta']?['status'] ?? 0,
message: json['meta']?['message'] ?? '',
);
}
}
class AuthService {
final ApiService _apiService = ApiService();
Future<AuthModel?> login(String phone) async {
Future<ResponseModel?> login(String phone) async {
try {
var response = await _apiService.post('/authmasyarakat/auth', {
'phone': phone,
});
return AuthModel.fromJson(response);
return ResponseModel.fromJson(response);
} catch (e) {
rethrow;
}
}
Future<Map<String, dynamic>> verifyOtp(String phone, String otp) async {
Future<ResponseModel?> verifyOtp(String phone, String otp) async {
try {
var response = await _apiService.post('/authmasyarakat/verify-otp', {
'phone': phone,
'otp': otp,
});
return response;
return ResponseModel.fromJson(response);
} catch (e) {
rethrow;
}

View File

@ -0,0 +1,15 @@
class ResponseModel {
final int status;
final String message;
final Map<String, dynamic>? data;
ResponseModel({required this.status, required this.message, this.data});
factory ResponseModel.fromJson(Map<String, dynamic> json) {
return ResponseModel(
status: json['meta']?['status'] ?? 0,
message: json['meta']?['message'] ?? '',
data: json['data'],
);
}
}

View File

@ -0,0 +1,27 @@
import 'package:rijig_mobile/core/api_services.dart';
import 'package:rijig_mobile/model/response_model.dart';
class AuthService {
final ApiService _apiService = ApiService();
Future<ResponseModel?> cekPinStatus(String userid) async {
try {
var response = await _apiService.get('/cek-pin-status');
return ResponseModel.fromJson(response);
} catch (e) {
rethrow;
}
}
Future<Map<String, dynamic>> verifyOtp(String phone, String otp) async {
try {
var response = await _apiService.post('/authmasyarakat/verify-otp', {
'phone': phone,
'otp': otp,
});
return response;
} catch (e) {
rethrow;
}
}
}

View File

View File

@ -1,26 +1,42 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rijig_mobile/core/guide.dart';
import 'package:rijig_mobile/core/router.dart';
import 'package:rijig_mobile/viewmodel/auth_vmod.dart';
import 'package:rijig_mobile/widget/buttoncard.dart';
import 'package:rijig_mobile/widget/formfiled.dart';
class LoginScreen extends StatelessWidget {
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
LoginScreenState createState() => LoginScreenState();
}
class LoginScreenState extends State<LoginScreen> {
final _phoneController = TextEditingController();
LoginScreen({super.key});
@override
void dispose() {
_phoneController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: PaddingCustom().paddingHorizontalVertical(15, 30),
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 30),
child: Consumer<AuthViewModel>(
builder: (context, userVM, child) {
if (userVM.authModel?.status == 200) {
Future.delayed(Duration.zero, () {
debugPrint('AuthModel Status: ${userVM.authModel?.status}');
if (userVM.authModel?.status == 200 && !userVM.isLoading) {
debugPrint(
'OTP Sent Successfully. Navigating to Verif-OTP Screen.',
);
WidgetsBinding.instance.addPostFrameCallback((_) {
router.go('/verif-otp', extra: _phoneController.text);
});
}
@ -36,10 +52,20 @@ class LoginScreen extends StatelessWidget {
keyboardType: TextInputType.phone,
errorText: userVM.errorMessage,
),
SizedBox(height: 20),
const SizedBox(height: 20),
userVM.isLoading
? CircularProgressIndicator()
? CardButtonOne(
textButton: 'Sending OTP...',
fontSized: 16,
colorText: Colors.white,
borderRadius: 12,
horizontal: double.infinity,
vertical: 50,
onTap: () {},
loadingTrue: true,
usingRow: false,
)
: CardButtonOne(
textButton: 'Send OTP',
fontSized: 16,
@ -49,21 +75,27 @@ class LoginScreen extends StatelessWidget {
vertical: 50,
onTap: () {
if (_phoneController.text.isNotEmpty) {
debugPrint(
'Sending OTP to: ${_phoneController.text}',
);
userVM.login(_phoneController.text);
}
},
loadingTrue: userVM.isLoading,
loadingTrue: false,
usingRow: false,
),
if (userVM.authModel != null)
Text(
userVM.authModel!.message,
style: TextStyle(
color:
userVM.authModel!.status == 200
? Colors.green
: Colors.red,
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text(
userVM.authModel!.message,
style: TextStyle(
color:
userVM.authModel!.status == 200
? Colors.green
: Colors.red,
),
),
),
],

View File

@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:rijig_mobile/model/response_model.dart';
import 'package:rijig_mobile/model/auth_model.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthViewModel extends ChangeNotifier {
final AuthService _authService = AuthService();
final AuthModel _authModel = AuthModel();
final FlutterSecureStorage _secureStorage = FlutterSecureStorage();
bool isLoading = false;
String? errorMessage;
AuthModel? authModel;
ResponseModel? authModel;
Future<void> login(String phone) async {
try {
@ -14,10 +17,15 @@ class AuthViewModel extends ChangeNotifier {
errorMessage = null;
notifyListeners();
authModel = await _authService.login(phone);
final response = await _authModel.login(phone);
if (authModel?.status != 200) {
errorMessage = authModel?.message ?? 'Failed to send OTP';
if (response != null && response.status == 200) {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('isLoggedIn', false);
authModel = response;
} else {
errorMessage = response?.message ?? 'Failed to send OTP';
}
} catch (e) {
errorMessage = 'Error: $e';
@ -33,18 +41,28 @@ class AuthViewModel extends ChangeNotifier {
errorMessage = null;
notifyListeners();
var response = await _authService.verifyOtp(phone, otp);
var response = await _authModel.verifyOtp(phone, otp);
if (response != null && response.status == 200) {
await _secureStorage.write(
key: 'token',
value: response.data?['token'],
);
await _secureStorage.write(
key: 'user_id',
value: response.data?['user_id'],
);
await _secureStorage.write(
key: 'user_role',
value: response.data?['user_role'],
);
if (response['meta'] != null && response['meta']['status'] == 200) {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('token', response['data']['token']);
await prefs.setString('user_id', response['data']['user_id']);
await prefs.setString('user_role', response['data']['user_role']);
await prefs.setBool('isLoggedIn', true);
authModel = AuthModel.fromJson(response['data']);
authModel = response;
} else {
errorMessage = response['meta']?['message'] ?? 'Failed to verify OTP';
errorMessage = response?.message ?? 'Failed to verify OTP';
}
} catch (e) {
errorMessage = 'Error: $e';
@ -53,4 +71,31 @@ class AuthViewModel extends ChangeNotifier {
notifyListeners();
}
}
Future<String?> getAuthToken() async {
return await _secureStorage.read(key: 'token');
}
Future<String?> getUserId() async {
return await _secureStorage.read(key: 'user_id');
}
Future<String?> getUserRole() async {
return await _secureStorage.read(key: 'user_role');
}
Future<void> logout() async {
await _secureStorage.delete(key: 'token');
await _secureStorage.delete(key: 'user_id');
await _secureStorage.delete(key: 'user_role');
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('isLoggedIn');
notifyListeners();
}
Future<bool> isUserLoggedIn() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getBool('isLoggedIn') ?? false;
}
}

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -5,10 +5,12 @@
import FlutterMacOS
import Foundation
import flutter_secure_storage_macos
import path_provider_foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@ -126,6 +126,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.9.3"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
url: "https://pub.dev"
source: hosted
version: "1.2.3"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_svg:
dependency: "direct main"
description:
@ -200,6 +248,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.20.2"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker:
dependency: transitive
description:
@ -541,6 +597,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
url: "https://pub.dev"
source: hosted
version: "5.12.0"
xdg_directories:
dependency: transitive
description:

View File

@ -14,6 +14,7 @@ dependencies:
sdk: flutter
flutter_dotenv: ^5.2.1
flutter_screenutil: ^5.9.3
flutter_secure_storage: ^9.2.4
flutter_svg: ^2.1.0
gap: ^3.0.1
go_router: ^15.1.1

View File

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST