feat: Add biometric permission and update activity to FlutterFragmentActivity; refactor bindings and remove unused repositories
This commit is contained in:
parent
803a28494d
commit
8d67e7bbb3
|
@ -5,6 +5,8 @@
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
<!-- Biometric permission -->
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||||
<!-- ... -->
|
<!-- ... -->
|
||||||
<application android:label="sigap"
|
<application android:label="sigap"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
package com.backspacex.sigap
|
package com.backspacex.sigap
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
class MainActivity: FlutterFragmentActivity()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.AppCompat.DayNight">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
running.
|
running.
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.AppCompat.DayNight">
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:get/get_navigation/src/root/get_material_app.dart';
|
import 'package:get/get_navigation/src/root/get_material_app.dart';
|
||||||
import 'package:sigap/src/cores/bindings/general_bindings.dart';
|
import 'package:sigap/src/cores/bindings/app_bindings.dart';
|
||||||
import 'package:sigap/src/cores/routes/app_pages.dart';
|
import 'package:sigap/src/cores/routes/app_pages.dart';
|
||||||
import 'package:sigap/src/utils/constants/colors.dart';
|
import 'package:sigap/src/utils/constants/colors.dart';
|
||||||
import 'package:sigap/src/utils/constants/text_strings.dart';
|
import 'package:sigap/src/utils/constants/text_strings.dart';
|
||||||
|
@ -18,7 +18,7 @@ class App extends StatelessWidget {
|
||||||
theme: TAppTheme.lightTheme,
|
theme: TAppTheme.lightTheme,
|
||||||
darkTheme: TAppTheme.darkTheme,
|
darkTheme: TAppTheme.darkTheme,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
initialBinding: GeneralBindings(),
|
initialBinding: AppBindings(),
|
||||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
supportedLocales: const [Locale('id', '')],
|
supportedLocales: const [Locale('id', '')],
|
||||||
getPages: AppPages.routes,
|
getPages: AppPages.routes,
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:sigap/app.dart';
|
import 'package:sigap/app.dart';
|
||||||
import 'package:sigap/src/cores/repositories/panic-button/panic_button_repository.dart';
|
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
|
@ -32,9 +30,5 @@ Future<void> main() async {
|
||||||
storageOptions: const StorageClientOptions(retryAttempts: 10),
|
storageOptions: const StorageClientOptions(retryAttempts: 10),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialize repositories
|
|
||||||
Get.put(PanicButtonRepository());
|
|
||||||
await Get.find<PanicButtonRepository>().init();
|
|
||||||
|
|
||||||
runApp(const App());
|
runApp(const App());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:sigap/src/cores/bindings/controller_bindings.dart';
|
||||||
|
import 'package:sigap/src/cores/bindings/general_bindings.dart';
|
||||||
|
import 'package:sigap/src/cores/bindings/repository_bindings.dart';
|
||||||
|
import 'package:sigap/src/cores/bindings/service_bindings.dart';
|
||||||
|
|
||||||
|
class AppBindings implements Bindings {
|
||||||
|
@override
|
||||||
|
Future<void> dependencies() async {
|
||||||
|
// Register general helpers and utilities
|
||||||
|
UtilityBindings().dependencies();
|
||||||
|
|
||||||
|
// Register all services
|
||||||
|
await ServiceBindings().dependencies();
|
||||||
|
|
||||||
|
// Register all repositories
|
||||||
|
RepositoryBindings().dependencies();
|
||||||
|
|
||||||
|
// Register all feature controllers
|
||||||
|
ControllerBindings().dependencies();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,45 +1,17 @@
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:sigap/src/features/auth/controllers/forgot_password_controller.dart';
|
import 'package:sigap/src/features/auth/bindings/auth_bindings.dart';
|
||||||
import 'package:sigap/src/features/auth/controllers/signin_controller.dart';
|
import 'package:sigap/src/features/onboarding/bindings/onboarding_binding.dart';
|
||||||
import 'package:sigap/src/features/auth/controllers/signup_controller.dart';
|
|
||||||
import 'package:sigap/src/features/onboarding/controllers/onboarding_controller.dart';
|
|
||||||
import 'package:sigap/src/features/onboarding/controllers/role_selection_controller.dart';
|
|
||||||
|
|
||||||
// Onboarding controller bindings
|
class ControllerBindings extends Bindings {
|
||||||
class OnboardingControllerBinding extends Bindings {
|
|
||||||
@override
|
@override
|
||||||
void dependencies() {
|
void dependencies() {
|
||||||
Get.lazyPut<OnboardingController>(() => OnboardingController());
|
// Register all controllers here
|
||||||
|
|
||||||
|
// Onboarding Bindings
|
||||||
|
OnboardingBindings().dependencies();
|
||||||
|
|
||||||
|
// Auth Bindings
|
||||||
|
AuthBindings().dependencies();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleSelectionControllerBinding extends Bindings {
|
|
||||||
@override
|
|
||||||
void dependencies() {
|
|
||||||
Get.lazyPut<RoleSelectionController>(() => RoleSelectionController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth controller bindings
|
|
||||||
class SignInControllerBinding extends Bindings {
|
|
||||||
@override
|
|
||||||
void dependencies() {
|
|
||||||
Get.lazyPut<SignInController>(() => SignInController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignUpControllerBinding extends Bindings {
|
|
||||||
@override
|
|
||||||
void dependencies() {
|
|
||||||
Get.lazyPut<SignUpController>(() => SignUpController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ForgotPasswordControllerBinding extends Bindings {
|
|
||||||
@override
|
|
||||||
void dependencies() {
|
|
||||||
Get.lazyPut<ForgotPasswordController>(() => ForgotPasswordController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main controller bindings
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:get/get.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:sigap/src/utils/helpers/network_manager.dart';
|
import 'package:sigap/src/utils/helpers/network_manager.dart';
|
||||||
|
|
||||||
class GeneralBindings extends Bindings {
|
class UtilityBindings extends Bindings {
|
||||||
Logger? get logger => Logger();
|
Logger? get logger => Logger();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -9,25 +9,14 @@ import 'package:sigap/src/cores/repositories/personalization/users_repository.da
|
||||||
class RepositoryBindings extends Bindings {
|
class RepositoryBindings extends Bindings {
|
||||||
@override
|
@override
|
||||||
void dependencies() {
|
void dependencies() {
|
||||||
// Auth Repository
|
|
||||||
Get.lazyPut<AuthenticationRepository>(
|
Get.lazyPut<AuthenticationRepository>(
|
||||||
() => AuthenticationRepository(),
|
() => AuthenticationRepository(),
|
||||||
fenix: true,
|
fenix: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// User Repository
|
|
||||||
Get.lazyPut<UserRepository>(() => UserRepository(), fenix: true);
|
Get.lazyPut<UserRepository>(() => UserRepository(), fenix: true);
|
||||||
|
|
||||||
// Officer Repository
|
|
||||||
Get.lazyPut<OfficerRepository>(() => OfficerRepository(), fenix: true);
|
Get.lazyPut<OfficerRepository>(() => OfficerRepository(), fenix: true);
|
||||||
|
|
||||||
// Unit Repository
|
|
||||||
Get.lazyPut<UnitRepository>(() => UnitRepository(), fenix: true);
|
Get.lazyPut<UnitRepository>(() => UnitRepository(), fenix: true);
|
||||||
|
|
||||||
// Profile Repository
|
|
||||||
Get.lazyPut<ProfileRepository>(() => ProfileRepository(), fenix: true);
|
Get.lazyPut<ProfileRepository>(() => ProfileRepository(), fenix: true);
|
||||||
|
|
||||||
// Role Repository
|
|
||||||
Get.lazyPut<RolesRepository>(() => RolesRepository(), fenix: true);
|
Get.lazyPut<RolesRepository>(() => RolesRepository(), fenix: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,504 +0,0 @@
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:get_storage/get_storage.dart';
|
|
||||||
import 'package:sigap/src/cores/services/biometric_service.dart';
|
|
||||||
import 'package:sigap/src/cores/services/location_service.dart';
|
|
||||||
import 'package:sigap/src/cores/services/supabase_service.dart';
|
|
||||||
import 'package:sigap/src/features/auth/screens/signin/signin_screen.dart';
|
|
||||||
import 'package:sigap/src/utils/constants/app_routes.dart';
|
|
||||||
import 'package:sigap/src/utils/exceptions/exceptions.dart';
|
|
||||||
import 'package:sigap/src/utils/exceptions/format_exceptions.dart';
|
|
||||||
import 'package:sigap/src/utils/exceptions/platform_exceptions.dart';
|
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
||||||
|
|
||||||
class AuthenticationRepository extends GetxController {
|
|
||||||
static AuthenticationRepository get instance => Get.find();
|
|
||||||
|
|
||||||
// Variable
|
|
||||||
final storage = GetStorage();
|
|
||||||
final _supabase = SupabaseService.instance.client;
|
|
||||||
final _locationService = LocationService.instance;
|
|
||||||
final _biometricService = Get.put(BiometricService());
|
|
||||||
|
|
||||||
// Getters that use the Supabase service
|
|
||||||
User? get authUser => SupabaseService.instance.currentUser;
|
|
||||||
String? get currentUserId => SupabaseService.instance.currentUserId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onReady() {
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
screenRedirect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for biometric login on app start
|
|
||||||
Future<bool> attemptBiometricLogin() async {
|
|
||||||
if (!await _biometricService.isBiometricLoginEnabled()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? refreshToken = await _biometricService.attemptBiometricLogin();
|
|
||||||
if (refreshToken == null || refreshToken.isEmpty) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Use the refresh token to recover the session
|
|
||||||
final response = await _supabase.auth.refreshSession(refreshToken);
|
|
||||||
if (response.session != null) {
|
|
||||||
Get.offAllNamed(AppRoutes.explore);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (e) {
|
|
||||||
// If refresh token is invalid or expired, disable biometric login
|
|
||||||
await _biometricService.disableBiometricLogin();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the onboarding process is complete
|
|
||||||
Future<bool> isOnboardingComplete() async {
|
|
||||||
return storage.read('ONBOARDING_COMPLETED') ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Screen redirect based on auth status
|
|
||||||
void screenRedirect() async {
|
|
||||||
final user = _supabase.auth.currentUser;
|
|
||||||
|
|
||||||
if (user != null) {
|
|
||||||
// User is authenticated
|
|
||||||
Get.offAllNamed(AppRoutes.panicButton);
|
|
||||||
} else {
|
|
||||||
// Check if onboarding is completed
|
|
||||||
final onboardingCompleted = await isOnboardingComplete();
|
|
||||||
|
|
||||||
if (onboardingCompleted) {
|
|
||||||
// Check location validity
|
|
||||||
final isLocationValid =
|
|
||||||
await _locationService.isLocationValidForFeature();
|
|
||||||
|
|
||||||
if (isLocationValid) {
|
|
||||||
// Go to role selection
|
|
||||||
Get.offAllNamed(AppRoutes.roleSelection);
|
|
||||||
} else {
|
|
||||||
// Show location warning
|
|
||||||
Get.offAllNamed(AppRoutes.locationWarning);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show onboarding
|
|
||||||
Get.offAllNamed(AppRoutes.onboarding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- Email and Password Sign In -----------------
|
|
||||||
Future<AuthResponse> signInWithEmailPassword({
|
|
||||||
required String email,
|
|
||||||
required String password,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// Check if the user is banned
|
|
||||||
final bannedUser = await _supabase.rpc(
|
|
||||||
'check_if_banned',
|
|
||||||
params: {'user_email': email},
|
|
||||||
);
|
|
||||||
if (bannedUser != null && bannedUser == true) {
|
|
||||||
throw TExceptions(
|
|
||||||
'This account has been banned due to violation of terms.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await _supabase.auth.signInWithPassword(
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup biometric login if available
|
|
||||||
if (_biometricService.isBiometricAvailable.value) {
|
|
||||||
// Ask user if they want to enable biometric login
|
|
||||||
// This would typically be done in the UI, but setting up the flow here
|
|
||||||
await _biometricService.enableBiometricLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [SESSION] - CHECK SESSION
|
|
||||||
Future<Map<String, dynamic>?> getSession() async {
|
|
||||||
try {
|
|
||||||
final session = _supabase.auth.currentSession;
|
|
||||||
if (session != null) {
|
|
||||||
return {'user': session.user, 'session': session};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Email Verification] - EMAIL VERIFICATION
|
|
||||||
Future<void> sendEmailVerification() async {
|
|
||||||
try {
|
|
||||||
await _supabase.auth.resend(
|
|
||||||
type: OtpType.signup,
|
|
||||||
email: _supabase.auth.currentUser!.email,
|
|
||||||
);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Email Reset Password ] - RESET PASSWORD
|
|
||||||
Future<void> sendResetPasswordForEmail(String email) async {
|
|
||||||
try {
|
|
||||||
await _supabase.auth.resetPasswordForEmail(email);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare OTP
|
|
||||||
Future<AuthResponse> verifyOtp(String otp) async {
|
|
||||||
try {
|
|
||||||
final AuthResponse res = await _supabase.auth.verifyOTP(
|
|
||||||
type: OtpType.signup,
|
|
||||||
token: otp,
|
|
||||||
);
|
|
||||||
|
|
||||||
final Session? session = res.session;
|
|
||||||
final User? user = res.user;
|
|
||||||
|
|
||||||
if (session == null && user == null) {
|
|
||||||
throw TExceptions('Failed to verify OTP. Please try again.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update password after reset
|
|
||||||
Future<UserResponse> updatePasswordAfterReset(String newPassword) async {
|
|
||||||
try {
|
|
||||||
final response = await _supabase.auth.updateUser(
|
|
||||||
UserAttributes(password: newPassword),
|
|
||||||
);
|
|
||||||
return response;
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> changePassword(
|
|
||||||
String currentPassword,
|
|
||||||
String newPassword,
|
|
||||||
String email,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
// Reauthenticate user
|
|
||||||
await _supabase.auth.reauthenticate();
|
|
||||||
|
|
||||||
// Update password
|
|
||||||
await _supabase.auth.updateUser(UserAttributes(password: newPassword));
|
|
||||||
|
|
||||||
// Password changed successfully
|
|
||||||
return 'Password changed successfully.';
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Email Verification] - CREATE NEW VERIFICATION USER EMAIL
|
|
||||||
Future<void> resendEmailVerification(String email) async {
|
|
||||||
try {
|
|
||||||
await _supabase.auth.resend(type: OtpType.signup, email: email);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- Social Sign In -----------------
|
|
||||||
// [GoogleAuthentication] - GOOGLE
|
|
||||||
Future<bool> signInWithGoogle() async {
|
|
||||||
try {
|
|
||||||
return await _supabase.auth.signInWithOAuth(
|
|
||||||
OAuthProvider.google,
|
|
||||||
authScreenLaunchMode: LaunchMode.inAppWebView,
|
|
||||||
);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [FacebookAuthentication] - FACEBOOK
|
|
||||||
Future<bool> signInWithFacebook() async {
|
|
||||||
try {
|
|
||||||
return await _supabase.auth.signInWithOAuth(
|
|
||||||
OAuthProvider.facebook,
|
|
||||||
authScreenLaunchMode: LaunchMode.inAppWebView,
|
|
||||||
);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [AppleAuthentication] - APPLE
|
|
||||||
Future<bool> signInWithApple() async {
|
|
||||||
try {
|
|
||||||
return await _supabase.auth.signInWithOAuth(
|
|
||||||
OAuthProvider.apple,
|
|
||||||
authScreenLaunchMode: LaunchMode.inAppWebView,
|
|
||||||
);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [GithubAuthentication] - GITHUB
|
|
||||||
Future<bool> signInWithGithub() async {
|
|
||||||
try {
|
|
||||||
return await _supabase.auth.signInWithOAuth(
|
|
||||||
OAuthProvider.github,
|
|
||||||
authScreenLaunchMode: LaunchMode.inAppWebView,
|
|
||||||
);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [TwitterAuthentication] - TWITTER
|
|
||||||
Future<bool> signInWithTwitter() async {
|
|
||||||
try {
|
|
||||||
return await _supabase.auth.signInWithOAuth(
|
|
||||||
OAuthProvider.twitter,
|
|
||||||
authScreenLaunchMode: LaunchMode.inAppWebView,
|
|
||||||
);
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Email AUTH] - SIGN UP
|
|
||||||
Future<AuthResponse> signUpWithEmailPassword({
|
|
||||||
required String email,
|
|
||||||
required String password,
|
|
||||||
Map<String, dynamic>? userMetadata,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// Validate location for registration
|
|
||||||
bool isLocationValid = await _locationService.isLocationValidForFeature();
|
|
||||||
if (!isLocationValid) {
|
|
||||||
throw TExceptions('Registration is only available within Jember area. Please ensure your location services are enabled and you are not using a mock location app.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create user with email and password
|
|
||||||
final response = await _supabase.auth.signUp(
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
data: userMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} catch (e) {
|
|
||||||
if (e is TExceptions) rethrow;
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable or disable biometric login
|
|
||||||
Future<void> toggleBiometricLogin(bool enable) async {
|
|
||||||
if (enable) {
|
|
||||||
await _biometricService.enableBiometricLogin();
|
|
||||||
} else {
|
|
||||||
await _biometricService.disableBiometricLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if biometric login is enabled
|
|
||||||
Future<bool> isBiometricLoginEnabled() async {
|
|
||||||
return await _biometricService.isBiometricLoginEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if biometrics are available on the device
|
|
||||||
Future<bool> isBiometricAvailable() async {
|
|
||||||
return _biometricService.isBiometricAvailable.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- Logout -----------------
|
|
||||||
// [Sign Out] - SIGN OUT
|
|
||||||
Future<void> signOut() async {
|
|
||||||
try {
|
|
||||||
await _supabase.auth.signOut();
|
|
||||||
Get.offAll(() => const SignInScreen());
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- Delete Account -----------------
|
|
||||||
Future<void> deleteAccount() async {
|
|
||||||
try {
|
|
||||||
final userId = _supabase.auth.currentUser!.id;
|
|
||||||
await _supabase.rpc('delete_account', params: {'user_id': userId});
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates a user's profile with the officer status and metadata
|
|
||||||
Future<UserResponse> updateUserRole({
|
|
||||||
required bool isOfficer,
|
|
||||||
Map<String, dynamic>? officerData,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// Prepare metadata with the officer flag
|
|
||||||
final userMetadata = <String, dynamic>{'is_officer': isOfficer};
|
|
||||||
|
|
||||||
// Add officer data if provided
|
|
||||||
if (isOfficer && officerData != null) {
|
|
||||||
userMetadata['officer_data'] = officerData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the user metadata which will trigger the role change function
|
|
||||||
final response = await _supabase.auth.updateUser(
|
|
||||||
UserAttributes(data: userMetadata),
|
|
||||||
);
|
|
||||||
return response;
|
|
||||||
} on AuthException catch (e) {
|
|
||||||
throw TExceptions(e.message);
|
|
||||||
} on FormatException catch (_) {
|
|
||||||
throw const TFormatException();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
throw TPlatformException(e.code).message;
|
|
||||||
} on PostgrestException catch (error) {
|
|
||||||
throw TExceptions.fromCode(error.code ?? 'unknown_error');
|
|
||||||
} catch (e) {
|
|
||||||
throw TExceptions('Something went wrong. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:sigap/src/cores/bindings/controller_bindings.dart';
|
|
||||||
import 'package:sigap/src/features/auth/screens/forgot-password/forgot_password.dart';
|
import 'package:sigap/src/features/auth/screens/forgot-password/forgot_password.dart';
|
||||||
import 'package:sigap/src/features/auth/screens/signin/signin_screen.dart';
|
import 'package:sigap/src/features/auth/screens/signin/signin_screen.dart';
|
||||||
import 'package:sigap/src/features/auth/screens/signup/signup_screen.dart';
|
import 'package:sigap/src/features/auth/screens/signup/signup_screen.dart';
|
||||||
|
@ -12,46 +11,21 @@ class AppPages {
|
||||||
|
|
||||||
static final routes = [
|
static final routes = [
|
||||||
// Onboarding
|
// Onboarding
|
||||||
GetPage(
|
GetPage(name: AppRoutes.onboarding, page: () => const OnboardingScreen()),
|
||||||
name: AppRoutes.onboarding,
|
|
||||||
page: () => const OnboardingScreen(),
|
|
||||||
binding: OnboardingControllerBinding(),
|
|
||||||
),
|
|
||||||
|
|
||||||
GetPage(
|
GetPage(
|
||||||
name: AppRoutes.roleSelection,
|
name: AppRoutes.roleSelection,
|
||||||
page: () => const RoleSelectionScreen(),
|
page: () => const RoleSelectionScreen(),
|
||||||
binding: RoleSelectionControllerBinding(),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
GetPage(
|
GetPage(name: AppRoutes.signIn, page: () => const SignInScreen()),
|
||||||
name: AppRoutes.signIn,
|
|
||||||
page: () => const SignInScreen(),
|
|
||||||
binding: SignInControllerBinding(),
|
|
||||||
),
|
|
||||||
|
|
||||||
GetPage(
|
GetPage(name: AppRoutes.signUp, page: () => const SignUpScreen()),
|
||||||
name: AppRoutes.signUp,
|
|
||||||
page: () => const SignUpScreen(),
|
|
||||||
binding: SignUpControllerBinding(),
|
|
||||||
),
|
|
||||||
|
|
||||||
GetPage(
|
GetPage(
|
||||||
name: AppRoutes.forgotPassword,
|
name: AppRoutes.forgotPassword,
|
||||||
page: () => const ForgotPasswordScreen(),
|
page: () => const ForgotPasswordScreen(),
|
||||||
binding: ForgotPasswordControllerBinding(),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// Main pages
|
|
||||||
// GetPage(name: AppRoutes.explore, page: () => const ExploreScreen()),
|
|
||||||
// GetPage(name: AppRoutes.map, page: () => const MapScreen()),
|
|
||||||
// GetPage(name: AppRoutes.panicButton, page: () => const PanicButtonScreen()),
|
|
||||||
// GetPage(name: AppRoutes.communityWatch, page: () => const CommunityWatchScreen()),
|
|
||||||
// GetPage(name: AppRoutes.dailyOps, page: () => const DailyOpsScreen()),
|
|
||||||
|
|
||||||
// Personalization
|
|
||||||
// GetPage(name: AppRoutes.profile, page: () => const ProfileScreen()),
|
|
||||||
// GetPage(name: AppRoutes.settings, page: () => const SettingsScreen()),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:sigap/src/features/auth/controllers/email_verification_controller.dart';
|
||||||
|
import 'package:sigap/src/features/auth/controllers/forgot_password_controller.dart';
|
||||||
|
import 'package:sigap/src/features/auth/controllers/signin_controller.dart';
|
||||||
|
import 'package:sigap/src/features/auth/controllers/signup_controller.dart';
|
||||||
|
import 'package:sigap/src/features/auth/controllers/step_form_controller.dart';
|
||||||
|
|
||||||
|
class AuthBindings extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
// Register all feature auth controllers
|
||||||
|
Get.lazyPut(() => SignInController());
|
||||||
|
Get.lazyPut(() => SignUpController());
|
||||||
|
Get.lazyPut(() => StepFormController());
|
||||||
|
Get.lazyPut(() => EmailVerificationController());
|
||||||
|
Get.lazyPut(() => ForgotPasswordController());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:sigap/src/features/onboarding/controllers/onboarding_controller.dart';
|
||||||
|
import 'package:sigap/src/features/onboarding/controllers/role_selection_controller.dart';
|
||||||
|
|
||||||
|
class OnboardingBindings extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
// Register all feature onboarding controllers
|
||||||
|
Get.lazyPut(() => OnboardingController());
|
||||||
|
Get.lazyPut(() => RoleSelectionController());
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,6 +93,9 @@ dependencies:
|
||||||
# Fonts
|
# Fonts
|
||||||
google_fonts:
|
google_fonts:
|
||||||
|
|
||||||
|
# Localization
|
||||||
|
flutter_localizations
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
Loading…
Reference in New Issue