import 'dart:async'; import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:device_info/device_info.dart'; import 'package:dio/dio.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:package_info/package_info.dart'; import 'package:path/path.dart' as Path; import 'package:fcs/model/shared_pref.dart'; import 'package:fcs/util.dart'; import 'package:fcs/vo/bank_account.dart'; import 'package:fcs/vo/buyer.dart'; import 'package:fcs/vo/setting.dart'; import 'package:fcs/widget/NetworkConnectivity.dart'; import '../config.dart'; import '../vo/status.dart'; import '../vo/user.dart'; import 'api_helper.dart'; import 'base_model.dart'; import 'constants.dart'; import 'firebase_helper.dart'; class ImplementInterfaceModel { void initUser(User user) => {}; void initSetting(Setting setting) => {}; } class MainModel extends ChangeNotifier { final log = Logger('MainModel'); final FirebaseAuth auth = FirebaseAuth.instance; FirebaseMessaging firebaseMessaging; List models = []; User user; Buyer buyer; FirebaseUser firebaseUser; StreamSubscription userListener; StreamSubscription buyerListener; bool pinRequired; Timer pinTimer; User customer = User( name: "Ko Myo Min", phoneNumber: '+95 9 444444444', fcsID: 'FCS-0203-390-2', shippingAddress: '154-19 64th Ave.Flushing, NY 11367 TEL. +1 (929) 215-2247', deliveryAddress: '39 42th St. Kyaut Ta Thar Township Yangon'); Setting setting; PackageInfo packageInfo; bool isLoaded = true; bool isOnline = true; static const PIN_TIME_MIN = 10; MainModel() { // NetworkConnectivity.instance.statusStream.listen((data) { // bool _isOnline = data["isOnline"]; // if (_isOnline && !this.isOnline) { // init(); // } // this.isOnline = _isOnline; // notifyListeners(); // }); } resetPinTimer() { if (pinTimer != null && pinTimer.isActive) { pinTimer.cancel(); } pinRequired = false; pinTimer = Timer(Duration(minutes: PIN_TIME_MIN), () { pinRequired = true; }); } bool isLogin() { return true; } bool hasEmail() { return this.user != null && this.user.isEmail(); } bool agreedTerm() { return this.user != null && this.user.agreeTerms; } bool isBuyer() { return this.user == null || this.user.isBuyer(); } bool isRegBuyer() { return isBuyer() && buyer != null; } bool isApprovedBuyer() { return isBuyer() && buyer != null && buyer.isApproved(); } bool isSysAdmin() { return this.user != null && this.user.isSysAdmin(); } bool isSysSupport() { return this.user != null && this.user.isSysSupport(); } bool isBizAdmin() { return this.user != null && this.user.isBizAdmin(); } bool isOwnerAndAbove() { return this.user != null && this.user.isOwnerAndAbove(); } bool isAdmin() { return this.user != null && this.user.hasAdmin(); } bool showHistoryBtn() { return isSysAdmin() || isSysSupport() || isBizAdmin(); } init() async { // await _loadSetting(); // _loadUser(); // resetPinTimer(); } void addModel(BaseModel model) { models.add(model); model.mainModel = this; } void _initUser(User user) { models.forEach((m) => m.initUser(user)); if (firebaseMessaging != null) { firebaseMessaging.subscribeToTopic(user.docID); } } void _initSetting(Setting setting) { models.forEach((m) => m.initSetting(setting)); } Future _loadSetting() async { this.setting = await _getSetting(); this.packageInfo = await PackageInfo.fromPlatform(); _initSetting(setting); } void _loadUser() async { this.firebaseUser = await auth.currentUser(); if (this.firebaseUser == null) { this.isLoaded = true; notifyListeners(); return; } _logUser(this.firebaseUser); // load from local, if successful,notify listeners User _user = await SharedPref.getUser(); if (_user != null) { await _user.setFirebaseUser(this.firebaseUser); _initUser(_user); this.user = _user; if (this.user.isRegisteredBuyer()) { _loadBuyer(); } this.isLoaded = true; notifyListeners(); log.info("user loaded from shared pref!"); } _listenUser(); } void _listenUser() { if (this.userListener != null) { this.userListener.cancel(); } this.userListener = getDocSnapshot( "/$biz_collection/${setting.okEnergyId}/$user_collection", firebaseUser.uid) .listen((userSnap) async { if (userSnap.exists) { User _user = User.fromMap(userSnap.data, userSnap.documentID); // load claims try { FirebaseUser _firebaseUser = await getProfile(this.firebaseUser); await _user.setFirebaseUser(_firebaseUser); _initUser(_user); this.user = _user; this.firebaseUser = _firebaseUser; await SharedPref.saveUser(this.user); } catch (e) { log.warning(e.toString()); } log.info( "_loadUser => ID: ${this.user.docID}, AccountID: ${this.user.accountID}," "BizID: ${this.user.accountID}," ", Privileges: ${this.user.claimPrivileges}, isSysAdmin: ${this.user.isSysAdmin()}"); if (this.user.isRegisteredBuyer()) { _loadBuyer(); } this.isLoaded = true; notifyListeners(); } }); } void _loadBuyer() async { if (this.user == null) return; if (buyerListener != null) buyerListener.cancel(); buyerListener = getDocSnapshot( "/$biz_collection/${setting.okEnergyId}/$buyer_collection", this.user.docID) .listen((buyerSnap) async { if (buyerSnap.exists) { this.buyer = Buyer.fromMap(buyerSnap.data, buyerSnap.documentID); } else { this.buyer = null; } notifyListeners(); }); } @override void dispose() { // super.dispose(); // if (this.userListener != null) { // this.userListener.cancel(); // } // SharedPref.removeUser(); // this.user = User(); } Future login(String phoneNumber, String pass) async { var id = phoneNumber.replaceFirst("+", ""); id = updatePhoneNumber(id); var data = {"id": id, "password": pass}; var result = await requestAPI("/login", "POST", payload: data); var token = result["Token"]; // login with custom token AuthResult r = await this.auth.signInWithCustomToken(token: token); this.firebaseUser = r.user; isLoaded = false; _loadUser(); _logUser(this.firebaseUser); } Future getProfile(FirebaseUser firebaseUser) async { IdTokenResult idtoken = await firebaseUser.getIdToken(); var data = await requestAPI( "/profile", "GET", token: idtoken.token, ); var _token = data["Token"]; AuthResult a = await this.auth.signInWithCustomToken(token: _token); return a.user; } Future _logUser(FirebaseUser firebaseUser) async { IdTokenResult idtoken = await firebaseUser.getIdToken(); await requestAPI( "/log", "GET", token: idtoken.token, ); } Future logout() async { if (this.userListener != null) { await this.userListener.cancel(); } await auth.signOut(); this.user = null; this.buyer = null; this.firebaseUser = null; await SharedPref.removeUser(); if (firebaseMessaging != null) { firebaseMessaging.unsubscribeFromTopic(user.docID); } // logout models models.forEach((m) => m.logout()); notifyListeners(); } Future signup( String name, password, confirmPassword, phoneNumber) async { if (password == "" || password.length < 6) { throw Exception("Password must be at least 6 characters"); } if (password != confirmPassword) { throw Exception("Password mismatch"); } var id = phoneNumber.replaceFirst("+", ""); id = updatePhoneNumber(id); var inputData = {"id": id, "password": password, "user_name": name}; DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; String deviceName = "${androidInfo.model}(${androidInfo.id})"; var url = "${Config.instance.apiURL}/signup"; Response response = await Dio().post(url, data: inputData, options: Options( headers: {"Device": androidInfo.androidId + ":" + deviceName})); var data = Status.fromJson(response.data); if (data.status != 'Ok') { throw Exception("${data.errorCode} : ${data.message}"); } } Future confirmSignup( String phoneNumber, password, confirmSMSCode) async { var id = phoneNumber.replaceFirst("+", ""); id = updatePhoneNumber(id); if (confirmSMSCode == "" || confirmSMSCode.length != 6) { throw Exception("Password must be 6 characters"); } var inputData = { "id": id, "password": password, "confirmation_code": confirmSMSCode }; var url = "${Config.instance.apiURL}/confirm"; Response response = await Dio().post( url, data: inputData, ); var data = Status.fromJson(response.data); if (data.status != 'Ok') { throw Exception(data.message); } } bool isSupport() { if (packageInfo == null || setting == null) return false; return int.parse(packageInfo.buildNumber) >= setting.supportBuildNum; } Future _getSetting() async { var snap = await Firestore.instance .collection(config_collection) .document(setting_doc_id) .get(); if (!snap.exists) { return null; } _listSetting(); return Setting.fromMap(snap.data); } void _listSetting() { getDocSnapshot("/configs", setting_doc_id).listen((snap) { this.setting = Setting.fromMap(snap.data); notifyListeners(); }); } Future updateProfile(String name) async { await requestAPI("/user", "PUT", payload: {"user_name": name}, token: await getToken()); } Future updateTerms(String terms) async { await requestAPI("/terms", "PUT", payload: {"terms": terms}, token: await getToken()); } Future agreeTerms() async { await requestAPI("/user/agree", "PUT", token: await getToken()); } Future updateContact(Setting setting) async { await requestAPI("/contact", "PUT", payload: { 'email': setting.email, 'facebook_url': setting.facebook, 'web_url': setting.website, 'phones': setting.phones, 'bank_account_info': setting.bankAccountInfo, 'delivery_phone': setting.deliveryPhone, 'address': setting.address, }, token: await getToken()); } Future updateSetting(Setting setting) async { await requestAPI("/setting", "PUT", payload: { 'do_expire_hours': setting.doExpireInHours, 'po_expire_hours': setting.poExpireInHours, 'po_open_at': setting.poOpenAt, 'po_close_at': setting.poCloseAt, 'po_close_on': setting.poCloseOn, 'first_storage_charge_in': setting.firstStorageChargeIn, 'first_storage_charge': setting.firstStorageCharge, 'second_storage_charge_in': setting.secondStorageChargeIn, 'second_storage_charge': setting.secondStorageCharge, 'latest_delivery_days': setting.latestDeliveryDay, }, token: await getToken()); } Future addBankAccount(BankAccount bankAccount, File image) async { String url = await uploadStorage(bank_images_path, image); bankAccount.bankLogo = url; await requestAPI("/bank_accounts", "POST", payload: bankAccount.toMap(), token: await getToken()); } Future updateBankAccount(BankAccount bankAccount, File image) async { if (image != null) { String url = await uploadStorage(bank_images_path, image); bankAccount.bankLogo = url; } await requestAPI("/bank_accounts", "PUT", payload: bankAccount.toMap(), token: await getToken()); } Future deleteBankAccount(BankAccount bankAccount) async { await requestAPI("/bank_accounts", "DELETE", payload: bankAccount.toMap(), token: await getToken()); } }