diff --git a/.gitignore b/.gitignore index c4c7ab6..734f9d4 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages android/key.properties +android/key.jks diff --git a/.vscode/launch.json b/.vscode/launch.json index 62ecdbb..e7b950e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,19 @@ "version": "0.2.0", "configurations": [ { - "name": "Flutter Dev", + "name": "Local Dev", + "request": "launch", + "type": "dart", + "program": "lib/main-local.dart", + "args": [ + "-t", + "lib/main-local.dart", + "--flavor", + "dev" + ], + }, + { + "name": "Dev", "request": "launch", "type": "dart", "program": "lib/main-dev.dart", @@ -14,7 +26,7 @@ ], }, { - "name": "Flutter Pro", + "name": "Pro", "request": "launch", "type": "dart", "program": "lib/main-prod.dart", diff --git a/lib/fcs/common/data/providers/auth_fb.dart b/lib/fcs/common/data/providers/auth_fb.dart new file mode 100644 index 0000000..9a9e19b --- /dev/null +++ b/lib/fcs/common/data/providers/auth_fb.dart @@ -0,0 +1,82 @@ +import 'dart:async'; + +import 'package:fcs/fcs/common/domain/entities/auth.dart'; +import 'package:fcs/fcs/common/domain/entities/auth_status.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +class AuthFb { + static final AuthFb instance = AuthFb._(); + AuthFb._(); + + final FirebaseAuth _fb = FirebaseAuth.instance; + static String _verificationId; + + Future sendSmsCodeToPhoneNumber(String phoneNumber) { + Completer completer = Completer(); + + final PhoneVerificationCompleted verificationCompleted = + (AuthCredential user) { + completer.complete(Auth(authStatus: AuthStatus.AUTH_VERIFIED)); + print( + 'Inside _sendCodeToPhoneNumber: signInWithPhoneNumber auto succeeded: $user'); + }; + + final PhoneVerificationFailed verificationFailed = + (AuthException authException) async { + print( + 'Phone number verification failed. Code: ${authException.code}. Message: ${authException.message}'); + completer.complete(Auth( + authStatus: AuthStatus.ERROR, + authErrorCode: authException.code, + authErrorMsg: authException.message)); + throw authException; + }; + + final PhoneCodeSent codeSent = + (String verificationId, [int forceResendingToken]) async { + _verificationId = verificationId; + print("code sent to " + phoneNumber); + completer.complete(Auth(authStatus: AuthStatus.SMS_SENT)); + }; + + final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout = + (String verificationId) { + _verificationId = verificationId; + }; + + _fb.verifyPhoneNumber( + phoneNumber: phoneNumber, + timeout: const Duration(minutes: 2), + verificationCompleted: verificationCompleted, + verificationFailed: verificationFailed, + codeSent: codeSent, + codeAutoRetrievalTimeout: codeAutoRetrievalTimeout); + + return completer.future; + } + + Future signInWithPhoneNumber(String smsCode) async { + Auth auth = Auth(); + try { + final AuthCredential credential = PhoneAuthProvider.getCredential( + verificationId: _verificationId, + smsCode: smsCode, + ); + + var firebaseUser = await _fb.signInWithCredential(credential); + final FirebaseUser currentUser = await _fb.currentUser(); + assert(firebaseUser.user.uid == currentUser.uid); + + auth.uid = firebaseUser.user.uid; + auth.authStatus = AuthStatus.AUTH_VERIFIED; + } on Exception catch (e) { + auth.authStatus = AuthStatus.ERROR; + auth.authErrorMsg = e.toString(); + } + return Future.value(auth); + } + + Future logout() { + return _fb.signOut(); + } +} diff --git a/lib/fcs/common/data/providers/user_fb_data_provider.dart b/lib/fcs/common/data/providers/user_fb_data_provider.dart new file mode 100644 index 0000000..d5e836c --- /dev/null +++ b/lib/fcs/common/data/providers/user_fb_data_provider.dart @@ -0,0 +1,7 @@ +import 'package:fcs/fcs/common/domain/entities/user.dart'; + +class UserFBDataProvider { + Future getUser(String id) { + return null; + } +} diff --git a/lib/fcs/common/data/providers/user_local_data_provider.dart b/lib/fcs/common/data/providers/user_local_data_provider.dart new file mode 100644 index 0000000..cce7642 --- /dev/null +++ b/lib/fcs/common/data/providers/user_local_data_provider.dart @@ -0,0 +1,7 @@ +import 'package:fcs/fcs/common/domain/entities/user.dart'; + +class UserLocalDataProvider { + Future getUser(String id) { + return null; + } +} diff --git a/lib/fcs/common/domain/entities/auth.dart b/lib/fcs/common/domain/entities/auth.dart new file mode 100644 index 0000000..5d173e3 --- /dev/null +++ b/lib/fcs/common/domain/entities/auth.dart @@ -0,0 +1,11 @@ +import 'auth_status.dart'; + +class Auth { + AuthStatus authStatus; + String authErrorCode; + String authErrorMsg; + + String uid; + + Auth({this.authStatus, this.authErrorCode, this.authErrorMsg}); +} diff --git a/lib/fcs/common/domain/entities/auth_status.dart b/lib/fcs/common/domain/entities/auth_status.dart new file mode 100644 index 0000000..540df1e --- /dev/null +++ b/lib/fcs/common/domain/entities/auth_status.dart @@ -0,0 +1 @@ +enum AuthStatus { SMS_SENT, AUTH_VERIFIED, ERROR } diff --git a/lib/fcs/common/domain/entities/connectivity.dart b/lib/fcs/common/domain/entities/connectivity.dart new file mode 100644 index 0000000..14674af --- /dev/null +++ b/lib/fcs/common/domain/entities/connectivity.dart @@ -0,0 +1,5 @@ +class Connectivity { + get isConnected { + return true; + } +} diff --git a/lib/fcs/common/domain/entities/user.dart b/lib/fcs/common/domain/entities/user.dart new file mode 100644 index 0000000..51248d6 --- /dev/null +++ b/lib/fcs/common/domain/entities/user.dart @@ -0,0 +1,285 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +class User { + String id; + String name; + String phoneNumber; + String fcsID; + String shippingAddress; + String deliveryAddress; + String get phone => phoneNumber != null && phoneNumber.startsWith("959") + ? "0${phoneNumber.substring(2)}" + : phoneNumber; + + List claimPrivileges = []; + + final String dateofBirth; + final String gender; + final String status; + final bool disable; + bool registeredBuyer; + List privilegeIds; + String roleName; + String roleID; + bool agreeTerms; + String bizID; + String accountID; + String email; + bool isBlock; + int userLevel; + String userLevelID; + + String frontUrl; + String backUrl; + String selfieUrl; + + DateTime lastActiveTime; + String device; + + String primaryDeviceID; + String primaryDeviceName; + + String pin; + + String get getname => this.name; + String get getphonenumber => this.phoneNumber; + String get getdateofBirth => this.dateofBirth; + bool get getdisable => this.disable; + + Future setFirebaseUser(FirebaseUser firebaseUser) async { + IdTokenResult idToken = await firebaseUser.getIdToken(refresh: true); + String privileges = idToken.claims["privileges"]; + if (privileges == null || privileges == "") return; + this.claimPrivileges = privileges.split(":").toList(); + + this.accountID = idToken.claims["account_id"]; + this.bizID = idToken.claims["biz_id"]; + } + + User( + {this.id, + this.name, + this.gender, + this.phoneNumber, + this.fcsID, + this.shippingAddress, + this.deliveryAddress, + this.dateofBirth, + this.roleName, + this.roleID, + this.privilegeIds, + this.email, + this.disable, + this.status, + this.frontUrl, + this.backUrl, + this.selfieUrl, + this.registeredBuyer, + this.agreeTerms, + this.lastActiveTime, + this.device, + this.primaryDeviceID, + this.primaryDeviceName, + this.isBlock, + this.userLevel, + this.userLevelID, + this.pin}); + + factory User.fromJson(Map json) { + return User( + id: json['id'], + name: json['user_name'], + phoneNumber: json['phone_number'], + dateofBirth: json['dob'], + gender: json['gender'], + frontUrl: json['front_url'], + backUrl: json['back_url'], + selfieUrl: json['selfie_url'], + status: json['status'], + agreeTerms: json['agree_terms'], + disable: json['disable'], + registeredBuyer: json['registered_buyer'], + privilegeIds: json['privileges'], + email: json['email'], + isBlock: json['black_list'], + userLevel: json['user_level'], + userLevelID: json['user_level_id'], + pin: json['pin']); + } + + factory User.fromUserJson(Map json) { + DateTime parsedDate = DateTime.parse(json['last_active_time']); + + return User( + id: json['id'], + name: json['user_name'], + phoneNumber: json['phone_number'], + dateofBirth: json['dob'], + roleName: json['role_name'], + roleID: json['role_id'], + disable: json['disable'], + gender: json['gender'], + status: json['status'], + lastActiveTime: parsedDate == null ? null : parsedDate, + device: json['last_active_device'], + email: json['email'], + primaryDeviceID: json['primary_device_id'], + primaryDeviceName: json['primary_device_name'], + userLevel: json['user_level'], + userLevelID: json['user_level_id'], + pin: json['pin']); + } + + Map toJson() => { + 'id': id, + 'user_name': name, + 'gender': gender, + 'phone_number': phoneNumber, + 'dob': dateofBirth, + 'roleName': roleName, + 'roleId': roleID, + 'disable': disable, + 'status': status, + 'registered_buyer': registeredBuyer, + 'agree_terms': agreeTerms, + 'front_url': frontUrl, + 'back_url': backUrl, + 'selfie_url': selfieUrl, + 'email': email, + 'black_list': isBlock, + 'user_level': userLevel, + 'user_level_id': userLevelID, + 'pin': pin, + 'privileges': privilegeIds, + }; + + Map toMap() { + return { + 'user_name': name, + 'phone_number': phoneNumber, + 'dob': dateofBirth, + 'role_name': roleName, + 'role_id': roleID, + 'disable': disable, + 'gender': gender, + 'status': status, + 'email': email, + 'black_list': isBlock, + 'user_level': userLevel, + 'user_level_id': userLevelID, + 'pin': pin + }; + } + + factory User.fromMap(Map map, String docID) { + var activeTime = (map['last_active_time'] as Timestamp); + return User( + id: docID, + name: map['user_name'], + phoneNumber: map['phone_number'], + privilegeIds: + map['privileges'] == null ? [] : map['privileges'].cast(), + dateofBirth: map['dob'], + roleName: map['role_name'], + roleID: map['role_id'], + disable: map['disable'], + gender: map['gender'], + status: map['status'], + registeredBuyer: map['registered_buyer'], + agreeTerms: map['agree_terms'] == null ? false : map['agree_terms'], + lastActiveTime: activeTime == null ? null : activeTime.toDate(), + device: map['last_active_device'], + email: map['email'], + primaryDeviceID: map['primary_device_id'], + primaryDeviceName: map['primary_device_name'], + isBlock: map['black_list'], + userLevel: map['user_level'], + userLevelID: map['user_level_id'], + pin: map['pin']); + } + + bool isBlockUser() { + return this.isBlock == true; + } + + bool isPrimaryDevice() { + return this.primaryDeviceID != null && this.primaryDeviceID != ''; + } + + bool isRegisteredBuyer() { + return this.registeredBuyer != null && this.registeredBuyer; + } + + bool isSysAdmin() { + return claimPrivileges != null + ? claimPrivileges.contains('sys_admin') + : false; + } + + bool isSysSupport() { + return claimPrivileges != null + ? claimPrivileges.contains('sys_support') + : false; + } + + bool isBizAdmin() { + return claimPrivileges != null ? claimPrivileges.contains('ba') : false; + } + + bool isBuyer() { + return claimPrivileges == null || claimPrivileges.length == 0; + } + + bool isEmail() { + return email != null; + } + + bool hasAccount() { + return isOwner() || + (claimPrivileges != null ? claimPrivileges.contains('a') : false); + } + + bool hasDelivery() { + return isOwner() || + (claimPrivileges != null ? claimPrivileges.contains('d') : false); + } + + bool hasBuyer() { + return isOwner() || + (claimPrivileges != null ? claimPrivileges.contains('b') : false); + } + + bool isOwner() { + return claimPrivileges != null ? claimPrivileges.contains('o') : false; + } + + bool isOwnerAndAbove() { + return isOwner() || isBizAdmin() || isSysAdmin(); + } + + bool hasAdmin() { + return isOwner() || + (claimPrivileges != null ? claimPrivileges.contains('admin') : false); + } + + bool hasDO() { + return isOwner() || + (claimPrivileges != null ? claimPrivileges.contains('do') : false); + } + + bool hasPO() { + return isOwner() || + (claimPrivileges != null ? claimPrivileges.contains('po') : false); + } + + bool hasInventory() { + return isOwner() || + (claimPrivileges != null ? claimPrivileges.contains('inv') : false); + } + + @override + String toString() { + return 'User{name: $name, phoneNumber: $phoneNumber,dateofBirth:$dateofBirth,disable:$disable,gender:$gender,roleName:$roleName,roleID:$roleID,privilegeIds:$privilegeIds,status:$status,frontUrl:$frontUrl,backUrl:$backUrl,selfieUrl:$selfieUrl}'; + } +} diff --git a/lib/fcs/common/domain/exceiptions/server_exceptions.dart b/lib/fcs/common/domain/exceiptions/server_exceptions.dart new file mode 100644 index 0000000..a76ba67 --- /dev/null +++ b/lib/fcs/common/domain/exceiptions/server_exceptions.dart @@ -0,0 +1,12 @@ + +class ServerException { + @override + List get props => null; + + call() { + return null; + } + + @override + bool get stringify => null; +} diff --git a/lib/pages/code_page.dart b/lib/fcs/common/pages/signin/code_page.dart similarity index 97% rename from lib/pages/code_page.dart rename to lib/fcs/common/pages/signin/code_page.dart index e167125..f9f3dd2 100644 --- a/lib/pages/code_page.dart +++ b/lib/fcs/common/pages/signin/code_page.dart @@ -8,10 +8,10 @@ import 'package:flutter/material.dart'; import 'package:pin_input_text_field/pin_input_text_field.dart'; import 'package:provider/provider.dart'; -import '../theme/theme.dart'; -import '../widget/local_text.dart'; -import '../widget/progress.dart'; -import 'user_edit.dart'; +import '../../../../theme/theme.dart'; +import '../../../../widget/local_text.dart'; +import '../../../../widget/progress.dart'; +import '../../../../pages/user_edit.dart'; const resend_count_sec = 5; diff --git a/lib/fcs/common/pages/signin/model/signin_model.dart b/lib/fcs/common/pages/signin/model/signin_model.dart new file mode 100644 index 0000000..15d8308 --- /dev/null +++ b/lib/fcs/common/pages/signin/model/signin_model.dart @@ -0,0 +1,8 @@ +import 'package:fcs/fcs/common/data/providers/auth_fb.dart'; +import 'package:flutter/foundation.dart'; + +class SigninModel extends ChangeNotifier { + setPhoneNumber(String phoneNumber) async { + await AuthFb.instance.sendSmsCodeToPhoneNumber(phoneNumber); + } +} diff --git a/lib/pages/signin_page.dart b/lib/fcs/common/pages/signin/signin_page.dart similarity index 96% rename from lib/pages/signin_page.dart rename to lib/fcs/common/pages/signin/signin_page.dart index 6b720b1..ce8f25e 100644 --- a/lib/pages/signin_page.dart +++ b/lib/fcs/common/pages/signin/signin_page.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import '../theme/theme.dart'; -import '../widget/local_text.dart'; -import '../widget/progress.dart'; +import '../../../../theme/theme.dart'; +import '../../../../widget/local_text.dart'; +import '../../../../widget/progress.dart'; import 'code_page.dart'; -import 'util.dart'; +import '../../../../pages/util.dart'; class SigninPage extends StatefulWidget { @override diff --git a/lib/fcs/common/services/auth_imp.dart b/lib/fcs/common/services/auth_imp.dart new file mode 100644 index 0000000..f510cd1 --- /dev/null +++ b/lib/fcs/common/services/auth_imp.dart @@ -0,0 +1,37 @@ +import 'package:fcs/fcs/common/data/providers/auth_fb.dart'; +import 'package:fcs/fcs/common/data/providers/user_fb_data_provider.dart'; +import 'package:fcs/fcs/common/data/providers/user_local_data_provider.dart'; +import 'package:fcs/fcs/common/domain/entities/auth.dart'; +import 'package:fcs/fcs/common/domain/entities/connectivity.dart'; +import 'package:flutter/material.dart'; + +import 'auth_interface.dart'; + +class AuthImp implements AuthInterface { + AuthImp({ + @required this.authFb, + @required this.connectivity, + @required this.userFBDataProvider, + @required this.userLocalDataProvider, + }); + + final Connectivity connectivity; + final UserFBDataProvider userFBDataProvider; + final UserLocalDataProvider userLocalDataProvider; + final AuthFb authFb; + + @override + Future sendSmsCodeToPhoneNumber(String phoneNumber) { + return authFb.sendSmsCodeToPhoneNumber(phoneNumber); + } + + @override + Future signInWithSmsCode(String smsCode) { + return authFb.signInWithPhoneNumber(smsCode); + } + + @override + Future logout() { + return authFb.logout(); + } +} diff --git a/lib/fcs/common/services/auth_interface.dart b/lib/fcs/common/services/auth_interface.dart new file mode 100644 index 0000000..8034341 --- /dev/null +++ b/lib/fcs/common/services/auth_interface.dart @@ -0,0 +1,7 @@ +import 'package:fcs/fcs/common/domain/entities/auth.dart'; + +abstract class AuthInterface { + Future sendSmsCodeToPhoneNumber(String phoneNumber); + Future signInWithSmsCode(String smsCode); + Future logout(); +} diff --git a/lib/fcs/common/services/user_imp.dart b/lib/fcs/common/services/user_imp.dart new file mode 100644 index 0000000..c863333 --- /dev/null +++ b/lib/fcs/common/services/user_imp.dart @@ -0,0 +1,33 @@ +import 'package:fcs/fcs/common/domain/entities/connectivity.dart'; +import 'package:fcs/fcs/common/domain/entities/user.dart'; +import 'package:fcs/fcs/common/domain/exceiptions/server_exceptions.dart'; +import 'package:flutter/material.dart'; + +import 'user_interface.dart'; + +class UserImp implements UserInterface { + UserImp({ + @required this.connectivity, + }); + + final Connectivity connectivity; + + @override + Future getUser(String id) async { + if (connectivity.isConnected) { + try { + final User user = User(); + // await userFBDataProvider.getUser(id); + // cache product + // productLocalDataProvider.cacheProduct(product); + return user; + } catch (e) { + print(e); + return ServerException()(); + } + } else { + return Future.value(User()); + // return userLocalDataProvider.getUser(id); + } + } +} diff --git a/lib/fcs/common/services/user_interface.dart b/lib/fcs/common/services/user_interface.dart new file mode 100644 index 0000000..31d48f4 --- /dev/null +++ b/lib/fcs/common/services/user_interface.dart @@ -0,0 +1,5 @@ +import 'package:fcs/fcs/common/domain/entities/user.dart'; + +abstract class UserInterface { + Future getUser(String id); +} diff --git a/lib/main-local.dart b/lib/main-local.dart new file mode 100644 index 0000000..6d48ad8 --- /dev/null +++ b/lib/main-local.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:fcs/config.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'app.dart'; + +void main() { + Config( + flavor: Flavor.DEV, + color: Colors.blue, + apiURL: "https://localhost:7777", + reportURL: "http://petrok-dev.mokkon.com:8080", + reportProjectID: "dev", + level: Level.ALL); + runApp(App()); +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 4b02747..6a421d0 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -58,7 +58,7 @@ import 'my_registeration.dart'; import 'pd/pd_list.dart'; import 'products_list.dart'; import 'profile_page.dart'; -import 'signin_page.dart'; +import '../fcs/common/pages/signin/signin_page.dart'; import 'staff_list.dart'; import 'fcs_profile_page.dart'; diff --git a/lib/pages/home_page_welcome.dart b/lib/pages/home_page_welcome.dart index 6f37b24..74e232b 100644 --- a/lib/pages/home_page_welcome.dart +++ b/lib/pages/home_page_welcome.dart @@ -15,7 +15,7 @@ import 'package:provider/provider.dart'; import '../theme/theme.dart'; import 'profile_page.dart'; -import 'signin_page.dart'; +import '../fcs/common/pages/signin/signin_page.dart'; import 'term.dart'; final msgLog = Logger('backgroundMessageHandler'); diff --git a/pubspec.yaml b/pubspec.yaml index 06777d8..44856c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: fcs -description: A new Flutter project. +description: FCS Logistics publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1