diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Guest.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Guest.kt index ce374496..50968a4c 100644 --- a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Guest.kt +++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Guest.kt @@ -1,22 +1,23 @@ package com.ejada.hmg.hmgwifi +import android.annotation.SuppressLint import android.content.Context import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest import android.net.wifi.WifiConfiguration -import android.net.wifi.WifiInfo import android.net.wifi.WifiManager +import android.net.wifi.WifiNetworkSpecifier import android.os.Build import android.util.Log -import android.widget.Toast +import androidx.annotation.RequiresApi import com.ejada.hmg.MainActivity -import com.ejada.hmg.utils.FlutterText import com.ejada.hmg.utils.HMGUtils -class HMG_Guest(context: MainActivity) { +class HMG_Guest(private var context: MainActivity) { private var wifiManager: WifiManager? = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager? - private var connectivityManager: ConnectivityManager? = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? - private var context = context private val TAG = "HMG_Guest" private val TEST = false @@ -40,114 +41,69 @@ class HMG_Guest(context: MainActivity) { wm.isWifiEnabled = true HMGUtils.popFlutterText(context,"enablingWifi"); HMGUtils.timer(2000,false){ - connect() + connectWifi() } - }else{ - connect() + connectWifi() } } } + private fun errorConnecting(){ + completionOnUiThread(false, "errorConnectingHmgNetwork") + } - private fun connect(){ - val security = "OPEN" - val networkPass = "" - Log.d(TAG, "Connecting to SSID \"$SSID\" with password \"$networkPass\" and with security \"$security\" ...") - - // You need to create WifiConfiguration instance like this: - val conf = WifiConfiguration() - conf.SSID = SSID - conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) - conf.networkId = ssidToNetworkId(SSID) - - val wm = wifiManager!! - - if (conf.networkId == -1) { - wm.addNetwork(conf) - } else { - Log.v(TAG, "WiFi found - updating it.\n") - wm.updateNetwork(conf) + fun connectWifi(){ + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + wifiManager?.let { connectApiGreaterThen28(it) } + }else { + connectApiLessThen29() } + } - conf.networkId = ssidToNetworkId(SSID) - Log.d(TAG, "Network ID: ${conf.networkId}") - - val networkIdToConnect = conf.networkId - if (networkIdToConnect >= 0) { - Log.v(TAG, "Start connecting to $SSID Wifi...") - - // We disable the network before connecting, because if this was the last connection before - // a disconnect(), this will not reconnect. - wm.disableNetwork(networkIdToConnect) - val result = wm.enableNetwork(networkIdToConnect, true) - if(result){ - HMGUtils.timer(8000,false){ - if(wm.getConnectionInfo().getSSID() == SSID){ - completionOnUiThread(true, "successConnectingHmgNetwork") - - }else{ - errorConnecting() - } - } + // I }else{f CompileSDK is greater and equals to APILevel 29 + @RequiresApi(Build.VERSION_CODES.Q) + private fun connectApiGreaterThen28(wm:WifiManager){ + Log.e(TAG, "connection wifi with Android Q+") + val wifiNetworkSpecifier: WifiNetworkSpecifier = WifiNetworkSpecifier.Builder() +// .setWpa2Passphrase(password) + .build() + + val networkRequest: NetworkRequest = NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setNetworkSpecifier(wifiNetworkSpecifier) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) //removeCapability added for hotspots without internet + .build() + + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + connectivityManager.bindProcessToNetwork(network) + Log.e(TAG, "onAvailable") + } - }else{ - errorConnecting() + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + Log.e(TAG, "onLosing") } + override fun onLost(network: Network) { + super.onLost(network) + Log.e(TAG, "onLosing") + Log.e(TAG, "losing active connection") + } + override fun onUnavailable() { + super.onUnavailable() + Log.e(TAG, "onUnavailable") + } - }else{ - Log.v(TAG, "Cannot connect to $SSID network") - errorConnecting() } - } - - private fun errorConnecting(){ - completionOnUiThread(false, "errorConnectingHmgNetwork") - } - // If CompileSDK is greater and equals to APILevel 29 - private fun connectNewer(wm:WifiManager){ - -// Log.e(TAG, "connection wifi Q") -// -// val wifiNetworkSpecifier: WifiNetworkSpecifier = WifiNetworkSpecifier.Builder() -// .setSsid(ssid) -// .setWpa2Passphrase(password) -// .build() -// -// val networkRequest: NetworkRequest = NetworkRequest.Builder() -// .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) -// .setNetworkSpecifier(wifiNetworkSpecifier) -// .build() -// -// var connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager -// var networkCallback = object : ConnectivityManager.NetworkCallback() { -// override fun onAvailable(network: Network) { -// super.onAvailable(network) -// connectivityManager.bindProcessToNetwork(network) -// Log.e(TAG, "onAvailable") -// } -// -// override fun onLosing(network: Network, maxMsToLive: Int) { -// super.onLosing(network, maxMsToLive) -// Log.e(TAG, "onLosing") -// } -// -// override fun onLost(network: Network) { -// super.onLost(network) -// Log.e(TAG, "onLosing") -// Log.e(TAG, "losing active connection") -// } -// -// override fun onUnavailable() { -// super.onUnavailable() -// Log.e(TAG, "onUnavailable") -// } -// } -// connectivityManager.requestNetwork(networkRequest, networkCallback) + //timeout add because "No devices found" wasn't handled correct and doesn't throw Unavailable + connectivityManager.requestNetwork(networkRequest, networkCallback, 30000) } @@ -156,6 +112,7 @@ class HMG_Guest(context: MainActivity) { * networks, and returns the networkId for the network if the SSID matches. If not, * it returns -1. */ + @SuppressLint("MissingPermission") private fun ssidToNetworkId(ssid: String): Int { val currentNetworks = wifiManager!!.configuredNetworks var networkId = -1 @@ -169,4 +126,130 @@ class HMG_Guest(context: MainActivity) { } return networkId } -} \ No newline at end of file + + + fun connectApiLessThen29(){ + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){ + + // Initialize the WifiConfiguration object + val security = "OPEN" + val networkPass = "" + Log.d(TAG, "Connecting to SSID \"$SSID\" with password \"$networkPass\" and with security \"$security\" ...") + + // You need to create WifiConfiguration instance like this: + val conf = WifiConfiguration() + conf.SSID = SSID + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) + conf.networkId = ssidToNetworkId(SSID) + + val wm = wifiManager!! + + if (conf.networkId == -1) { + wm.addNetwork(conf) + } else { + Log.v(TAG, "WiFi found - updating it.\n") + wm.updateNetwork(conf) + } + + conf.networkId = ssidToNetworkId(SSID) + Log.d(TAG, "Network ID: ${conf.networkId}") + + val networkIdToConnect = conf.networkId + if (networkIdToConnect >= 0) { + Log.v(TAG, "Start connecting to $SSID Wifi...") + + // We disable the network before connecting, because if this was the last connection before + // a disconnect(), this will not reconnect. + wm.disableNetwork(networkIdToConnect) + val result = wm.enableNetwork(networkIdToConnect, true) + if(result){ + HMGUtils.timer(8000,false){ + if(wm.getConnectionInfo().getSSID() == SSID){ + completionOnUiThread(true, "successConnectingHmgNetwork") + + }else{ + errorConnecting() + } + } + + }else{ + errorConnecting() + } + + + + }else{ + Log.v(TAG, "Cannot connect to $SSID network") + errorConnecting() + } + } + + /*val wifi = WifiConfiguration(); + wifi.hiddenSSID = this.hiddenSSID; + wifi.SSID = newSSID; + wifi.preSharedKey = newPass; + wifi.status = WifiConfiguration.Status.ENABLED; + wifi.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); + wifi.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + wifi.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); + wifi.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + wifi.allowedProtocols.set(WifiConfiguration.Protocol.RSN); + wifi.allowedProtocols.set(WifiConfiguration.Protocol.WPA); + + wifi.networkId = ssidToNetworkId(newSSID); + + // Set network to highest priority (deprecated in API >= 26) + if(Build.VERSION.SDK_INT < 26) { + wifi.priority = getMaxWifiPriority(wifiManager) + 1; + } + + // After processing authentication types, add or update network + if(wifi.networkId == -1) { // -1 means SSID configuration does not exist yet + + int newNetId = wifiManager.addNetwork(wifi); + if( newNetId > -1 ){ + callbackContext.success( newNetId ); + } else { + callbackContext.error( "ERROR_ADDING_NETWORK" ); + } + + } else { + + int updatedNetID = wifiManager.updateNetwork(wifi); + + if(updatedNetID == -1) + updatedNetID = wifiManager.addNetwork(wifi); + + if(updatedNetID > -1) { + callbackContext.success( updatedNetID ); + } else { + callbackContext.error("ERROR_UPDATING_NETWORK"); + } + + } + + // WifiManager configurations are presistent for API 26+ + if(API_VERSION < 26) { + wifiManager.saveConfiguration(); // Call saveConfiguration for older < 26 API + }*/ + } + + companion object{ + + /** + * Figure out what the highest priority network in the network list is and return that priority + */ + @RequiresApi(Build.VERSION_CODES.S) + fun getMaxWifiPriority(wifiManager:WifiManager) : Int { + val configurations = wifiManager.callerConfiguredNetworks + var maxPriority = 0 + configurations.forEach { + if (it.priority > maxPriority) { + maxPriority = it.priority; + } + } + return maxPriority; + } + } +} diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Internet.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Internet.kt index 2eedef3a..c55040b3 100644 --- a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Internet.kt +++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/HMG_Internet.kt @@ -3,7 +3,6 @@ package com.ejada.hmg.hmgwifi import android.annotation.SuppressLint import com.ejada.hmg.utils.API import com.ejada.hmg.MainActivity -import com.ejada.hmg.utils.FlutterText import com.github.kittinunf.fuel.core.extensions.jsonBody import com.github.kittinunf.fuel.httpGet import com.github.kittinunf.fuel.httpPost @@ -14,7 +13,7 @@ import java.util.* @SuppressLint("MissingPermission") class HMG_Internet(flutterMainActivity: MainActivity) { private val TAG = "HMG_Wifi" - private val TEST = false + private val TEST = true private var context = flutterMainActivity; @@ -41,7 +40,7 @@ class HMG_Internet(flutterMainActivity: MainActivity) { fun connectToHMGGuestNetwork(patientId: String, completion: (status: Boolean, message: String) -> Unit): HMG_Internet { completionListener = completion getWifiCredentials(patientId) { - WPA(context,SSID).connect(USER_NAME,PASSWORD) { status, message -> + WpaEnterprise(context,SSID).connect(USER_NAME,PASSWORD) { status, message -> completionOnUiThread(status,message) } } diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/WpaEnterprise.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/WpaEnterprise.kt new file mode 100644 index 00000000..23871fd6 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/hmgwifi/WpaEnterprise.kt @@ -0,0 +1,160 @@ +package com.ejada.hmg.hmgwifi + +import android.annotation.SuppressLint +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.net.wifi.* +import android.net.wifi.SupplicantState.ASSOCIATED +import android.net.wifi.SupplicantState.COMPLETED +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.ejada.hmg.MainActivity +import com.ejada.hmg.utils.HMGUtils + +class WpaEnterprise(private val mainActivity: MainActivity, private var SSID: String) { + private var TAG = "WpaEnterprise" + + private lateinit var identity:String + private lateinit var password:String + private lateinit var completion:((status: Boolean, message: String) -> Unit) + + fun connect(identity:String, password:String, completion: (status: Boolean, message: String) -> Unit) { + this.password = password + this.identity = identity + this.completion = completion + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + apiGreaterThen28() + }else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){ + apiLessThen29() + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun apiLessThen29(){ + val wifiManager = mainActivity.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + + val wifi = WifiConfiguration() + wifi.SSID = """"$SSID"""" + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP) + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X) + wifi.enterpriseConfig = enterpriseConfig() + wifi.networkId = ssidToNetworkId(wifi.SSID, wifiManager) + if (wifi.networkId == -1) { + wifiManager.addNetwork(wifi) + } else { + Log.v(TAG, "WiFi found - updating it.\n") + wifiManager.updateNetwork(wifi) + } + Log.v(TAG, "saving config.\n") + wifiManager.saveConfiguration() + wifi.networkId = ssidToNetworkId(wifi.SSID, wifiManager) + + Log.v(TAG, "wifi ID in device = " + wifi.networkId) + + var supState: SupplicantState + val networkIdToConnect = wifi.networkId + if (networkIdToConnect >= 0) { + Log.v(TAG, "Start connecting...\n") + + // We disable the network before connecting, because if this was the last connection before + // a disconnect(), this will not reconnect. + wifiManager.disableNetwork(networkIdToConnect) + wifiManager.enableNetwork(networkIdToConnect, true) + + val wifiInfo: WifiInfo = wifiManager.connectionInfo + + HMGUtils.timer(5000,false){ + supState = wifiInfo.supplicantState + Log.i(TAG, "Done connect to network : status = $supState") + val successStates = listOf(COMPLETED, ASSOCIATED) + if (successStates.contains(supState)) + completion(true,"Connected to internet Wifi") + else + completion(false,"errorConnectingHmgNetwork") + } + + } else { + Log.v(TAG, "WifiWizard: cannot connect to network") + completion(false,"errorConnectingHmgNetwork") + } + } + + /** + * This method takes a given String, searches the current list of configured WiFi + * networks, and returns the networkId for the network if the SSID matches. If not, + * it returns -1. + */ + @SuppressLint("MissingPermission") + private fun ssidToNetworkId(ssid: String, wifiManager: WifiManager): Int { + val currentNetworks = wifiManager.configuredNetworks + var networkId = -1 + // For each network in the list, compare the SSID with the given one + for (test in currentNetworks) { + if (test.SSID == ssid) { + networkId = test.networkId + break + } + } + return networkId + } + + + @RequiresApi(Build.VERSION_CODES.Q) + fun apiGreaterThen28(){ + val connectivityManager = mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + Log.e(TAG, "connection wifi with Android Q+") + val wifiNetworkSpecifier: WifiNetworkSpecifier = WifiNetworkSpecifier.Builder() + .setWpa2EnterpriseConfig(enterpriseConfig()) + .build() + + val networkRequest: NetworkRequest = NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setNetworkSpecifier(wifiNetworkSpecifier) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) //removeCapability added for hotspots without internet + .build() + + val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + connectivityManager.bindProcessToNetwork(network) + Log.e(TAG, "onAvailable") + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + Log.e(TAG, "onLosing") + } + + override fun onLost(network: Network) { + super.onLost(network) + Log.e(TAG, "onLosing") + Log.e(TAG, "losing active connection") + } + + override fun onUnavailable() { + super.onUnavailable() + Log.e(TAG, "onUnavailable") + } + + } + + //timeout add because "No devices found" wasn't handled correct and doesn't throw Unavailable + connectivityManager.requestNetwork(networkRequest, networkCallback, 30000) + } + + fun enterpriseConfig() : WifiEnterpriseConfig{ + // Initialize the WifiConfiguration object + val enterpriseConfig = WifiEnterpriseConfig() + enterpriseConfig.eapMethod = WifiEnterpriseConfig.Eap.PEAP + enterpriseConfig.identity = identity + enterpriseConfig.password = password + return enterpriseConfig; + } + +} \ No newline at end of file diff --git a/assets/images/new/hmg_icon.svg b/assets/images/new/hmg_icon.svg index e777d9ee..e09967e2 100644 --- a/assets/images/new/hmg_icon.svg +++ b/assets/images/new/hmg_icon.svg @@ -1,3 +1,6 @@ + + + diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9bf7caa5..14e77cd0 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -14,7 +14,6 @@ 306FE6CB271D8B73002D6EFC /* OpenTok.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306FE6CA271D8B73002D6EFC /* OpenTok.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 813F9CBA7DD5ED63B28B8BB6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 767C165F7ABF3BF1F829B9BF /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -36,6 +35,7 @@ E9C8C136256BACDA00EFFB62 /* HMG_Guest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C8C135256BACDA00EFFB62 /* HMG_Guest.swift */; }; E9E27168256E3A4000F49B69 /* LocalizedFromFlutter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E27167256E3A4000F49B69 /* LocalizedFromFlutter.swift */; }; E9F7623B25922BCE00FB5CCF /* FlutterConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F7623A25922BCE00FB5CCF /* FlutterConstants.swift */; }; + FA0839686861F9C0546E6F45 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C703B0BB1CDBBCA30693DBD3 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -58,13 +58,12 @@ 301C79AF27200DED0016307B /* OpenTokLocalVideoFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenTokLocalVideoFactory.swift; sourceTree = ""; }; 306FE6C7271D790C002D6EFC /* OpenTokPlatformBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenTokPlatformBridge.swift; sourceTree = ""; }; 306FE6CA271D8B73002D6EFC /* OpenTok.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenTok.swift; sourceTree = ""; }; - 308FEC658188F7D588BE7580 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 44DB2098C8C4B08C2F3B1329 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 767C165F7ABF3BF1F829B9BF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 969F0CEC868FD7C196987A3E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 84A70B284E0DDF62ECE83D3B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -72,7 +71,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DCFFD369041FFFFA94CF7B26 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9C2760F1A81999922ACAA9E6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C703B0BB1CDBBCA30693DBD3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E91B538D256AAA6500E96549 /* GlobalHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalHelper.swift; sourceTree = ""; }; E91B538E256AAA6500E96549 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; E91B538F256AAA6500E96549 /* API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; @@ -99,8 +99,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 813F9CBA7DD5ED63B28B8BB6 /* Pods_Runner.framework in Frameworks */, E9620805255C2ED100D3A35D /* NetworkExtension.framework in Frameworks */, + FA0839686861F9C0546E6F45 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -121,7 +121,7 @@ isa = PBXGroup; children = ( E9620804255C2ED100D3A35D /* NetworkExtension.framework */, - 767C165F7ABF3BF1F829B9BF /* Pods_Runner.framework */, + C703B0BB1CDBBCA30693DBD3 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -129,9 +129,9 @@ 605039E5DDF72C245F9765FE /* Pods */ = { isa = PBXGroup; children = ( - DCFFD369041FFFFA94CF7B26 /* Pods-Runner.debug.xcconfig */, - 969F0CEC868FD7C196987A3E /* Pods-Runner.release.xcconfig */, - 308FEC658188F7D588BE7580 /* Pods-Runner.profile.xcconfig */, + 44DB2098C8C4B08C2F3B1329 /* Pods-Runner.debug.xcconfig */, + 9C2760F1A81999922ACAA9E6 /* Pods-Runner.release.xcconfig */, + 84A70B284E0DDF62ECE83D3B /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -232,15 +232,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 299B8FE131E5BAE7FA7E2FC9 /* [CP] Check Pods Manifest.lock */, + D74B98D9E413413D50043E1B /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - EFDAD5E1235DCA1DB6187148 /* [CP] Embed Pods Frameworks */, - 29B24CD65FDFD6111DD04897 /* [CP] Copy Pods Resources */, + 796644D4DF82E4D5FBECBF3B /* [CP] Embed Pods Frameworks */, + 0EF99525B5E50490BA0DF0A4 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -257,7 +257,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -304,29 +304,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 299B8FE131E5BAE7FA7E2FC9 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 29B24CD65FDFD6111DD04897 /* [CP] Copy Pods Resources */ = { + 0EF99525B5E50490BA0DF0A4 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -357,6 +335,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 796644D4DF82E4D5FBECBF3B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -371,21 +366,26 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - EFDAD5E1235DCA1DB6187148 /* [CP] Embed Pods Frameworks */ = { + D74B98D9E413413D50043E1B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -520,6 +520,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); + MARKETING_VERSION = 1; PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -659,6 +660,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); + MARKETING_VERSION = 1; PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -692,6 +694,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); + MARKETING_VERSION = 1; PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fb2dffc4..c87d15a3 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) + LSApplicationQueriesSchemes + + comgooglemaps + baidumap + iosamap + LSRequiresIPhoneOS MinimumOSVersion @@ -94,13 +100,5 @@ io.flutter.embedded_views_preview - LSApplicationQueriesSchemes - - comgooglemaps - baidumap - iosamap - - - diff --git a/lib/config/config.dart b/lib/config/config.dart index 3ed8d764..0e899c89 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -14,6 +14,7 @@ const PACKAGES_PRODUCTS = '/api/products'; const PACKAGES_CUSTOMER = '/api/customers'; const PACKAGES_SHOPPING_CART = '/api/shopping_cart_items'; const PACKAGES_ORDERS = '/api/orders'; +const PACKAGES_ORDER_HISTORY = '/api/orders/items'; const PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara'; const BASE_URL = 'https://uat.hmgwebservices.com/'; // const BASE_URL = 'https://hmgwebservices.com/'; diff --git a/lib/core/service/client/base_app_client.dart b/lib/core/service/client/base_app_client.dart index fefc9139..b339ed69 100644 --- a/lib/core/service/client/base_app_client.dart +++ b/lib/core/service/client/base_app_client.dart @@ -517,7 +517,8 @@ class BaseAppClient { } simpleGet(String fullUrl, - {Function(dynamic response, int statusCode) onSuccess, Function(String error, int statusCode) onFailure, Map queryParams, Map headers}) async { + {Function(dynamic response, int statusCode) onSuccess, Function(String error, int statusCode) onFailure, Map queryParams, Map headers}) async { + headers = headers ?? {}; String url = fullUrl; var haveParams = (queryParams != null); diff --git a/lib/core/service/packages_offers/PackagesOffersServices.dart b/lib/core/service/packages_offers/PackagesOffersServices.dart index 6c4e0d24..978164bd 100644 --- a/lib/core/service/packages_offers/PackagesOffersServices.dart +++ b/lib/core/service/packages_offers/PackagesOffersServices.dart @@ -24,14 +24,15 @@ Map packagesAuthHeader = {}; class OffersAndPackagesServices extends BaseService { AuthenticatedUser patientUser; - List categoryList = List(); - List productList = List(); - List latestOffersList = List(); - List tamaraPaymentOptions = List(); - List bestSellerList = List(); - List bannersList = List(); - List cartItemList = List(); - List _hospitals = List(); + List categoryList = []; + List productList = []; + List latestOffersList = []; + List tamaraPaymentOptions = []; + List bestSellerList = []; + List bannersList = []; + List ordersHistory = []; + List cartItemList = []; + List _hospitals = []; List get hospitals => _hospitals; String cartItemCount = ""; @@ -58,7 +59,7 @@ class OffersAndPackagesServices extends BaseService { request.sinceId = (productList.isNotEmpty) ? productList.last.id : 0; - productList = List(); + productList = []; var url = EXA_CART_API_BASE_URL + PACKAGES_PRODUCTS; await baseAppClient.simpleGet(url, headers: packagesAuthHeader, onSuccess: (dynamic stringResponse, int statusCode) { if (statusCode == 200) { @@ -349,6 +350,40 @@ class OffersAndPackagesServices extends BaseService { return errorThrow ?? order_id; } + // -------------------- + // Order History + // -------------------- + Future> orderHistory({@required BuildContext context, bool showLoading = true}) async { + // if(ordersHistory.isNotEmpty) + // return ordersHistory; + + Future errorThrow; + // https://mdlaboratories.com/offersdiscounts/api/orders/items/0535256053?fields=id,product,utilize_by_vida,valid_until_date_utc,order_id&page=1&limit=100 + Map queryParams ={}; + queryParams['fields'] = 'id,product,utilize_by_vida,valid_until_date_utc,order_id'; + queryParams['page'] = "1"; + queryParams['limit'] = "100"; + print(queryParams); + + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_ORDER_HISTORY + "/${user.mobileNumber}"; + + await baseAppClient.simpleGet(url, headers: packagesAuthHeader, queryParams: queryParams, onSuccess: (dynamic stringResponse, int statusCode) { + _hideLoading(context, showLoading); + var jsonResponse = json.decode(stringResponse); + final order_items = jsonResponse["order_items"] as List; + ordersHistory = order_items.map((e) => PackagesResponseModel().fromJson(e['product'])).toList(); + print(ordersHistory); + + }, onFailure: (String error, int statusCode) { + _hideLoading(context, showLoading); + log(error); + errorThrow = Future.error(error); + }); + + return errorThrow ?? ordersHistory; + } + Future> getOrderById(int id, {@required BuildContext context, bool showLoading = true}) async { Future errorThrow; ResponseModel response; diff --git a/lib/pages/landing/fragments/home_page_fragment2.dart b/lib/pages/landing/fragments/home_page_fragment2.dart index 75b9a473..2b8aa394 100644 --- a/lib/pages/landing/fragments/home_page_fragment2.dart +++ b/lib/pages/landing/fragments/home_page_fragment2.dart @@ -16,6 +16,7 @@ import 'package:diplomaticquarterapp/pages/landing/widgets/services_view.dart'; import 'package:diplomaticquarterapp/pages/landing/widgets/slider_view.dart'; import 'package:diplomaticquarterapp/pages/medical/medical_profile_page_new.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/packages_offers_tab_pager.dart'; import 'package:diplomaticquarterapp/theme/colors.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/uitl/utils.dart'; @@ -284,7 +285,7 @@ class _HomePageFragment2State extends State { child: InkWell( onTap: () { AuthenticatedUser user = projectViewModel.user; - Navigator.of(context).push(MaterialPageRoute(builder: (context) => PackagesHomePage(user))); + Navigator.of(context).push(MaterialPageRoute(builder: (context) => PackagesOfferTabPage(user))); }, child: Container( width: double.infinity, diff --git a/lib/pages/landing/home_page.dart b/lib/pages/landing/home_page.dart index f3af7c74..18c1e4cd 100644 --- a/lib/pages/landing/home_page.dart +++ b/lib/pages/landing/home_page.dart @@ -13,6 +13,7 @@ import 'package:diplomaticquarterapp/pages/ErService/ErOptions.dart'; import 'package:diplomaticquarterapp/pages/base/base_view.dart'; import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/packages_offers_tab_pager.dart'; import 'package:diplomaticquarterapp/pages/paymentService/payment_service.dart'; import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; @@ -420,7 +421,7 @@ class _HomePageState extends State { padding: const EdgeInsets.only(bottom: 15, right: 15, left: 15), child: InkWell( onTap: () { - Navigator.of(context).push(MaterialPageRoute(builder: (context) => PackagesHomePage(projectViewModel.user))); + Navigator.of(context).push(MaterialPageRoute(builder: (context) => PackagesOfferTabPage(projectViewModel.user))); }, child: Container( decoration: BoxDecoration( diff --git a/lib/pages/landing/landing_page.dart b/lib/pages/landing/landing_page.dart index f73172b6..915c8e96 100644 --- a/lib/pages/landing/landing_page.dart +++ b/lib/pages/landing/landing_page.dart @@ -21,7 +21,9 @@ import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart' import 'package:diplomaticquarterapp/services/family_files/family_files_provider.dart' as family; import 'package:diplomaticquarterapp/services/robo_search/event_provider.dart'; import 'package:diplomaticquarterapp/theme/colors.dart'; +import 'package:diplomaticquarterapp/uitl/HMGNetworkConnectivity.dart'; import 'package:diplomaticquarterapp/uitl/LocalNotification.dart'; +import 'package:diplomaticquarterapp/uitl/PlatformBridge.dart'; import 'package:diplomaticquarterapp/uitl/SignalRUtil.dart'; import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; @@ -232,10 +234,10 @@ class _LandingPageState extends State with WidgetsBindingObserver { }); // HMG (Guest/Internet) Wifi Access [Zohaib Kambrani] //for now commented to reduce this call will enable it when needed - // HMGNetworkConnectivity(context, () { - // GifLoaderDialogUtils.showMyDialog(context); - // PlatformBridge().connectHMGGuestWifi().then((value) => {GifLoaderDialogUtils.hideDialog(context)}); - // }).checkAndConnectIfNoInternet(); + HMGNetworkConnectivity(context, () { + GifLoaderDialogUtils.showMyDialog(context); + PlatformBridge.shared().connectHMGGuestWifi().then((value) => {GifLoaderDialogUtils.hideDialog(context)}); + }).checkAndConnectIfNoInternet(); requestPermissions().then((results) { locationUtils.getCurrentLocation(); diff --git a/lib/pages/packages_offers/OfferAndPackageDetailPage.dart b/lib/pages/packages_offers/OfferAndPackageDetailPage.dart index c4d0ce6e..6b6f1a5a 100644 --- a/lib/pages/packages_offers/OfferAndPackageDetailPage.dart +++ b/lib/pages/packages_offers/OfferAndPackageDetailPage.dart @@ -15,8 +15,9 @@ import 'package:rating_bar/rating_bar.dart'; class OfferAndPackagesDetail extends StatefulWidget { final PackagesResponseModel itemModel; final Function(PackagesResponseModel product) onCartClick; + bool showAddToCartFooter = true; - const OfferAndPackagesDetail({@required this.itemModel, @required this.onCartClick, Key key}) : super(key: key); + OfferAndPackagesDetail({@required this.itemModel, @required this.onCartClick, Key key}) : super(key: key); @override State createState() => OfferAndPackagesDetailState(); @@ -30,6 +31,11 @@ class OfferAndPackagesDetailState extends State { @override Widget build(BuildContext context) { + final images = widget.itemModel.images ?? []; + String image = ""; + if(images.isNotEmpty) + image = widget.itemModel.images.first.src ?? ""; + return BaseView( onModelReady: (model) { viewModel = model; @@ -58,7 +64,11 @@ class OfferAndPackagesDetailState extends State { aspectRatio: 333 / 333, child: ClipRRect( borderRadius: BorderRadius.circular(15.0), - child: Image.network("https://mdlaboratories.com/offersdiscounts/images/thumbs/0000162_dermatology-testing.jpeg", fit: BoxFit.fill), + child: Image.network( + image, + fit: BoxFit.fill, + errorBuilder: (a1,a2,a3) => Container(color: Colors.grey.withOpacity(0.25),), + ), ), ), ), @@ -181,7 +191,8 @@ class OfferAndPackagesDetailState extends State { ], ), ), - Row( + if(widget.showAddToCartFooter && widget.onCartClick != null) + Row( children: [ Expanded( child: Column( diff --git a/lib/pages/packages_offers/OfferAndPackagesPage.dart b/lib/pages/packages_offers/OfferAndPackagesPage.dart index 91f59c74..4a72aeb8 100644 --- a/lib/pages/packages_offers/OfferAndPackagesPage.dart +++ b/lib/pages/packages_offers/OfferAndPackagesPage.dart @@ -14,9 +14,11 @@ import 'package:diplomaticquarterapp/pages/AlHabibMedicalService/h2o/h20_setting import 'package:diplomaticquarterapp/pages/base/base_view.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/ClinicOfferAndPackagesPage.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesCartPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/packages_orders_history.dart'; import 'package:diplomaticquarterapp/routes.dart'; import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart' as auth; import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; +import 'package:diplomaticquarterapp/uitl/navigation_service.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/uitl/utils.dart' as utils; import 'package:diplomaticquarterapp/uitl/utils_new.dart'; @@ -84,7 +86,6 @@ class _PackagesHomePageState extends State { } } - AppScaffold appScaffold; PackagesCategoriesResponseModel selectedClinic; @override @@ -94,87 +95,74 @@ class _PackagesHomePageState extends State { allowAny: true, onModelReady: (model) => viewModel = model, builder: (_, model, wi) { - return appScaffold = AppScaffold( - description: TranslationBase.of(context).offerAndPackagesDetails, - imagesInfo: [ImagesInfo(imageAr: 'https://hmgwebservices.com/Images/MobileApp/CMC/ar/0.png', imageEn: 'https://hmgwebservices.com/Images/MobileApp/CMC/en/0.png')], - appBarTitle: TranslationBase.of(context).offerAndPackages, - isShowAppBar: true, - isPharmacy: false, - backgroundColor: Color(0xfff7f7f7), - showPharmacyCart: false, - isOfferPackages: true, - showOfferPackagesCart: false, - isShowDecPage: false, - showNewAppBar: true, - showNewAppBarTitle: true, - body: Column( - children: [ - Expanded( - child: ListView( - padding: EdgeInsets.only(top: 21, bottom: 21), - physics: BouncingScrollPhysics(), - children: [ - inputWidget(TranslationBase.of(context).search, "", _searchTextController, isInputTypeNum: false), - SizedBox(height: 12), - Padding( - padding: const EdgeInsets.only(left: 21, right: 21), - child: CommonDropDownView(TranslationBase.of(context).browseOffers, selectedClinic?.name ?? TranslationBase.of(context).selectClinic, () => showClinicSelectionList()) - .withBorderedContainer, - ), - SizedBox(height: 12), - Container( - width: double.infinity, - height: MediaQuery.of(context).size.width * 0.8, - child: ListView.separated( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - physics: BouncingScrollPhysics(), - padding: EdgeInsets.only(left: 21, right: 21), - itemCount: viewModel.bestSellerList.length, - separatorBuilder: (context, index) { - return mWidth(9.0); - }, - itemBuilder: (BuildContext context, int index) { - return PackagesItemCard( - itemModel: viewModel.bestSellerList[index], - onCartClick: onProductCartClick, - ); - }, - ), + return Column( + children: [ + Expanded( + child: ListView( + padding: EdgeInsets.only(top: 21, bottom: 21), + physics: BouncingScrollPhysics(), + children: [ + inputWidget(TranslationBase.of(context).search, "", _searchTextController, isInputTypeNum: false), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(left: 21, right: 21), + child: CommonDropDownView(TranslationBase.of(context).browseOffers, selectedClinic?.name ?? TranslationBase.of(context).selectClinic, () => showClinicSelectionList()) + .withBorderedContainer, + ), + SizedBox(height: 12), + Container( + width: double.infinity, + height: MediaQuery.of(context).size.width * 0.8, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: BouncingScrollPhysics(), + padding: EdgeInsets.only(left: 21, right: 21), + itemCount: viewModel.bestSellerList.length, + separatorBuilder: (context, index) { + return mWidth(9.0); + }, + itemBuilder: (BuildContext context, int index) { + return PackagesItemCard( + itemModel: viewModel.bestSellerList[index], + onCartClick: onProductCartClick, + ); + }, ), - SizedBox(height: 12), - Container( - width: double.infinity, - height: MediaQuery.of(context).size.width * 0.8, - child: ListView.separated( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - physics: BouncingScrollPhysics(), - padding: EdgeInsets.only(left: 21, right: 21), - itemCount: viewModel.latestOffersList.length, - separatorBuilder: (context, index) { - return mWidth(9.0); - }, - itemBuilder: (BuildContext context, int index) { - return PackagesItemCard( - itemModel: viewModel.latestOffersList[index], - onCartClick: onProductCartClick, - ); - }, - ), + ), + SizedBox(height: 12), + Container( + width: double.infinity, + height: MediaQuery.of(context).size.width * 0.8, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: BouncingScrollPhysics(), + padding: EdgeInsets.only(left: 21, right: 21), + itemCount: viewModel.latestOffersList.length, + separatorBuilder: (context, index) { + return mWidth(9.0); + }, + itemBuilder: (BuildContext context, int index) { + return PackagesItemCard( + itemModel: viewModel.latestOffersList[index], + onCartClick: onProductCartClick, + ); + }, ), - ], - ), + ), + ], ), - DefaultButton( - TranslationBase.of(context).myCart, - onCartClick, - svgIcon: "assets/images/new/cart.svg", - isTextExpanded: false, - count: viewModel?.service?.customer?.shoppingCartItems?.length ?? 0, - ).insideContainer - ], - ), + ), + + DefaultButton( + TranslationBase.of(context).myCart, + onCartClick, + svgIcon: "assets/images/new/cart.svg", + isTextExpanded: false, + count: viewModel?.service?.customer?.shoppingCartItems?.length ?? 0, + ).insideContainer + ], ); }, ); diff --git a/lib/pages/packages_offers/packages_offers_tab_pager.dart b/lib/pages/packages_offers/packages_offers_tab_pager.dart new file mode 100644 index 00000000..7042327d --- /dev/null +++ b/lib/pages/packages_offers/packages_offers_tab_pager.dart @@ -0,0 +1,86 @@ +import 'package:diplomaticquarterapp/core/model/ImagesInfo.dart'; +import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart'; +import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/packages_orders_history.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class PackagesOfferTabPage extends StatefulWidget{ + + AuthenticatedUser user; + PackagesOfferTabPage(this.user); + + @override + State createState() => PackagesOfferTabPageState(); +} + +class PackagesOfferTabPageState extends State with SingleTickerProviderStateMixin{ + TabController _tabController; + ProjectViewModel _projectViewModel; + + @override + void initState() { + _tabController = TabController(length: 2, vsync: this); + super.initState(); + } + + @override + Widget build(BuildContext context) { + _projectViewModel = Provider.of(context); + return AppScaffold( + description: TranslationBase.of(context).offerAndPackagesDetails, + imagesInfo: [ImagesInfo(imageAr: 'https://hmgwebservices.com/Images/MobileApp/CMC/ar/0.png', imageEn: 'https://hmgwebservices.com/Images/MobileApp/CMC/en/0.png')], + appBarTitle: TranslationBase.of(context).offerAndPackages, + isShowAppBar: true, + isPharmacy: false, + backgroundColor: Color(0xfff7f7f7), + showPharmacyCart: false, + isOfferPackages: true, + showOfferPackagesCart: false, + isShowDecPage: false, + showNewAppBar: true, + showNewAppBarTitle: true, + body: Column( + children: [ + TabBar( + controller: _tabController, + indicatorWeight: 3.0, + indicatorSize: TabBarIndicatorSize.tab, + labelColor: Color(0xff2B353E), + unselectedLabelColor: Color(0xff575757), + labelPadding: EdgeInsets.only(top: 15, bottom: 13, left: 20, right: 20), + labelStyle: TextStyle( + fontFamily: _projectViewModel.isArabic ? 'Cairo' : 'Poppins', + fontSize: 16, + fontWeight: FontWeight.w600, + letterSpacing: -0.48, + ), + unselectedLabelStyle: TextStyle( + fontFamily: _projectViewModel.isArabic ? 'Cairo' : 'Poppins', + fontSize: 16, + fontWeight: FontWeight.w600, + letterSpacing: -0.48, + ), + tabs: [Text(TranslationBase.of(context).offerAndPackages), Text(TranslationBase.of(context).orderLog)], + ), + Expanded( + child: TabBarView( + physics: BouncingScrollPhysics(), + controller: _tabController, + children: [ + PackagesHomePage(widget.user), + PackagesOrdersHistory() + ], + ), + ) + ], + ), + ); + } + +} diff --git a/lib/pages/packages_offers/packages_orders_history.dart b/lib/pages/packages_offers/packages_orders_history.dart new file mode 100644 index 00000000..bc9a44e8 --- /dev/null +++ b/lib/pages/packages_offers/packages_orders_history.dart @@ -0,0 +1,98 @@ +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/uitl/utils_new.dart'; +import 'package:diplomaticquarterapp/widgets/Loader/gif_loader_container.dart'; +import 'package:diplomaticquarterapp/widgets/dialogs/ConfirmWithMessageDialog.dart'; +import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesOrderHistoryItemCard.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'OfferAndPackageDetailPage.dart'; +class PackagesOrdersHistory extends StatefulWidget{ + @override + State createState() => PackagesOrdersHistorySatate(); +} + +class PackagesOrdersHistorySatate extends State{ + ProjectViewModel projectViewModel; + PackagesViewModel packagesViewModel; + + List orders; + + @override + void initState() { + Future.delayed(Duration(milliseconds: 200)).then((value) async{ + final orders_ = await packagesViewModel.service.orderHistory(context: context); + setState(() => orders = orders_ ?? []); + }); + } + + @override + Widget build(BuildContext context) { + projectViewModel = Provider.of(context); + return BaseView( + allowAny: true, + onModelReady: (model) => packagesViewModel = model, + builder: (_, model, wi) { + return content(context); + } + ); + } + + Widget content(BuildContext context){ + if(orders == null){ + return SizedBox(); + }else if(orders.isEmpty){ + return getNoDataWidget(context); + }else { + return ListView.separated( + padding: EdgeInsets.all(20), + itemCount: orders.length, + itemBuilder: (ctx, idx) => item(ctx, order: orders[idx]), + separatorBuilder: (ctx, idx) => SizedBox(height: 10), + ); + } + } + + Widget item(BuildContext context, {@required PackagesResponseModel order}){ + return InkWell( + child: PackagesOrderHistoryItemCard(itemModel: order, viewModel: packagesViewModel), + onTap: (){ + final detailPage = OfferAndPackagesDetail(itemModel: order, onCartClick: null)..showAddToCartFooter = false; + Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => detailPage)); + }, + ); + } + + void showConfirmMessage(BuildContext context, {@required PackagesResponseModel order}){ + showDialog( + context: context, + builder: (cxt) => ConfirmWithMessageDialog( + message: TranslationBase.of(context).cancelOrderMsg, + onTap: () { + }, + ), + ); + return; + } + + Color color(int status){ + if (status == 1) { //pending + return Color(0xffCC9B14); + } else if (status == 2) { //processing + return Color(0xff2E303A); + } else if (status == 3) { //completed + return Color(0xff359846); + } else if (status == 4 || status == 6 || status == 7) { //cancel // Rejected + return Color(0xffD02127); + } + return Colors.transparent; + } +} \ No newline at end of file diff --git a/lib/pages/rateAppointment/rate_appointment_clinic.dart b/lib/pages/rateAppointment/rate_appointment_clinic.dart index 94ee801a..aac0a4d6 100644 --- a/lib/pages/rateAppointment/rate_appointment_clinic.dart +++ b/lib/pages/rateAppointment/rate_appointment_clinic.dart @@ -70,7 +70,7 @@ class _RateAppointmentClinicState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - model.appointmentDetails.projectName, + model.appointmentDetails.projectName ?? '', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2E303A), letterSpacing: -0.64, height: 25 / 16), ), Text( diff --git a/lib/uitl/app_shared_preferences.dart b/lib/uitl/app_shared_preferences.dart index f90a2f98..0a952cf3 100644 --- a/lib/uitl/app_shared_preferences.dart +++ b/lib/uitl/app_shared_preferences.dart @@ -6,7 +6,7 @@ class AppSharedPreferences { Future _prefs = SharedPreferences.getInstance(); /// Save String [key] the key for save value [value] the value we need to save it - setString(String key, String value) async { + Future setString(String key, String value) async { final SharedPreferences prefs = await _prefs; return prefs.setString(key, value); } @@ -42,7 +42,7 @@ class AppSharedPreferences { } /// Get String [key] the key was saved - getString(String key) async { + Future getString(String key) async { final SharedPreferences prefs = await _prefs; return prefs.getString(key); } diff --git a/lib/uitl/navigation_service.dart b/lib/uitl/navigation_service.dart index d394fa63..a589249c 100644 --- a/lib/uitl/navigation_service.dart +++ b/lib/uitl/navigation_service.dart @@ -1,6 +1,8 @@ import 'package:diplomaticquarterapp/locator.dart'; import 'package:flutter/material.dart'; +MaterialPageRoute _pageRoute(Widget page) => MaterialPageRoute(builder: (BuildContext context) => page); + class NavigationService { final GlobalKey navigatorKey = new GlobalKey(); @@ -18,4 +20,11 @@ class NavigationService { } } +class Navigate{ + static to({@required Widget page}){ + final context = locator().navigatorKey.currentContext; + Navigator.of(context).push(_pageRoute(page)); + } +} + BuildContext get currentContext => locator().navigatorKey.currentContext; diff --git a/lib/uitl/translations_delegate_base.dart b/lib/uitl/translations_delegate_base.dart index 5fdcbb01..e0f7e728 100644 --- a/lib/uitl/translations_delegate_base.dart +++ b/lib/uitl/translations_delegate_base.dart @@ -1472,6 +1472,7 @@ class TranslationBase { String get enablingWifi => localizedValues['enablingWifi'][locale.languageCode]; String get offerAndPackages => localizedValues['offerAndPackages'][locale.languageCode]; + String get orderHistory => localizedValues['orderHistory'][locale.languageCode]; String get offerAndPackagesDetails => localizedValues['offerAndPackagesDetails'][locale.languageCode]; diff --git a/lib/uitl/utils.dart b/lib/uitl/utils.dart index f20f4243..97ba5e25 100644 --- a/lib/uitl/utils.dart +++ b/lib/uitl/utils.dart @@ -546,7 +546,7 @@ class Utils { medical.add(InkWell( onTap: () { userData().then((userData_) { - if (projectViewModel.isLogin && userData_ != null) { + if (projectViewModel.isLogin && userData_ != null || true) { String patientID = userData_.patientID.toString(); GifLoaderDialogUtils.showMyDialog(context); projectViewModel diff --git a/lib/widgets/buttons/defaultButton.dart b/lib/widgets/buttons/defaultButton.dart index affd9a3e..e2ee35dc 100644 --- a/lib/widgets/buttons/defaultButton.dart +++ b/lib/widgets/buttons/defaultButton.dart @@ -3,6 +3,7 @@ import 'package:flutter_svg/svg.dart'; extension WithContainer on Widget { Widget get insideContainer => Container(color: Colors.white, padding: EdgeInsets.only(top: 16, bottom: 16, right: 21, left: 21), child: this); + Widget get expand => Expanded(child: this); } class DefaultButton extends StatelessWidget { diff --git a/lib/widgets/in_app_browser/InAppBrowser.dart b/lib/widgets/in_app_browser/InAppBrowser.dart index 9e363e4d..e139f105 100644 --- a/lib/widgets/in_app_browser/InAppBrowser.dart +++ b/lib/widgets/in_app_browser/InAppBrowser.dart @@ -99,18 +99,19 @@ class MyInAppBrowser extends InAppBrowser { if (onExitCallback != null) onExitCallback(appo, isPaymentDone); } - // @override - // Future shouldOverrideUrlLoading(ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest) async { - // var url = shouldOverrideUrlLoadingRequest.url; - // debugPrint("redirecting/overriding to: $url"); - // - // if (paymentType == _PAYMENT_TYPE.PACKAGES && [PACKAGES_PAYMENT_SUCCESS_URL, PACKAGES_PAYMENT_FAIL_URL].contains(url)) { - // isPaymentDone = (url == PACKAGES_PAYMENT_SUCCESS_URL); - // close(); - // } - // - // return ShouldOverrideUrlLoadingAction.ALLOW; - // } + @override + Future shouldOverrideUrlLoading(NavigationAction navigationAction) { + var url = navigationAction.request.url.toString(); + debugPrint("redirecting/overriding to: $url"); + + if (paymentType == _PAYMENT_TYPE.PACKAGES && [PACKAGES_PAYMENT_SUCCESS_URL, PACKAGES_PAYMENT_FAIL_URL].contains(url)) { + isPaymentDone = (url == PACKAGES_PAYMENT_SUCCESS_URL); + close(); + } + + return Future.value(NavigationActionPolicy.ALLOW); + } + getLanguageID() async { return await sharedPref.getStringWithDefaultValue(APP_LANGUAGE, 'ar'); @@ -135,7 +136,7 @@ class MyInAppBrowser extends InAppBrowser { openPackagesPaymentBrowser({@required int customer_id, @required int order_id}) { paymentType = _PAYMENT_TYPE.PACKAGES; var full_url = '$PACKAGES_REQUEST_PAYMENT_URL?customer_id=$customer_id&order_id=$order_id'; - this.browser.openUrlRequest(urlRequest: URLRequest(url: Uri.parse(full_url)), options: _InAppBrowserOptions); + this.openUrlRequest(urlRequest: URLRequest(url: Uri.parse(full_url)), options: _InAppBrowserOptions); } openPaymentBrowser(double amount, String orderDesc, String transactionID, String projId, String emailId, String paymentMethod, dynamic patientType, String patientName, dynamic patientID, @@ -367,6 +368,8 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser { onLoadStartCallback("ApplePay"); } + + @override void onClosed() { print("ChromeSafari browser closed"); diff --git a/lib/widgets/offers_packages/PackagesOrderHistoryItemCard.dart b/lib/widgets/offers_packages/PackagesOrderHistoryItemCard.dart new file mode 100644 index 00000000..6ba6bb29 --- /dev/null +++ b/lib/widgets/offers_packages/PackagesOrderHistoryItemCard.dart @@ -0,0 +1,124 @@ +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/theme/colors.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart'; +import 'package:diplomaticquarterapp/uitl/utils_new.dart'; +import 'package:diplomaticquarterapp/widgets/CounterView.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +bool wide = true; + +class PackagesOrderHistoryItemCard extends StatefulWidget { + final PackagesResponseModel itemModel; + final PackagesViewModel viewModel; + final Function getCartItems; + + const PackagesOrderHistoryItemCard({@required this.itemModel, @required this.viewModel, this.getCartItems, Key key}) : super(key: key); + + @override + State createState() => PackagesOrderHistoryItemCardState(); +} + +class PackagesOrderHistoryItemCardState extends State { + @override + Widget build(BuildContext context) { + wide = !wide; + return Container( + decoration: cardRadius(15.0), + height: 95, + padding: EdgeInsets.all(9), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + _image(widget.itemModel), + SizedBox(width: 7), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _itemName(widget.itemModel.name), + Expanded(child: _itemDescription(widget.itemModel.shortDescription)), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _itemPrice(widget.itemModel.price, context: context), + _cancelButton(context, itemModel: widget.itemModel) + ], + ), + ], + ), + ) + ], + ), + ); + } +} + +Widget _cancelButton(BuildContext context, {@required PackagesResponseModel itemModel, VoidCallback onClick}) => InkWell( + onTap:onClick, + child: Padding( + padding: const EdgeInsets.only(right: 3, bottom: 3), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Text( + // TranslationBase.of(context).removeMember, + // style: TextStyle( + // fontSize: 10, + // color: CustomColors.accentColor, + // fontWeight: FontWeight.w600, + // letterSpacing: -0.36, + // ), + // ), + // SizedBox(width: 2), + // SvgPicture.asset("assets/images/new-design/delete.svg", color: CustomColors.accentColor), + ], + ), + ), + ); + +Widget _image(PackagesResponseModel model) => AspectRatio( + aspectRatio: 1 / 1, + child: ClipRRect( + borderRadius: BorderRadius.circular(15), child: ((model.images ?? []).isNotEmpty) ? Utils.loadNetworkImage(url: model.images.first.src, fitting: BoxFit.fill) : Container(color: Colors.grey[200])), + ); + +Widget _itemName(String name) => Text( + name, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Color(0xff2B353E), + letterSpacing: -0.56, + ), + ); + +Widget _itemDescription(String desc) => Text( + desc, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: Color(0xff535353), + letterSpacing: -0.4, + ), + ); + +Widget _itemPrice(double price, {@required BuildContext context}) { + final prc = (price ?? 0.0).toStringAsFixed(2); + return Text( + '$prc ${TranslationBase.of(context).sar}', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Color(0xff5D5D5D), + letterSpacing: -0.44, + ), + ); +}