feat: SMS OTP phone auth + mandatory phone verification gate
- MyUser: add isPhoneVerified field (from is_phone_verified in API) - ApiUserRepository: signInWithPhoneNumber calls POST /auth/send-otp, verifyOTP calls POST /auth/phone, linkWithOTP calls POST /auth/verify-phone Added updateFcmToken and changePassword methods - AuthBloc: remove Firebase PhoneVerificationService, use repository OTP flow - app_view.dart: gate authenticated users without verified phone to PhoneVerifyRequiredScreen before showing HomeScreen - welcome_screen.dart: full redesign with gradient header, logo, integrated OTP flow (phone → code in same screen), 60s resend timer - PhoneVerifyRequiredScreen: new screen for users who need to verify phone Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
38beca4b4f
commit
2c5b237890
@@ -13,6 +13,7 @@ class MyUser extends Equatable {
|
||||
final String? gender;
|
||||
final ProState proState;
|
||||
final String? token;
|
||||
final bool isPhoneVerified;
|
||||
|
||||
const MyUser({
|
||||
required this.id,
|
||||
@@ -26,6 +27,7 @@ class MyUser extends Equatable {
|
||||
this.gender,
|
||||
required this.proState,
|
||||
this.token,
|
||||
this.isPhoneVerified = false,
|
||||
});
|
||||
|
||||
get drawerLabel => email != null && email!.isNotEmpty
|
||||
@@ -47,6 +49,7 @@ class MyUser extends Equatable {
|
||||
gender: '',
|
||||
proState: ProState.inactive,
|
||||
token: '',
|
||||
isPhoneVerified: false,
|
||||
);
|
||||
|
||||
/// Modify MyUser parameters
|
||||
@@ -62,6 +65,7 @@ class MyUser extends Equatable {
|
||||
String? gender,
|
||||
ProState? proState,
|
||||
String? token,
|
||||
bool? isPhoneVerified,
|
||||
}) {
|
||||
return MyUser(
|
||||
id: id ?? this.id,
|
||||
@@ -75,6 +79,7 @@ class MyUser extends Equatable {
|
||||
gender: gender ?? this.gender,
|
||||
proState: proState ?? this.proState,
|
||||
token: token ?? this.token,
|
||||
isPhoneVerified: isPhoneVerified ?? this.isPhoneVerified,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,5 +134,6 @@ class MyUser extends Equatable {
|
||||
gender,
|
||||
proState,
|
||||
token,
|
||||
isPhoneVerified,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ class ApiUserRepository implements UserRepository {
|
||||
gender: json['gender']?.toString(),
|
||||
proState: _proStateFromInt((json['pro_state'] as num?)?.toInt() ?? 0),
|
||||
token: null,
|
||||
isPhoneVerified: json['is_phone_verified'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,9 +118,7 @@ class ApiUserRepository implements UserRepository {
|
||||
_controller.add(user);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signIn(String email, String password) async {
|
||||
final data = await _post('/auth/login', {'email': email, 'password': password});
|
||||
Future<void> _saveTokenAndEmit(Map<String, dynamic> data) async {
|
||||
_token = data['access_token'] as String?;
|
||||
if (_token != null) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -129,6 +128,12 @@ class ApiUserRepository implements UserRepository {
|
||||
_emit(_current);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signIn(String email, String password) async {
|
||||
final data = await _post('/auth/login', {'email': email, 'password': password});
|
||||
await _saveTokenAndEmit(data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MyUser> signUp(MyUser myUser, String password) async {
|
||||
final data = await _post('/auth/register', {
|
||||
@@ -136,13 +141,7 @@ class ApiUserRepository implements UserRepository {
|
||||
'password': password,
|
||||
'name': myUser.name ?? myUser.email ?? '',
|
||||
});
|
||||
_token = data['access_token'] as String?;
|
||||
if (_token != null) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('token', _token!);
|
||||
}
|
||||
_current = _fromApi(data['user'] as Map<String, dynamic>);
|
||||
_emit(_current);
|
||||
await _saveTokenAndEmit(data);
|
||||
return _current!;
|
||||
}
|
||||
|
||||
@@ -270,13 +269,7 @@ class ApiUserRepository implements UserRepository {
|
||||
'phone': _pendingOtpPhone!,
|
||||
'code': code,
|
||||
});
|
||||
_token = data['access_token'] as String?;
|
||||
if (_token != null) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('token', _token!);
|
||||
}
|
||||
_current = _fromApi(data['user'] as Map<String, dynamic>);
|
||||
_emit(_current);
|
||||
await _saveTokenAndEmit(data);
|
||||
_pendingOtpPhone = null;
|
||||
return true;
|
||||
} catch (_) {
|
||||
@@ -309,4 +302,22 @@ class ApiUserRepository implements UserRepository {
|
||||
Future<void> resetPassword(String email) async {
|
||||
// Not supported by current API — stub
|
||||
}
|
||||
|
||||
Future<void> updateFcmToken(String token) async {
|
||||
try {
|
||||
await _post('/users/me/fcm-token', {'token': token}, auth: true);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<bool> changePassword(String currentPassword, String newPassword) async {
|
||||
try {
|
||||
await _post('/auth/change-password', {
|
||||
'current_password': currentPassword,
|
||||
'new_password': newPassword,
|
||||
}, auth: true);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user