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, \nNY 11367 \nTEL. +1 (929) 215-2247', deliveryAddress: '39 42th St. Kyaut Ta Thar Township Yangon'); User recipient = User( name: "Ko Myo Min", phoneNumber: '+95 9 444444444', shippingAddress: '154-19 64th Ave.Flushing, \nNY 11367', deliveryAddress: '39 42th St. Kyaut Ta Thar Township Yangon'); Setting setting = Setting( terms: '[{"insert":"Minimum shipping weight is 1lbs.\nOversized goods, Light weight/Large volume items, laptops, phones, tablets may incur extra charges based on pecifications.Please contact us for pricing.\nGoods with lithium battary needs extra packaging and declaration. Please inform us ahead of time so that we can process your package accordingly.\nLoose Batteries, Drones, and Prescription medicines are not allowed on aircraft.\nPayment: We accept money orders, any US bank transfers via Zelle, AYA, KBZ and CB. No COD except for pick-ups.\nPayments made in Myanmar will incur 2% tranfer fee\n"}]'); 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(); // }); _loadFcs(); } _loadFcs() async { user = await SharedPref.getUser(); notifyListeners(); } saveUser(String pin, String phone) { if (pin == "000000") { user = User(name: "Owner", phoneNumber: phone); SharedPref.saveUser(user); } else { user = User(name: "Customer", phoneNumber: phone); SharedPref.saveUser(user); } notifyListeners(); } resetPinTimer() { if (pinTimer != null && pinTimer.isActive) { pinTimer.cancel(); } pinRequired = false; pinTimer = Timer(Duration(minutes: PIN_TIME_MIN), () { pinRequired = true; }); } bool isLogin() { return this.user != null; } 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); } 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 { this.user = null; notifyListeners(); return; 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()); } }