feat: Update user metadata model and authentication flow; enhance ID card verification process and error handling
This commit is contained in:
parent
498b71c184
commit
ac39366371
|
@ -5,13 +5,13 @@ import 'package:sigap/src/utils/helpers/network_manager.dart';
|
||||||
|
|
||||||
class UtilityBindings extends Bindings {
|
class UtilityBindings extends Bindings {
|
||||||
Logger? get logger => Logger();
|
Logger? get logger => Logger();
|
||||||
NetworkManager? get networkManager => NetworkManager();
|
|
||||||
BackgroundService? get backgroundService => BackgroundService.instance;
|
BackgroundService? get backgroundService => BackgroundService.instance;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dependencies() {
|
void dependencies() {
|
||||||
Get.put(backgroundService, permanent: true);
|
Get.put(backgroundService, permanent: true);
|
||||||
Get.put(networkManager, permanent: true);
|
Get.put(NetworkManager(), permanent: true);
|
||||||
Get.put(logger, permanent: true);
|
Get.put(logger, permanent: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,8 @@ class AzureOCRService {
|
||||||
'$endpoint$ocrApiPath?language=id&detectOrientation=true',
|
'$endpoint$ocrApiPath?language=id&detectOrientation=true',
|
||||||
);
|
);
|
||||||
|
|
||||||
final response = await DioClient().post(
|
// First request: Submit the image to the Read API
|
||||||
|
final submitResponse = await DioClient().post(
|
||||||
uri.toString(),
|
uri.toString(),
|
||||||
data: bytes,
|
data: bytes,
|
||||||
options: Options(
|
options: Options(
|
||||||
|
@ -40,14 +41,25 @@ class AzureOCRService {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (submitResponse.statusCode == 202) {
|
||||||
final jsonResponse = json.decode(response.data);
|
// Get the operation-location header from the response
|
||||||
|
final operationLocation = submitResponse.headers.value(
|
||||||
|
'operation-location',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (operationLocation == null) {
|
||||||
|
throw Exception('Failed to get operation location from response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for results
|
||||||
|
final ocrResult = await _pollForOcrResults(operationLocation);
|
||||||
|
|
||||||
return isOfficer
|
return isOfficer
|
||||||
? _extractKtaInfo(jsonResponse)
|
? _extractKtaInfo(ocrResult)
|
||||||
: _extractKtpInfo(jsonResponse);
|
: _extractKtpInfo(ocrResult);
|
||||||
} else {
|
} else {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Failed to process image: ${response.statusCode} - ${response.data}',
|
'Failed to submit image: ${submitResponse.statusCode} - ${submitResponse.data}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -55,10 +67,55 @@ class AzureOCRService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract KTP (Civilian ID) information
|
// Poll for OCR results until the operation completes
|
||||||
|
Future<Map<String, dynamic>> _pollForOcrResults(
|
||||||
|
String operationLocation,
|
||||||
|
) async {
|
||||||
|
const maxRetries = 10;
|
||||||
|
const pollingInterval = Duration(milliseconds: 1000);
|
||||||
|
|
||||||
|
for (int i = 0; i < maxRetries; i++) {
|
||||||
|
try {
|
||||||
|
final response = await DioClient().get(
|
||||||
|
operationLocation,
|
||||||
|
options: Options(
|
||||||
|
headers: {'Ocp-Apim-Subscription-Key': subscriptionKey},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final result = json.decode(response.data);
|
||||||
|
final status = result['status'];
|
||||||
|
|
||||||
|
if (status == 'succeeded') {
|
||||||
|
return result;
|
||||||
|
} else if (status == 'failed') {
|
||||||
|
throw Exception('OCR operation failed: ${result['error']}');
|
||||||
|
}
|
||||||
|
// If status is 'running' or 'notStarted', continue polling
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to get OCR results: ${response.statusCode} - ${response.data}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (i == maxRetries - 1) {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to get OCR results after $maxRetries attempts: $e',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.delayed(pollingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception('Timeout while waiting for OCR results');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract KTP (Civilian ID) information - updated for Read API response format
|
||||||
Map<String, String> _extractKtpInfo(Map<String, dynamic> ocrResult) {
|
Map<String, String> _extractKtpInfo(Map<String, dynamic> ocrResult) {
|
||||||
final Map<String, String> extractedInfo = {};
|
final Map<String, String> extractedInfo = {};
|
||||||
final List<String> allLines = _getAllTextLines(ocrResult);
|
final List<String> allLines = _getAllTextLinesFromReadAPI(ocrResult);
|
||||||
|
|
||||||
for (int i = 0; i < allLines.length; i++) {
|
for (int i = 0; i < allLines.length; i++) {
|
||||||
String line = allLines[i].toLowerCase();
|
String line = allLines[i].toLowerCase();
|
||||||
|
@ -129,10 +186,10 @@ class AzureOCRService {
|
||||||
return extractedInfo;
|
return extractedInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract KTA (Officer ID) information
|
// Extract KTA (Officer ID) information - updated for Read API response format
|
||||||
Map<String, String> _extractKtaInfo(Map<String, dynamic> ocrResult) {
|
Map<String, String> _extractKtaInfo(Map<String, dynamic> ocrResult) {
|
||||||
final Map<String, String> extractedInfo = {};
|
final Map<String, String> extractedInfo = {};
|
||||||
final List<String> allLines = _getAllTextLines(ocrResult);
|
final List<String> allLines = _getAllTextLinesFromReadAPI(ocrResult);
|
||||||
|
|
||||||
for (int i = 0; i < allLines.length; i++) {
|
for (int i = 0; i < allLines.length; i++) {
|
||||||
String line = allLines[i].toLowerCase();
|
String line = allLines[i].toLowerCase();
|
||||||
|
@ -196,7 +253,33 @@ class AzureOCRService {
|
||||||
return extractedInfo;
|
return extractedInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to extract all text lines from OCR result
|
// Updated helper method to extract text lines from Read API result format
|
||||||
|
List<String> _getAllTextLinesFromReadAPI(Map<String, dynamic> ocrResult) {
|
||||||
|
final List<String> allText = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Navigate through the Read API response structure
|
||||||
|
if (ocrResult.containsKey('analyzeResult') &&
|
||||||
|
ocrResult['analyzeResult'].containsKey('readResults')) {
|
||||||
|
final readResults = ocrResult['analyzeResult']['readResults'];
|
||||||
|
for (var page in readResults) {
|
||||||
|
if (page.containsKey('lines')) {
|
||||||
|
for (var line in page['lines']) {
|
||||||
|
if (line.containsKey('text')) {
|
||||||
|
allText.add(line['text']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error extracting text from OCR result: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
return allText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original helper method for old OCR API format (keeping for compatibility)
|
||||||
List<String> _getAllTextLines(Map<String, dynamic> ocrResult) {
|
List<String> _getAllTextLines(Map<String, dynamic> ocrResult) {
|
||||||
final List<String> allText = [];
|
final List<String> allText = [];
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ class SupabaseService extends GetxService {
|
||||||
if (metadata.isOfficer == true && metadata.officerData != null) {
|
if (metadata.isOfficer == true && metadata.officerData != null) {
|
||||||
return metadata.officerData?.nrp;
|
return metadata.officerData?.nrp;
|
||||||
} else {
|
} else {
|
||||||
return metadata.profileData?.nik;
|
return metadata.viewerData?.profile?.nik;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,32 @@
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:sigap/src/features/daily-ops/data/models/models/officers_model.dart';
|
import 'package:sigap/src/features/daily-ops/data/models/models/officers_model.dart';
|
||||||
import 'package:sigap/src/features/personalization/data/models/models/profile_model.dart';
|
import 'package:sigap/src/features/personalization/data/models/index.dart';
|
||||||
|
|
||||||
|
/// A model representing user metadata with a focus on single source of truth
|
||||||
|
/// by delegating properties to related models when possible
|
||||||
class UserMetadataModel {
|
class UserMetadataModel {
|
||||||
|
// Core properties that define the user type
|
||||||
final bool isOfficer;
|
final bool isOfficer;
|
||||||
final String? userId;
|
final String? userId;
|
||||||
final String? roleId;
|
final String? roleId;
|
||||||
final String? nik;
|
final String profileStatus;
|
||||||
final String? email;
|
|
||||||
final String? phone;
|
|
||||||
final String? name;
|
|
||||||
final OfficerModel? officerData;
|
|
||||||
final ProfileModel? profileData;
|
|
||||||
final Map<String, dynamic>? additionalData;
|
|
||||||
|
|
||||||
|
|
||||||
|
// Related models that hold specific data
|
||||||
|
final OfficerModel? officerData;
|
||||||
|
final UserModel? viewerData;
|
||||||
|
|
||||||
|
// Only store properties here that aren't available in related models
|
||||||
|
final String? email;
|
||||||
|
final Map<String, dynamic>? additionalData;
|
||||||
|
|
||||||
const UserMetadataModel({
|
const UserMetadataModel({
|
||||||
this.isOfficer = false,
|
this.isOfficer = false,
|
||||||
this.userId,
|
this.userId,
|
||||||
this.roleId,
|
this.roleId,
|
||||||
this.nik,
|
this.profileStatus = 'incomplete',
|
||||||
this.email,
|
this.email,
|
||||||
this.phone,
|
|
||||||
this.name,
|
|
||||||
this.officerData,
|
this.officerData,
|
||||||
this.profileData,
|
this.viewerData,
|
||||||
|
|
||||||
this.additionalData,
|
this.additionalData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ class UserMetadataModel {
|
||||||
|
|
||||||
final bool isOfficer = json['is_officer'] == true;
|
final bool isOfficer = json['is_officer'] == true;
|
||||||
|
|
||||||
// Parse officer data with better error handling
|
// Parse officer data
|
||||||
OfficerModel? officerData;
|
OfficerModel? officerData;
|
||||||
if (json['officer_data'] != null && isOfficer) {
|
if (json['officer_data'] != null && isOfficer) {
|
||||||
try {
|
try {
|
||||||
|
@ -50,14 +51,12 @@ class UserMetadataModel {
|
||||||
|
|
||||||
officerData = OfficerModel.fromJson(officerJson);
|
officerData = OfficerModel.fromJson(officerJson);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Use proper logging in production
|
|
||||||
Logger().e('Failed to parse officer data: $e');
|
Logger().e('Failed to parse officer data: $e');
|
||||||
// Consider rethrow for critical errors
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse profile data with better error handling
|
// Parse profile data
|
||||||
ProfileModel? profileData;
|
UserModel? viewerData;
|
||||||
if (json['profile_data'] != null && !isOfficer) {
|
if (json['profile_data'] != null && !isOfficer) {
|
||||||
try {
|
try {
|
||||||
final profileJson = Map<String, dynamic>.from(json['profile_data']);
|
final profileJson = Map<String, dynamic>.from(json['profile_data']);
|
||||||
|
@ -66,7 +65,7 @@ class UserMetadataModel {
|
||||||
profileJson.putIfAbsent('user_id', () => json['id']);
|
profileJson.putIfAbsent('user_id', () => json['id']);
|
||||||
profileJson.putIfAbsent('nik', () => json['nik']);
|
profileJson.putIfAbsent('nik', () => json['nik']);
|
||||||
|
|
||||||
profileData = ProfileModel.fromJson(profileJson);
|
viewerData = UserModel.fromJson(profileJson);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger().e('Failed to parse profile data: $e');
|
Logger().e('Failed to parse profile data: $e');
|
||||||
}
|
}
|
||||||
|
@ -77,10 +76,7 @@ class UserMetadataModel {
|
||||||
'is_officer',
|
'is_officer',
|
||||||
'user_id',
|
'user_id',
|
||||||
'role_id',
|
'role_id',
|
||||||
'nik',
|
|
||||||
'email',
|
'email',
|
||||||
'phone',
|
|
||||||
'name',
|
|
||||||
'officer_data',
|
'officer_data',
|
||||||
'profile_data',
|
'profile_data',
|
||||||
'emergency_contact',
|
'emergency_contact',
|
||||||
|
@ -92,51 +88,36 @@ class UserMetadataModel {
|
||||||
return UserMetadataModel(
|
return UserMetadataModel(
|
||||||
isOfficer: isOfficer,
|
isOfficer: isOfficer,
|
||||||
userId: json['user_id'] as String?,
|
userId: json['user_id'] as String?,
|
||||||
roleId: json['role_id'] as String?,
|
roleId: json['role_id'] ?? json['initial_role_id'] as String?,
|
||||||
nik: json['nik'] as String?,
|
profileStatus: json['profile_status'] as String? ?? 'incomplete',
|
||||||
email: json['email'] as String?,
|
email: json['email'] as String?,
|
||||||
phone: json['phone'] as String?,
|
|
||||||
name: json['name'] as String?,
|
|
||||||
officerData: officerData,
|
officerData: officerData,
|
||||||
profileData: profileData,
|
viewerData: viewerData,
|
||||||
additionalData: additionalData.isNotEmpty ? additionalData : null,
|
additionalData: additionalData.isNotEmpty ? additionalData : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert model to JSON Map for Supabase Auth
|
/// Convert model to JSON Map for Supabase Auth
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final data = <String, dynamic>{'is_officer': isOfficer};
|
final data = <String, dynamic>{
|
||||||
|
'is_officer': isOfficer,
|
||||||
|
'profile_status': profileStatus,
|
||||||
|
};
|
||||||
|
|
||||||
// Add basic user data
|
// Add basic user data
|
||||||
if (roleId != null) data['role_id'] = roleId;
|
if (roleId != null) data['role_id'] = roleId;
|
||||||
if (userId != null) data['user_id'] = userId;
|
if (userId != null) data['user_id'] = userId;
|
||||||
if (nik != null) data['nik'] = nik;
|
|
||||||
if (email != null) data['email'] = email;
|
if (email != null) data['email'] = email;
|
||||||
if (phone != null) data['phone'] = phone;
|
|
||||||
if (name != null) data['name'] = name;
|
|
||||||
|
|
||||||
// Add officer-specific data
|
// Add officer-specific data
|
||||||
if (officerData != null && isOfficer) {
|
if (officerData != null && isOfficer) {
|
||||||
data['officer_data'] = {
|
data['officer_data'] = officerData!.toJson();
|
||||||
'nrp': officerData!.nrp,
|
|
||||||
'name': officerData!.name,
|
|
||||||
'rank': officerData!.rank,
|
|
||||||
'position': officerData!.position,
|
|
||||||
'phone': officerData!.phone,
|
|
||||||
'unit_id': officerData!.unitId,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add profile data for non-officers
|
// Add profile data for non-officers
|
||||||
if (profileData != null && !isOfficer) {
|
if (viewerData != null && !isOfficer) {
|
||||||
data['profile_data'] = {
|
data['profile_data'] = viewerData!.toJson();
|
||||||
'nik': profileData!.nik,
|
|
||||||
'first_name': profileData!.firstName,
|
|
||||||
'last_name': profileData!.lastName,
|
|
||||||
'address': profileData!.address,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Add additional data
|
// Add additional data
|
||||||
if (additionalData != null) {
|
if (additionalData != null) {
|
||||||
|
@ -146,50 +127,111 @@ class UserMetadataModel {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert to specialized JSON for initial signup
|
||||||
|
Map<String, dynamic> toInitialSignupJson() {
|
||||||
|
return {
|
||||||
|
'is_officer': isOfficer,
|
||||||
|
if (email != null) 'email': email,
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
if (phone != null) 'phone': phone,
|
||||||
|
'profile_status': profileStatus,
|
||||||
|
'role_id': roleId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to specialized JSON for profile completion
|
||||||
|
Map<String, dynamic> toProfileCompletionJson() {
|
||||||
|
return {
|
||||||
|
'profile_status': 'complete',
|
||||||
|
'is_officer': isOfficer,
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
if (phone != null) 'phone': phone,
|
||||||
|
if (roleId != null) 'role_id': roleId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts to JSON format for auth metadata update
|
||||||
|
Map<String, dynamic> toAuthMetadataJson() {
|
||||||
|
return {
|
||||||
|
'is_officer': isOfficer,
|
||||||
|
'role_id': roleId,
|
||||||
|
'profile_status': profileStatus,
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory UserMetadataModel.fromInitUserMetadata(
|
||||||
|
UserMetadataModel metadata, {
|
||||||
|
String? email,
|
||||||
|
String profileStatus = 'incomplete',
|
||||||
|
}) {
|
||||||
|
return UserMetadataModel(
|
||||||
|
email: email,
|
||||||
|
isOfficer: metadata.isOfficer,
|
||||||
|
roleId: metadata.roleId,
|
||||||
|
profileStatus: profileStatus,
|
||||||
|
viewerData: metadata.viewerData,
|
||||||
|
officerData: metadata.officerData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Create copy with updated fields
|
/// Create copy with updated fields
|
||||||
UserMetadataModel copyWith({
|
UserMetadataModel copyWith({
|
||||||
bool? isOfficer,
|
bool? isOfficer,
|
||||||
String? userId,
|
String? userId,
|
||||||
String? roleId,
|
String? roleId,
|
||||||
String? nik,
|
String? profileStatus,
|
||||||
String? email,
|
String? email,
|
||||||
String? phone,
|
|
||||||
String? name,
|
|
||||||
OfficerModel? officerData,
|
OfficerModel? officerData,
|
||||||
ProfileModel? profileData,
|
UserModel? viewerData,
|
||||||
Map<String, dynamic>? emergencyContact,
|
|
||||||
Map<String, dynamic>? additionalData,
|
Map<String, dynamic>? additionalData,
|
||||||
}) {
|
}) {
|
||||||
return UserMetadataModel(
|
return UserMetadataModel(
|
||||||
isOfficer: isOfficer ?? this.isOfficer,
|
isOfficer: isOfficer ?? this.isOfficer,
|
||||||
userId: userId ?? this.userId,
|
userId: userId ?? this.userId,
|
||||||
roleId: roleId ?? this.roleId,
|
roleId: roleId ?? this.roleId,
|
||||||
nik: nik ?? this.nik,
|
profileStatus: profileStatus ?? this.profileStatus,
|
||||||
email: email ?? this.email,
|
email: email ?? this.email,
|
||||||
phone: phone ?? this.phone,
|
|
||||||
name: name ?? this.name,
|
|
||||||
officerData: officerData ?? this.officerData,
|
officerData: officerData ?? this.officerData,
|
||||||
profileData: profileData ?? this.profileData,
|
viewerData: viewerData ?? this.viewerData,
|
||||||
|
|
||||||
additionalData: additionalData ?? this.additionalData,
|
additionalData: additionalData ?? this.additionalData,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Computed properties (getters)
|
||||||
|
|
||||||
/// Primary identifier (NRP for officers, NIK for users)
|
/// Primary identifier (NRP for officers, NIK for users)
|
||||||
String? get identifier => isOfficer ? officerData?.nrp : nik;
|
String? get identifier => isOfficer ? officerData?.nrp : nik;
|
||||||
|
|
||||||
/// Get display name with fallback priority
|
/// User's NIK (delegated to viewerData if available)
|
||||||
String? get displayName {
|
String? get nik => viewerData?.profile?.nik;
|
||||||
// Priority: explicit name > officer name > profile name > email
|
|
||||||
if (name?.isNotEmpty == true) return name;
|
/// User's phone number (delegated to appropriate model based on user type)
|
||||||
|
String? get phone => isOfficer ? officerData?.phone : viewerData?.phone;
|
||||||
|
|
||||||
|
/// User's name (delegated to appropriate model or fallback to email)
|
||||||
|
String? get name {
|
||||||
if (isOfficer && officerData?.name.isNotEmpty == true) {
|
if (isOfficer && officerData?.name.isNotEmpty == true) {
|
||||||
return officerData!.name;
|
return officerData!.name;
|
||||||
}
|
}
|
||||||
if (!isOfficer && profileData?.fullName?.isNotEmpty == true) {
|
if (!isOfficer && viewerData?.profile?.fullName?.isNotEmpty == true) {
|
||||||
return profileData!.fullName;
|
return viewerData!.profile?.fullName;
|
||||||
}
|
}
|
||||||
return email?.split('@').first; // Fallback to email username
|
return email?.split('@').first; // Fallback to email username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// User's address (only available for regular users)
|
||||||
|
Map<String, dynamic>? get address =>
|
||||||
|
!isOfficer ? viewerData?.profile?.address : null;
|
||||||
|
|
||||||
|
/// User's rank (only available for officers)
|
||||||
|
String? get rank => isOfficer ? officerData?.rank : null;
|
||||||
|
|
||||||
|
/// User's unit ID (only available for officers)
|
||||||
|
String? get unitId => isOfficer ? officerData?.unitId : null;
|
||||||
|
|
||||||
|
/// Get display name with fallback priority
|
||||||
|
String? get displayName => name;
|
||||||
|
|
||||||
/// Validate required fields based on user type
|
/// Validate required fields based on user type
|
||||||
List<String> validate() {
|
List<String> validate() {
|
||||||
|
|
|
@ -65,19 +65,36 @@ class AuthenticationRepository extends GetxController {
|
||||||
|
|
||||||
// Redirect user to appropriate screen on app start
|
// Redirect user to appropriate screen on app start
|
||||||
screenRedirect() async {
|
screenRedirect() async {
|
||||||
final user = _supabase.auth.currentUser;
|
final session = _supabase.auth.currentSession;
|
||||||
if (user != null) {
|
|
||||||
// local storage
|
// Check if onboarding has been shown before
|
||||||
storage.writeIfNull('isFirstTime', true);
|
final isFirstTime = storage.read('isFirstTime') ?? false;
|
||||||
// check if user is already logged in
|
|
||||||
storage.read('isFirstTime') != true
|
if (session != null) {
|
||||||
? Get.offAll(() => const SignInScreen())
|
if (session.user.emailConfirmedAt == null) {
|
||||||
: Get.offAll(() => const OnboardingScreen());
|
// User is not verified, go to email verification screen
|
||||||
|
Get.offAllNamed(AppRoutes.emailVerification);
|
||||||
|
} else if (session.user.userMetadata!['profile_status'] == 'incomplete') {
|
||||||
|
// User is regular user, go to main app screen
|
||||||
|
Get.offAllNamed(AppRoutes.registrationForm);
|
||||||
|
} else if (session.user.userMetadata!['profile_status'] == 'complete' &&
|
||||||
|
session.user.emailConfirmedAt != null) {
|
||||||
|
// Redirect to the main app screen
|
||||||
|
Get.offAllNamed(AppRoutes.panicButton);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Try biometric login first
|
// Try biometric login first
|
||||||
bool biometricSuccess = await attemptBiometricLogin();
|
bool biometricSuccess = await attemptBiometricLogin();
|
||||||
if (!biometricSuccess) {
|
if (!biometricSuccess) {
|
||||||
Get.offAll(() => const SignInScreen());
|
// If not first time, go to sign in directly
|
||||||
|
// If first time, show onboarding first
|
||||||
|
if (isFirstTime) {
|
||||||
|
Get.offAll(() => const SignInScreen());
|
||||||
|
} else {
|
||||||
|
// Mark that onboarding has been shown
|
||||||
|
storage.write('isFirstTime', true);
|
||||||
|
Get.offAll(() => const OnboardingScreen());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -522,16 +539,16 @@ class AuthenticationRepository extends GetxController {
|
||||||
required UserMetadataModel initialData,
|
required UserMetadataModel initialData,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
// Convert to UserModel for more functionality
|
||||||
|
final userMetadataModel = UserMetadataModel.fromInitUserMetadata(
|
||||||
|
initialData,
|
||||||
|
email: email,
|
||||||
|
);
|
||||||
|
|
||||||
final AuthResponse res = await _supabase.auth.signUp(
|
final AuthResponse res = await _supabase.auth.signUp(
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
data: {
|
data: userMetadataModel.toInitialSignupJson(),
|
||||||
'name': initialData.name,
|
|
||||||
'phone': initialData.phone,
|
|
||||||
'is_officer': initialData.isOfficer,
|
|
||||||
'profile_status': 'incomplete', // Mark profile as incomplete
|
|
||||||
'initial_role_id': initialData.roleId,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -545,15 +562,16 @@ class AuthenticationRepository extends GetxController {
|
||||||
/// Updates user profile after registration form completion
|
/// Updates user profile after registration form completion
|
||||||
Future<void> completeUserProfile(UserMetadataModel completeData) async {
|
Future<void> completeUserProfile(UserMetadataModel completeData) async {
|
||||||
try {
|
try {
|
||||||
|
// Convert to UserModel
|
||||||
|
final userMetadataModel = UserMetadataModel.fromInitUserMetadata(
|
||||||
|
completeData,
|
||||||
|
profileStatus: 'complete',
|
||||||
|
);
|
||||||
|
|
||||||
// First update auth metadata
|
// First update auth metadata
|
||||||
await _supabase.auth.updateUser(
|
await _supabase.auth.updateUser(
|
||||||
UserAttributes(
|
UserAttributes(
|
||||||
data: {
|
data: userMetadataModel.toProfileCompletionJson(),
|
||||||
'profile_status': 'complete',
|
|
||||||
'is_officer': completeData.isOfficer,
|
|
||||||
'name': completeData.name,
|
|
||||||
'phone': completeData.phone,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -562,16 +580,43 @@ class AuthenticationRepository extends GetxController {
|
||||||
await _supabase
|
await _supabase
|
||||||
.from('officers')
|
.from('officers')
|
||||||
.insert(completeData.officerData!.toJson());
|
.insert(completeData.officerData!.toJson());
|
||||||
} else if (!completeData.isOfficer && completeData.profileData != null) {
|
} else if (!completeData.isOfficer && completeData.viewerData != null) {
|
||||||
await _supabase
|
await _supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.insert(completeData.profileData!.toJson());
|
.insert(completeData.viewerData!.toJson());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw 'Failed to update profile: ${e.toString()}';
|
throw 'Failed to update profile: ${e.toString()}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update user role after social authentication
|
||||||
|
Future<void> updateUserRoleOAuth({
|
||||||
|
required String userId,
|
||||||
|
required UserMetadataModel metadata,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
// Convert to UserModel for more functionality
|
||||||
|
final userMetadataModel = UserMetadataModel.fromInitUserMetadata(
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update user metadata in auth
|
||||||
|
await _supabase.auth.updateUser(
|
||||||
|
UserAttributes(data: userMetadataModel.toAuthMetadataJson()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store role information for later use
|
||||||
|
final localStorage = GetStorage();
|
||||||
|
localStorage.write('TEMP_USER_ID', userId);
|
||||||
|
localStorage.write('TEMP_ROLE_ID', metadata.roleId);
|
||||||
|
localStorage.write('IS_OFFICER', metadata.isOfficer);
|
||||||
|
} catch (e) {
|
||||||
|
throw 'Failed to update user role: ${e.toString()}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Update user role (officer/user) and metadata
|
// Update user role (officer/user) and metadata
|
||||||
Future<UserResponse> updateUserRole({
|
Future<UserResponse> updateUserRole({
|
||||||
required bool isOfficer,
|
required bool isOfficer,
|
||||||
|
@ -606,33 +651,6 @@ class AuthenticationRepository extends GetxController {
|
||||||
|
|
||||||
// Add these methods to the AuthenticationRepository class
|
// Add these methods to the AuthenticationRepository class
|
||||||
|
|
||||||
/// Update user role after social authentication
|
|
||||||
Future<void> updateUserRoleOAuth({
|
|
||||||
required String userId,
|
|
||||||
required UserMetadataModel metadata,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// Update user metadata in auth
|
|
||||||
await _supabase.auth.updateUser(
|
|
||||||
UserAttributes(
|
|
||||||
data: {
|
|
||||||
'is_officer': metadata.isOfficer,
|
|
||||||
'role_id': metadata.roleId,
|
|
||||||
'profile_status':
|
|
||||||
'incomplete', // Mark as incomplete until registration form is filled
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Store role information for later use
|
|
||||||
final localStorage = GetStorage();
|
|
||||||
localStorage.write('TEMP_USER_ID', userId);
|
|
||||||
localStorage.write('TEMP_ROLE_ID', metadata.roleId);
|
|
||||||
localStorage.write('IS_OFFICER', metadata.isOfficer);
|
|
||||||
} catch (e) {
|
|
||||||
throw 'Failed to update user role: ${e.toString()}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// BIOMETRIC AUTHENTICATION
|
// BIOMETRIC AUTHENTICATION
|
||||||
|
|
|
@ -35,6 +35,13 @@ class FormRegistrationController extends GetxController {
|
||||||
// User metadata model
|
// User metadata model
|
||||||
final Rx<UserMetadataModel> userMetadata = UserMetadataModel().obs;
|
final Rx<UserMetadataModel> userMetadata = UserMetadataModel().obs;
|
||||||
|
|
||||||
|
// Vievewer data
|
||||||
|
final Rx<UserModel?> viewerModel = Rx<UserModel?>(null);
|
||||||
|
final Rx<ProfileModel?> profileModel = Rx<ProfileModel?>(null);
|
||||||
|
|
||||||
|
// Officer data
|
||||||
|
final Rx<OfficerModel?> officerModel = Rx<OfficerModel?>(null);
|
||||||
|
|
||||||
// Loading state
|
// Loading state
|
||||||
final RxBool isLoading = false.obs;
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
@ -289,8 +296,7 @@ class FormRegistrationController extends GetxController {
|
||||||
|
|
||||||
if (isOfficerRole) {
|
if (isOfficerRole) {
|
||||||
// Officer role - create OfficerModel with the data
|
// Officer role - create OfficerModel with the data
|
||||||
final officerData = OfficerModel(
|
final officerData = officerModel.value?.copyWith(
|
||||||
id: '', // Will be assigned by backend
|
|
||||||
unitId: unitInfoController!.unitIdController.text,
|
unitId: unitInfoController!.unitIdController.text,
|
||||||
roleId: selectedRole.value!.id,
|
roleId: selectedRole.value!.id,
|
||||||
nrp: officerInfoController!.nrpController.text,
|
nrp: officerInfoController!.nrpController.text,
|
||||||
|
@ -302,8 +308,6 @@ class FormRegistrationController extends GetxController {
|
||||||
|
|
||||||
userMetadata.value = userMetadata.value.copyWith(
|
userMetadata.value = userMetadata.value.copyWith(
|
||||||
isOfficer: true,
|
isOfficer: true,
|
||||||
name: personalInfoController.nameController.text,
|
|
||||||
phone: personalInfoController.phoneController.text,
|
|
||||||
roleId: selectedRole.value!.id,
|
roleId: selectedRole.value!.id,
|
||||||
officerData: officerData,
|
officerData: officerData,
|
||||||
additionalData: {
|
additionalData: {
|
||||||
|
@ -311,24 +315,25 @@ class FormRegistrationController extends GetxController {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Regular user - create profile-related data
|
// Regular user - create profile-related data
|
||||||
final profileData = ProfileModel(
|
final viewerData = viewerModel.value?.copyWith(
|
||||||
id: '', // Will be assigned by backend
|
phone: personalInfoController.phoneController.text,
|
||||||
userId: userMetadata.value.userId ?? '',
|
profile: profileModel.value?.copyWith(
|
||||||
nik: identityController.nikController.text,
|
firstName: personalInfoController.firstNameController.text,
|
||||||
firstName: personalInfoController.firstNameController.text.trim(),
|
lastName: personalInfoController.lastNameController.text,
|
||||||
lastName: personalInfoController.lastNameController.text.trim(),
|
nik: identityController.nikController.text,
|
||||||
bio: identityController.bioController.text,
|
birthDate: _parseBirthDate(
|
||||||
birthDate: _parseBirthDate(identityController.birthDateController.text),
|
identityController.birthDateController.text,
|
||||||
|
),
|
||||||
|
address: {'address': personalInfoController.addressController.text},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
userMetadata.value = userMetadata.value.copyWith(
|
userMetadata.value = userMetadata.value.copyWith(
|
||||||
isOfficer: false,
|
isOfficer: false,
|
||||||
nik: identityController.nikController.text,
|
|
||||||
name: personalInfoController.nameController.text,
|
|
||||||
phone: personalInfoController.phoneController.text,
|
|
||||||
roleId: selectedRole.value!.id,
|
roleId: selectedRole.value!.id,
|
||||||
profileData: profileData,
|
viewerData: viewerData,
|
||||||
additionalData: {
|
additionalData: {
|
||||||
'address': personalInfoController.addressController.text,
|
'address': personalInfoController.addressController.text,
|
||||||
},
|
},
|
||||||
|
|
|
@ -197,35 +197,52 @@ class SignupWithRoleController extends GetxController {
|
||||||
isOfficer: isOfficer,
|
isOfficer: isOfficer,
|
||||||
);
|
);
|
||||||
|
|
||||||
// First create the basic account with email/password
|
try {
|
||||||
final authResponse = await AuthenticationRepository.instance
|
// First create the basic account with email/password
|
||||||
.initialSignUp(
|
final authResponse = await AuthenticationRepository.instance
|
||||||
email: emailController.text.trim(),
|
.initialSignUp(
|
||||||
password: passwordController.text.trim(),
|
email: emailController.text.trim(),
|
||||||
initialData: initialMetadata,
|
password: passwordController.text.trim(),
|
||||||
);
|
initialData: initialMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
// Store email for verification
|
// Check if authResponse has a user property
|
||||||
storage.write('CURRENT_USER_EMAIL', emailController.text.trim());
|
if (authResponse.user == null || authResponse.session == null) {
|
||||||
storage.write('TEMP_AUTH_TOKEN', authResponse.session?.accessToken);
|
throw Exception('Failed to create account. Please try again.');
|
||||||
storage.write('TEMP_USER_ID', authResponse.user?.id);
|
}
|
||||||
storage.write('TEMP_ROLE_ID', selectedRoleId.value);
|
|
||||||
|
|
||||||
// Navigate to registration form
|
// Store email for verification
|
||||||
Get.offNamed(
|
storage.write('CURRENT_USER_EMAIL', emailController.text.trim());
|
||||||
AppRoutes.registrationForm,
|
storage.write('TEMP_AUTH_TOKEN', authResponse.session?.accessToken);
|
||||||
arguments: {
|
storage.write('TEMP_USER_ID', authResponse.user?.id);
|
||||||
'role': selectedRole.value,
|
storage.write('TEMP_ROLE_ID', selectedRoleId.value);
|
||||||
'userId': authResponse.user?.id,
|
|
||||||
'initialData': initialMetadata,
|
// Navigate to registration form
|
||||||
},
|
Get.offNamed(
|
||||||
);
|
AppRoutes.registrationForm,
|
||||||
|
arguments: {
|
||||||
|
'role': selectedRole.value,
|
||||||
|
'userId': authResponse.user?.id,
|
||||||
|
'initialData': initialMetadata,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (authError) {
|
||||||
|
// Handle specific authentication errors
|
||||||
|
Logger().e('Error during signup: $authError');
|
||||||
|
TLoaders.errorSnackBar(
|
||||||
|
title: 'Registration Failed',
|
||||||
|
message: authError.toString(),
|
||||||
|
);
|
||||||
|
// Important: Do not navigate or redirect on error
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger().e('Error during signup: $e');
|
Logger().e('Error during signup: $e');
|
||||||
TLoaders.errorSnackBar(
|
TLoaders.errorSnackBar(
|
||||||
title: 'Registration Failed',
|
title: 'Registration Failed',
|
||||||
message: e.toString(),
|
message: e.toString(),
|
||||||
);
|
);
|
||||||
|
// No navigation on error
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -261,6 +278,10 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Check if authResponse has a user property
|
// Check if authResponse has a user property
|
||||||
final userId = AuthenticationRepository.instance.currentUserId;
|
final userId = AuthenticationRepository.instance.currentUserId;
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
throw Exception("Failed to authenticate. Please try again.");
|
||||||
|
}
|
||||||
|
|
||||||
// Create or update user metadata with role information
|
// Create or update user metadata with role information
|
||||||
final userMetadata = UserMetadataModel(
|
final userMetadata = UserMetadataModel(
|
||||||
|
@ -270,7 +291,7 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Update user metadata in the database
|
// Update user metadata in the database
|
||||||
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
||||||
userId: userId!,
|
userId: userId,
|
||||||
metadata: userMetadata,
|
metadata: userMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -332,6 +353,12 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Check if authResponse has a user property
|
// Check if authResponse has a user property
|
||||||
final userId = AuthenticationRepository.instance.currentUserId;
|
final userId = AuthenticationRepository.instance.currentUserId;
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
throw Exception(
|
||||||
|
"Failed to authenticate with Apple ID. Please try again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create or update user metadata with role information
|
// Create or update user metadata with role information
|
||||||
final userMetadata = UserMetadataModel(
|
final userMetadata = UserMetadataModel(
|
||||||
|
@ -341,7 +368,7 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Update user metadata in the database
|
// Update user metadata in the database
|
||||||
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
||||||
userId: userId!,
|
userId: userId,
|
||||||
metadata: userMetadata,
|
metadata: userMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -360,6 +387,7 @@ class SignupWithRoleController extends GetxController {
|
||||||
title: 'Authentication Failed',
|
title: 'Authentication Failed',
|
||||||
message: e.toString(),
|
message: e.toString(),
|
||||||
);
|
);
|
||||||
|
// No redirection on error - user stays on current page
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -395,6 +423,12 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Check if authResponse has a user property
|
// Check if authResponse has a user property
|
||||||
final userId = AuthenticationRepository.instance.currentUserId;
|
final userId = AuthenticationRepository.instance.currentUserId;
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
throw Exception(
|
||||||
|
"Failed to authenticate with Facebook. Please try again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create or update user metadata with role information
|
// Create or update user metadata with role information
|
||||||
final userMetadata = UserMetadataModel(
|
final userMetadata = UserMetadataModel(
|
||||||
|
@ -404,7 +438,7 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Update user metadata in the database
|
// Update user metadata in the database
|
||||||
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
||||||
userId: userId!,
|
userId: userId,
|
||||||
metadata: userMetadata,
|
metadata: userMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -423,6 +457,7 @@ class SignupWithRoleController extends GetxController {
|
||||||
title: 'Authentication Failed',
|
title: 'Authentication Failed',
|
||||||
message: e.toString(),
|
message: e.toString(),
|
||||||
);
|
);
|
||||||
|
// No redirection on error - user stays on current page
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -461,6 +496,12 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Check if authResponse has a user property
|
// Check if authResponse has a user property
|
||||||
final userId = AuthenticationRepository.instance.currentUserId;
|
final userId = AuthenticationRepository.instance.currentUserId;
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
throw Exception(
|
||||||
|
"Failed to sign in with email. Please check your credentials and try again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create or update user metadata with role information
|
// Create or update user metadata with role information
|
||||||
final userMetadata = UserMetadataModel(
|
final userMetadata = UserMetadataModel(
|
||||||
|
@ -470,7 +511,7 @@ class SignupWithRoleController extends GetxController {
|
||||||
|
|
||||||
// Update user metadata in the database
|
// Update user metadata in the database
|
||||||
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
await AuthenticationRepository.instance.updateUserRoleOAuth(
|
||||||
userId: userId!,
|
userId: userId,
|
||||||
metadata: userMetadata,
|
metadata: userMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -489,6 +530,7 @@ class SignupWithRoleController extends GetxController {
|
||||||
title: 'Authentication Failed',
|
title: 'Authentication Failed',
|
||||||
message: e.toString(),
|
message: e.toString(),
|
||||||
);
|
);
|
||||||
|
// No redirection on error - user stays on current page
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ class IdCardVerificationController extends GetxController {
|
||||||
idCardImage.value!,
|
idCardImage.value!,
|
||||||
isOfficer,
|
isOfficer,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we get here without an exception, the image is likely valid
|
// If we get here without an exception, the image is likely valid
|
||||||
isImageValid = result.isNotEmpty;
|
isImageValid = result.isNotEmpty;
|
||||||
|
|
||||||
|
|
|
@ -47,31 +47,23 @@ class IdCardVerificationStep extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: TSizes.spaceBtwItems),
|
const SizedBox(height: TSizes.spaceBtwItems),
|
||||||
|
|
||||||
|
// Error Messages
|
||||||
|
Obx(
|
||||||
|
() =>
|
||||||
|
controller.idCardError.value.isNotEmpty
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.only(top: TSizes.sm),
|
||||||
|
child: Text(
|
||||||
|
controller.idCardError.value,
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
|
||||||
// ID Card Upload Widget
|
// ID Card Upload Widget
|
||||||
_buildIdCardUploader(controller, isOfficer),
|
_buildIdCardUploader(controller, isOfficer),
|
||||||
|
|
||||||
// Verification Status for ID Card
|
|
||||||
// Obx(
|
|
||||||
// () =>
|
|
||||||
// controller.isVerifying.value &&
|
|
||||||
// !controller.isUploadingIdCard.value
|
|
||||||
// ? const Padding(
|
|
||||||
// padding: EdgeInsets.symmetric(
|
|
||||||
// vertical: TSizes.spaceBtwItems,
|
|
||||||
// ),
|
|
||||||
// child: Center(
|
|
||||||
// child: Column(
|
|
||||||
// children: [
|
|
||||||
// CircularProgressIndicator(),
|
|
||||||
// SizedBox(height: TSizes.sm),
|
|
||||||
// Text('Validating your ID card...'),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// : const SizedBox.shrink(),
|
|
||||||
// ),
|
|
||||||
|
|
||||||
// Verification Message for ID Card
|
// Verification Message for ID Card
|
||||||
Obx(
|
Obx(
|
||||||
() =>
|
() =>
|
||||||
|
@ -178,19 +170,7 @@ class IdCardVerificationStep extends StatelessWidget {
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Error Messages
|
|
||||||
Obx(
|
|
||||||
() =>
|
|
||||||
controller.idCardError.value.isNotEmpty
|
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.only(top: TSizes.sm),
|
|
||||||
child: Text(
|
|
||||||
controller.idCardError.value,
|
|
||||||
style: const TextStyle(color: Colors.red),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -154,7 +154,7 @@ enable_signup = true
|
||||||
# addresses. If disabled, only the new email is required to confirm.
|
# addresses. If disabled, only the new email is required to confirm.
|
||||||
double_confirm_changes = true
|
double_confirm_changes = true
|
||||||
# If enabled, users need to confirm their email address before signing in.
|
# If enabled, users need to confirm their email address before signing in.
|
||||||
enable_confirmations = false
|
enable_confirmations = true
|
||||||
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
||||||
secure_password_change = false
|
secure_password_change = false
|
||||||
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
||||||
|
|
Loading…
Reference in New Issue