fix logout issue

This commit is contained in:
tzw
2024-02-23 17:05:51 +06:30
parent 421bcf0a11
commit 5496bae681
23 changed files with 144 additions and 84 deletions

View File

@@ -12,6 +12,8 @@ import 'package:fcs/helpers/firebase_helper.dart';
import 'package:firebase_auth/firebase_auth.dart' as fb; import 'package:firebase_auth/firebase_auth.dart' as fb;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import '../services/services.dart';
class AuthFb { class AuthFb {
final log = Logger('AuthFb'); final log = Logger('AuthFb');
@@ -21,6 +23,10 @@ class AuthFb {
late StreamController<User?> controller; late StreamController<User?> controller;
static final fb.FirebaseAuth _fb = fb.FirebaseAuth.instance; static final fb.FirebaseAuth _fb = fb.FirebaseAuth.instance;
static String _verificationId = ''; static String _verificationId = '';
static bool _logIn = false;
StreamSubscription<DocumentSnapshot>? userListener;
StreamSubscription<DocumentSnapshot>? userAuthListener;
Future<fcs.AuthResult> sendSmsCodeToPhoneNumber(String phoneNumber) { Future<fcs.AuthResult> sendSmsCodeToPhoneNumber(String phoneNumber) {
Completer<fcs.AuthResult> completer = Completer(); Completer<fcs.AuthResult> completer = Completer();
@@ -99,9 +105,13 @@ class AuthFb {
return Future.value(fcs.AuthResult(authStatus: AuthStatus.AUTH_VERIFIED)); return Future.value(fcs.AuthResult(authStatus: AuthStatus.AUTH_VERIFIED));
} }
Future<void> signout() async { Future<void> signoutStart() async {
if (userListener != null) await userListener!.cancel(); await userListener?.cancel();
return _fb.signOut(); await userAuthListener?.cancel();
}
Future<void> signoutEnd() async {
await _fb.signOut();
} }
Future<void> _addUserToStream({bool refreshIdToken = false}) async { Future<void> _addUserToStream({bool refreshIdToken = false}) async {
@@ -111,7 +121,6 @@ class AuthFb {
await getClaims(refreshIdToken: refreshIdToken); await getClaims(refreshIdToken: refreshIdToken);
log.info("Claims:$claims"); log.info("Claims:$claims");
if (claims == null) return;
String? cid = claims["cid"]; String? cid = claims["cid"];
User? user; User? user;
@@ -123,6 +132,11 @@ class AuthFb {
return; return;
} }
loadUserClaim(claims, user);
controller.add(user);
}
loadUserClaim(Map claims, User user) {
// add privileges // add privileges
String? privileges = claims["pr"]; String? privileges = claims["pr"];
if (privileges != null && privileges != "") { if (privileges != null && privileges != "") {
@@ -130,7 +144,6 @@ class AuthFb {
} else { } else {
user.privileges = []; user.privileges = [];
} }
controller.add(user);
} }
Future<User?> _getUserFromFirestore(String userID) async { Future<User?> _getUserFromFirestore(String userID) async {
@@ -205,13 +218,12 @@ class AuthFb {
fb.User? firebaseUser = _fb.currentUser; fb.User? firebaseUser = _fb.currentUser;
if (firebaseUser == null) return null; if (firebaseUser == null) return null;
Map? claims = await getClaims(); Map? claims = await getClaims();
if (claims == null) return null;
String cid = claims["cid"]; String cid = claims["cid"];
return cid; return cid;
} }
Future<void> _startUserListener() async { Future<void> _startUserListener() async {
if (userListener != null) userListener!.cancel(); _startAuthListener();
String? _userID = await _getCurrentUserID(); String? _userID = await _getCurrentUserID();
if (_userID == null) { if (_userID == null) {
return; return;
@@ -221,6 +233,7 @@ class AuthFb {
.collection(user_collection) .collection(user_collection)
.doc(_userID) .doc(_userID)
.snapshots(); .snapshots();
userListener?.cancel();
userListener = snapshot.listen((snap) async { userListener = snapshot.listen((snap) async {
User user = User.fromMap(snap.data() as Map<String, dynamic>, snap.id); User user = User.fromMap(snap.data() as Map<String, dynamic>, snap.id);
@@ -229,14 +242,12 @@ class AuthFb {
userListener?.cancel(); userListener?.cancel();
return; return;
} }
if (!_logIn) return;
try { try {
// get privilege from claim // get privilege from claim
fb.IdTokenResult idToken = await firebaseUser.getIdTokenResult(true); Map<dynamic, dynamic> claims = await getClaims(refreshIdToken: true);
loadUserClaim(claims, user);
String? privileges = idToken.claims?["pr"] ?? '';
if (privileges != null && privileges != "") {
user.privileges = privileges.split(":").toList();
}
controller.add(user); controller.add(user);
} catch (e) { } catch (e) {
controller.add(null); controller.add(null);
@@ -244,14 +255,47 @@ class AuthFb {
}); });
} }
StreamSubscription<DocumentSnapshot>? userListener; Future<void> _startAuthListener() async {
String? authId = _fb.currentUser?.uid;
if (authId == null) return;
Stream<DocumentSnapshot> snapshot = FirebaseFirestore.instance
.collection(authCollection)
.doc(authId)
.snapshots();
userAuthListener?.cancel();
userAuthListener = snapshot.listen((snap) async {
if (snap.exists) {
Map<String, dynamic> map = snap.data() as Map<String, dynamic>;
String userID = map['user_id'] ?? "";
User? user = await Services.instance.userService.getUser(userID);
if (user == null) return;
if (_fb.currentUser == null) {
userAuthListener?.cancel();
return;
}
Map<dynamic, dynamic> claims = await getClaims(refreshIdToken: true);
loadUserClaim(claims, user);
log.info("_startAuthListener: $user");
if (_logIn) {
controller.add(user);
}
}
});
}
Stream<User?> user() { Stream<User?> user() {
// ignore: close_sinks // ignore: close_sinks
StreamSubscription<fb.User?>? authListener; StreamSubscription<fb.User?>? authListener;
Future<void> _start() async { Future<void> _start() async {
await authListener?.cancel(); await authListener?.cancel();
authListener = _fb.authStateChanges().listen((firebaseUser) { authListener = _fb.authStateChanges().listen((firebaseUser) async {
_logIn = firebaseUser != null;
if (firebaseUser == null) { if (firebaseUser == null) {
controller.add(null); controller.add(null);
} else { } else {

View File

@@ -85,4 +85,29 @@ class UserDataProvider {
return await requestAPI("/enable_user", "PUT", return await requestAPI("/enable_user", "PUT",
payload: {"id": userID, "enabled": enabled}, token: await getToken()); payload: {"id": userID, "enabled": enabled}, token: await getToken());
} }
Future<User?> getUser(String userID) async {
if (userID == "") return null;
String path = "/$user_collection";
try {
var snap = await FirebaseFirestore.instance
.collection(path)
.doc(userID)
.get(const GetOptions(source: Source.server));
if (snap.data() == null) return null;
Map<String, dynamic>? data = snap.data() as Map<String, dynamic>;
if (data['delete_time'] == 0) {
User user = User.fromMap(data, snap.id);
return user;
} else {
return null;
}
} catch (e) {
log.warning("Error!! $e");
}
return null;
}
} }

View File

@@ -25,11 +25,6 @@ class AuthServiceImp implements AuthService {
return authFb.signInWithPhoneNumber(smsCode); return authFb.signInWithPhoneNumber(smsCode);
} }
@override
Future<void> signout() {
return authFb.signout();
}
@override @override
Stream<User?> getUserStream() { Stream<User?> getUserStream() {
return authFb.user(); return authFb.user();
@@ -64,4 +59,14 @@ class AuthServiceImp implements AuthService {
Future<void> updatePreferredCurrency(String currency) { Future<void> updatePreferredCurrency(String currency) {
return authFb.updatePreferredCurrency(currency); return authFb.updatePreferredCurrency(currency);
} }
@override
Future<void> signoutEnd() {
return authFb.signoutEnd();
}
@override
Future<void> signoutStart() {
return authFb.signoutStart();
}
} }

View File

@@ -5,7 +5,8 @@ import 'package:fcs/domain/entities/user.dart';
abstract class AuthService { abstract class AuthService {
Future<AuthResult> sendSmsCodeToPhoneNumber(String phoneNumber); Future<AuthResult> sendSmsCodeToPhoneNumber(String phoneNumber);
Future<AuthResult> signInWithSmsCode(String smsCode); Future<AuthResult> signInWithSmsCode(String smsCode);
Future<void> signout(); Future<void> signoutStart();
Future<void> signoutEnd();
Future<void> signup(String userName); Future<void> signup(String userName);
Future<void> joinInvite(String userName); Future<void> joinInvite(String userName);
Future<void> updateProfileName(String newUserName); Future<void> updateProfileName(String newUserName);

View File

@@ -52,4 +52,9 @@ class UserServiceImp implements UserService {
Future<void> enableUser(String userID, bool enabled) { Future<void> enableUser(String userID, bool enabled) {
return userDataProvider.enableUser(userID, enabled); return userDataProvider.enableUser(userID, enabled);
} }
@override
Future<User?> getUser(String userID) {
return userDataProvider.getUser(userID);
}
} }

View File

@@ -9,4 +9,5 @@ abstract class UserService {
Future<void> uploadMsgToken(String token); Future<void> uploadMsgToken(String token);
Future<void> removeMsgToken(String token); Future<void> removeMsgToken(String token);
Future<void> enableUser(String userID, bool enabled); Future<void> enableUser(String userID, bool enabled);
Future<User?> getUser(String userID);
} }

View File

@@ -2,6 +2,7 @@ const uploadPhotoLimit = 10;
const config_collection = "configs"; const config_collection = "configs";
const user_collection = "users"; const user_collection = "users";
const authCollection = "auths";
const invitations_collection = "invitations"; const invitations_collection = "invitations";
const privilege_collection = "privileges"; const privilege_collection = "privileges";
const markets_collection = "markets"; const markets_collection = "markets";

View File

@@ -21,7 +21,6 @@ class User {
String? preferCurrency; String? preferCurrency;
bool enablePinLogin; bool enablePinLogin;
String? pinDigit; String? pinDigit;
String? confirmPinDigit;
String get initial => String get initial =>
name != null && name != "" ? name!.substring(0, 1) : "?"; name != null && name != "" ? name!.substring(0, 1) : "?";
@@ -60,8 +59,8 @@ class User {
bool get disabled => status != null && status == user_disabled_status; bool get disabled => status != null && status == user_disabled_status;
String get share => "Your phone number:$phoneNumber"; String get share => "Your phone number:$phoneNumber";
User( User({
{this.id, this.id,
this.name, this.name,
this.phoneNumber, this.phoneNumber,
this.fcsID, this.fcsID,
@@ -74,7 +73,7 @@ class User {
this.preferCurrency, this.preferCurrency,
this.enablePinLogin = false, this.enablePinLogin = false,
this.pinDigit, this.pinDigit,
this.confirmPinDigit}); });
factory User.fromJson(Map<String, dynamic> json) { factory User.fromJson(Map<String, dynamic> json) {
return User( return User(

View File

@@ -15,12 +15,12 @@ Future<String> getToken() async {
return token; return token;
} }
Future<Map?> getClaims({bool refreshIdToken = false}) async { Future<Map> getClaims({bool refreshIdToken = false}) async {
fb.User? firebaseUser = auth.currentUser; fb.User? firebaseUser = auth.currentUser;
if (firebaseUser == null) return null; if (firebaseUser == null) return {};
fb.IdTokenResult idToken = fb.IdTokenResult idToken =
await firebaseUser.getIdTokenResult(refreshIdToken); await firebaseUser.getIdTokenResult(refreshIdToken);
return idToken.claims; return idToken.claims ?? {};
} }
// returns list of url // returns list of url

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/services.dart' show rootBundle;

View File

@@ -4,7 +4,6 @@ import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/fcs_id_icon.dart'; import 'package:fcs/pages/widgets/fcs_id_icon.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart'; import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@@ -77,7 +77,7 @@ class _CustomerListState extends State<CustomerList> {
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: new Padding( child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: new Row( child: new Row(
children: <Widget>[ children: <Widget>[
InkWell( InkWell(

View File

@@ -1,5 +1,3 @@
import 'dart:ui';
import 'package:country_code_picker/country_code_picker.dart'; import 'package:country_code_picker/country_code_picker.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/customer/model/customer_model.dart'; import 'package:fcs/pages/customer/model/customer_model.dart';

View File

@@ -2,7 +2,6 @@ import 'package:fcs/domain/entities/shipment.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart'; import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef OnAdd(Shipment shipment); typedef OnAdd(Shipment shipment);

View File

@@ -10,7 +10,7 @@ abstract class BaseModel extends ChangeNotifier {
Setting? setting; Setting? setting;
MainModel? mainModel; MainModel? mainModel;
void initUser(User user) async { void initUser(User? user) async {
this.user = user; this.user = user;
} }

View File

@@ -87,23 +87,6 @@ class MainModel extends ChangeNotifier {
userListener?.cancel(); userListener?.cancel();
userListener = userListener =
Services.instance.authService.getUserStream().listen((_user) { Services.instance.authService.getUserStream().listen((_user) {
// if (_user != null) {
// models.forEach((m) => m.initUser(_user));
// // call diffPrivileges if privilege changed or first time login
// if (this.user == null || _user.diffPrivileges(this.user!)) {
// models.forEach((m) => m.privilegeChanged());
// }
// if (this.user == null) {
// uploadMsgToken();
// }
// } else {
// if (this.user != null) {
// models.forEach((m) => m.logout());
// }
// }
// this.user = _user;
// isLoaded = true;
bool isFirstTime = user == null && _user != null; bool isFirstTime = user == null && _user != null;
bool diffPrivilege = bool diffPrivilege =
_user != null && (user == null || user!.diffPrivileges(_user)); _user != null && (user == null || user!.diffPrivileges(_user));
@@ -163,7 +146,6 @@ class MainModel extends ChangeNotifier {
} }
Future<void> _uploadMsgToken() { Future<void> _uploadMsgToken() {
log.info("messagingToken:$messagingToken::user:$user");
if (messagingToken == null || user == null) return Future.value(); if (messagingToken == null || user == null) return Future.value();
return Services.instance.userService.uploadMsgToken(messagingToken!); return Services.instance.userService.uploadMsgToken(messagingToken!);
} }
@@ -175,12 +157,17 @@ class MainModel extends ChangeNotifier {
Future<void> signout() async { Future<void> signout() async {
try { try {
await Services.instance.authService.signoutStart();
await _removeMsgToken(); await _removeMsgToken();
} catch (e) { for (var i = 0; i < models.length; i++) {
log.info(e.toString()); models[i].initUser(null);
models[i].logout();
}
await Services.instance.authService.signoutEnd();
} catch (e) {
log.info("signout:${e.toString()}");
} }
await Services.instance.authService.signout();
models.forEach((m) => m.logout());
} }
Future<bool> hasInvite() async { Future<bool> hasInvite() async {

View File

@@ -10,7 +10,6 @@ import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/local_dropdown.dart'; import 'package:fcs/pages/widgets/local_dropdown.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/progress.dart'; import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart'; import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@@ -81,7 +81,7 @@ class StaffModel extends BaseModel {
Future<void> updatePin( Future<void> updatePin(
{required String userID, {required String userID,
required bool enablePin, required bool enablePin,
required int pin}) async { required int? pin}) async {
// await request("/employee/pin", "PUT", // await request("/employee/pin", "PUT",
// payload: { // payload: {
// "id": userID, // "id": userID,

View File

@@ -39,7 +39,7 @@ class _StaffPinEditorState extends State<StaffPinEditor> {
_staff = widget.staff; _staff = widget.staff;
_enablePinLogin = _staff.enablePinLogin; _enablePinLogin = _staff.enablePinLogin;
_newPin = _staff.pinDigit ?? ""; _newPin = _staff.pinDigit ?? "";
_confirmPin = _staff.confirmPinDigit ?? ""; _confirmPin = _staff.pinDigit ?? "";
_newPinCtl.text = _newPin; _newPinCtl.text = _newPin;
_confirmPinCtl.text = _confirmPin; _confirmPinCtl.text = _confirmPin;
_checkFocusNode(); _checkFocusNode();
@@ -298,7 +298,9 @@ class _StaffPinEditorState extends State<StaffPinEditor> {
} }
_save() async { _save() async {
if (_enablePinLogin) {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
}
setState(() { setState(() {
_isLoading = true; _isLoading = true;
@@ -308,7 +310,7 @@ class _StaffPinEditorState extends State<StaffPinEditor> {
await context.read<StaffModel>().updatePin( await context.read<StaffModel>().updatePin(
userID: _staff.id!, userID: _staff.id!,
enablePin: _enablePinLogin, enablePin: _enablePinLogin,
pin: int.parse(_confirmPin)); pin: _enablePinLogin ? int.parse(_confirmPin) : null);
Navigator.pop(context, true); Navigator.pop(context, true);
} catch (e) { } catch (e) {
showMsgDialog(context, "Error", e.toString()); showMsgDialog(context, "Error", e.toString());

View File

@@ -1,5 +1,4 @@
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'callbacks.dart'; import 'callbacks.dart';

View File

@@ -1,6 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'show_img.dart'; import 'show_img.dart';

View File

@@ -1,7 +1,6 @@
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/localization/app_translations.dart'; import 'package:fcs/localization/app_translations.dart';
import 'package:fcs/pages/main/model/language_model.dart'; import 'package:fcs/pages/main/model/language_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';