diff --git a/android/app/build.gradle b/android/app/build.gradle index 5d196986..c656f247 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -27,7 +27,7 @@ apply plugin: 'com.google.gms.google-services' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 30 + compileSdkVersion 28 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -41,7 +41,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.cloud.diplomaticquarterapp" minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true @@ -78,6 +78,12 @@ dependencies { implementation 'com.google.guava:guava:27.0.1-android' // Dependency on local binaries implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + implementation "org.jetbrains.anko:anko-commons:0.10.4" + implementation 'com.github.kittinunf.fuel:fuel:2.3.0' //for JVM + implementation 'com.github.kittinunf.fuel:fuel-android:2.3.0' //for Android // Dependency on a remote binary // implementation 'com.example.android:app-magic:12.3' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d0751535..8162b752 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,13 @@ + - + FlutterApplication and put your custom class here. + --> @@ -17,33 +20,72 @@ - - + + + + + + + + + + + + + + + android:label="diplomaticquarterapp" + android:usesCleartextTraffic="true"> + + + + + + + + + + + + + + + + + - + to determine the Window background behind the Flutter UI. + --> - + Flutter's first frame. + --> @@ -52,18 +94,20 @@ + - - + - - + + @@ -71,14 +115,9 @@ - - - + - - - - - + \ No newline at end of file diff --git a/android/app/src/main/java/com/cloud/diplomaticquarterapp/API.kt b/android/app/src/main/java/com/cloud/diplomaticquarterapp/API.kt new file mode 100644 index 00000000..f8cc815e --- /dev/null +++ b/android/app/src/main/java/com/cloud/diplomaticquarterapp/API.kt @@ -0,0 +1,9 @@ +package com.cloud.diplomaticquarterapp + +class API { + companion object{ + private val BASE = "https://uat.hmgwebservices.com" + private val SERVICE = "Services/Patients.svc/REST" + val WIFI_CREDENTIALS = "$BASE/$SERVICE/Hmg_SMS_Get_By_ProjectID_And_PatientID" + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/cloud/diplomaticquarterapp/FlutterMainActivity.kt b/android/app/src/main/java/com/cloud/diplomaticquarterapp/FlutterMainActivity.kt new file mode 100644 index 00000000..b984835f --- /dev/null +++ b/android/app/src/main/java/com/cloud/diplomaticquarterapp/FlutterMainActivity.kt @@ -0,0 +1,91 @@ +package com.cloud.diplomaticquarterapp + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import com.cloud.diplomaticquarterapp.utils.PlatformBridge +import io.flutter.embedding.android.FlutterView +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.plugin.common.MethodChannel +import io.flutter.view.FlutterMain +import java.util.ArrayList + +class FlutterMainActivity : AppCompatActivity() { + + private lateinit var channel: MethodChannel + + private var flutterView: FlutterView? = null + companion object { + private var flutterEngine: FlutterEngine? = null + private const val CHANNEL = "HMG-Platform-Bridge" + private const val METHOD_CONNECT_WIFI = "connectHMGGuestWifi" + private const val METHOD_SHOW_LOADING = "loading" + } + + + // to get and check returned intent + private fun getArgsFromIntent(intent: Intent): Array? { + // Before adding more entries to this list, consider that arbitrary + // Android applications can generate intents with extra data and that + // there are many security-sensitive args in the binary. + val args = ArrayList() + if (intent.getBooleanExtra("trace-startup", false)) { + args.add("--trace-startup") + } + if (intent.getBooleanExtra("start-paused", false)) { + args.add("--start-paused") + } + if (intent.getBooleanExtra("enable-dart-profiling", false)) { + args.add("--enable-dart-profiling") + } + if (!args.isEmpty()) { + return args.toTypedArray() + } + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val args = getArgsFromIntent(intent) + + // check if flutterEngine is null + if (flutterEngine == null) { + println(args) + flutterEngine = FlutterEngine(this, args) + flutterEngine!!.dartExecutor.executeDartEntrypoint( + // set which of dart methode will be used here + DartExecutor.DartEntrypoint(FlutterMain.findAppBundlePath(), "main") + ) + } + + setContentView(R.layout.activity_flutter_main) + + flutterView = findViewById(R.id.flutterView) + flutterView!!.attachToFlutterEngine(flutterEngine!!) + + PlatformBridge(flutterEngine!!.dartExecutor.binaryMessenger, this).create() + } + + + override fun onResume() { + super.onResume() + flutterEngine!!.lifecycleChannel.appIsResumed() + } + + override fun onPause() { + super.onPause() + flutterEngine!!.lifecycleChannel.appIsInactive() + } + + override fun onStop() { + super.onStop() + flutterEngine!!.lifecycleChannel.appIsPaused() + } + + override fun onDestroy() { + flutterView!!.detachFromFlutterEngine() + super.onDestroy() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/HMG_Wifi.kt b/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/HMG_Wifi.kt new file mode 100644 index 00000000..5e43fafd --- /dev/null +++ b/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/HMG_Wifi.kt @@ -0,0 +1,316 @@ +package com.cloud.diplomaticquarterapp.utils + +import android.annotation.SuppressLint +import android.content.Context +import android.net.ConnectivityManager +import android.net.wifi.ScanResult +import android.net.wifi.WifiConfiguration +import android.net.wifi.WifiManager +import android.util.Log +import com.cloud.diplomaticquarterapp.API +import com.cloud.diplomaticquarterapp.FlutterMainActivity +import com.github.kittinunf.fuel.core.extensions.jsonBody +import com.github.kittinunf.fuel.httpGet +import com.github.kittinunf.fuel.httpPost +import org.json.JSONObject +import java.util.* + + +@SuppressLint("MissingPermission") +class HMG_Wifi(flutterMainActivity: FlutterMainActivity) { + val TAG = "WIFI" + + var context = flutterMainActivity; + var completionListener: ((status:Boolean, message:String) -> Unit)? = null + + + private var SSID = "HMG-GUEST" + private var USER_NAME = "" + private var PASSWORD = "" + private lateinit var PATIENT_ID:String + /* + * Helpful: + * http://stackoverflow.com/questions/5452940/how-can-i-get-android-wifi-scan-results-into-a-list + */ + fun triggerWifiScan(context: Context) { + val wifi = context.getSystemService(Context.WIFI_SERVICE) as WifiManager + wifi.startScan() + } + + /* + * Helpful: + * http://stackoverflow.com/questions/8818290/how-to-connect-to-a-specific-wifi-network-in-android-programmatically + */ + fun connectToWifiNetworkWith(patientId:String): HMG_Wifi { + PATIENT_ID = patientId + + 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 + "\"" + + if (security == "OPEN") { + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) + } else if (security == "WEP") { + conf.wepKeys[0] = "\"" + networkPass + "\"" + conf.wepTxKeyIndex = 0 + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) + conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40) + } else { + conf.preSharedKey = "\"" + networkPass + "\"" + } + + // Then, you need to add it to Android wifi manager settings: + val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + + val networkId = wifiManager.addNetwork(conf) + Log.d(TAG, "Network ID: $networkId") + + //wifiManager.disconnect(); + val result = wifiManager.enableNetwork(networkId, true) + //wifiManager.reconnect(); + wifiManager.saveConfiguration() + + if(result == true){ + authNetworkConnection(networkId); + }else{ + completionListener?.let { it(false, "Error connecting to HMG network") } + } + return this + } + + private var authTimer:Timer? = null + fun authNetworkConnection(networkId:Int){ + authTimer = Timer() + authTimer?.scheduleAtFixedRate( object : TimerTask(){ + override fun run() { + if (connectedNetworkId() == networkId && connectedNetworkIPAddress() > 0){ + authServerCall() + authTimer?.cancel() + } + } + + },2000,1000) + + // If wifi not connected in 5 sec terminate with fail status + Timer().schedule(object : TimerTask(){ + override fun run() { + if (null != authTimer) { + authTimer?.cancel() + completionListener?.let { it(false, "Error connecting to HMG network") } + } + } + },5000) + + } + + fun authServerCall(){ + fun forceCallOverWifi(){ + val connectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + connectivityManager.networkPreference = ConnectivityManager.TYPE_WIFI + } + + fun call(){ + forceCallOverWifi() + val params = listOf("cmd" to "authenticate", "password" to PASSWORD, "user" to USER_NAME) + val serverUrl = "https://captiveportal-login.hmg.com/cgi-bin/login" +// val serverUrl = "http://192.168.102.223/cgi-bin/login" + serverUrl + .httpPost(params) + .timeout(10000) + .response { request, response, result -> + Log.v(TAG,response.statusCode.toString()) + + haveInternet { have -> + if(have){ + Log.v(TAG,"Connected to internet via $SSID network at HMG") + completionListener?.let { it(true, "Successfully connected to the internet") } + }else{ + Log.e(TAG,"failed to connect to internet via $SSID network at HMG") + completionListener?.let { it(false, "Authentication failed or you are already using your credentials on another device") } + } + } + } + } + + haveInternet { has -> + if (has){ + getAuthCredentials { + call() + } + }else{ + completionListener?.let { it(false,"You must have active internet connection to connect with HMG Network") } + } + } + } + + fun haveInternet(completion:((status:Boolean)->Unit)){ + "https://captive.apple.com".httpGet().response {request, response, result -> + val have = response.statusCode == 200 && String(response.data).contains("Success",true) + completion(have) + } + } + + fun getAuthCredentials(completion:(() -> Unit)){ + val test = true + if (test){ + USER_NAME = "2300" + PASSWORD = "1820" + completion() + return + } + + val jsonBody = """{"PatientID":$PATIENT_ID}""" + API.WIFI_CREDENTIALS + .httpPost() + .jsonBody(jsonBody,Charsets.UTF_8) + .response { request, response, result -> + val jsonString = String(response.data) + Log.d(TAG, "JSON $jsonString") + + if (response.statusCode == 200){ + + val jsonObject = JSONObject(jsonString) + if(!jsonObject.getString("ErrorMessage").equals("null")){ + val errorMsg = jsonObject.getString("ErrorMessage") + completionListener?.let { it(false,errorMsg) } + + }else{ + jsonObject.getJSONArray("Hmg_SMS_Get_By_ProjectID_And_PatientIDList").let { array -> + array.getJSONObject(0).let { object_ -> + if (object_.has("UserName") && object_.has("UserName")){ + USER_NAME = object_.getString("UserName") + PASSWORD = object_.getString("Password") + completion() + }else{ + completionListener?.let { it(false,"Failed to get your internet credentials") } + } + } + } + } + + }else{ + completionListener?.let { it(false,"Failed to get your internet credentials") } + } + } + } + + /* + * Helpful: + * http://stackoverflow.com/questions/6517314/android-wifi-connection-programmatically + */ + fun getScanResultSecurity(result: ScanResult): String? { + val capabilities: String = result.capabilities + val securityModes = arrayOf("WEP", "PSK", "EAP") + for (securityMode in securityModes) { + if (capabilities.contains(securityMode)) { + return securityMode + } + } + return "OPEN" + } + + //connects to the given ssid + fun connectToWPAWiFi(ssid: String, password: String){ + +// WifiUtils.withContext(context) +// .connectWith(ssid, "") +// .setTimeout(40000) +// .onConnectionResult(object : ConnectionSuccessListener { +// override fun success() { +// Log.v(TAG,"Success") +// } +// +// override fun failed(@NonNull errorCode: ConnectionErrorCode) { +// Log.v(TAG,"Failed") +// } +// }) +// .start() +// if(isConnectedTo(ssid)){ //see if we are already connected to the given ssid +// return +// } +// +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { +// 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) +// +// }else{ +// +// try { +// val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager +// +// Log.e(TAG, "connection wifi pre Q") +// +// var netId: Int = wm.addNetwork(getWifiConfig(ssid)) +// if (netId == -1) netId = getExistingNetworkId(ssid); +// wm.saveConfiguration() +// if(wm.enableNetwork(netId, true)){ +// Log.v(TAG,"HMG-GUEST Connected") +// }else{ +// Log.v(TAG,"HMG-GUEST failed to connect") +// } +// } catch (e: Exception) { +// e.printStackTrace() +// Log.v(TAG,"HMG-GUEST failed to connect") +// } +// } + + } + + fun connectedNetworkId():Int{ + val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + return wm.connectionInfo.networkId + } + + fun connectedNetworkIPAddress():Int{ + val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + return wm.connectionInfo.ipAddress + } + + fun isConnectedTo(bssid: String):Boolean{ + val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + if(wm.connectionInfo.bssid == bssid){ + return true + } + return false + } + +} \ No newline at end of file diff --git a/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/HTTPRequest.kt b/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/HTTPRequest.kt new file mode 100644 index 00000000..6bc738c8 --- /dev/null +++ b/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/HTTPRequest.kt @@ -0,0 +1,49 @@ +package com.cloud.diplomaticquarterapp.utils + +import android.os.AsyncTask +import android.os.Parcel +import android.os.Parcelable +import android.util.Log +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLEncoder + +class HTTPRequest { + val TAG = "HTTPRequest" + + fun post(serverURL:String, params:Map){ + + var encodedParamsList = params.map { + URLEncoder.encode(it.key, "UTF-8") + "=" + URLEncoder.encode(it.value,"UTF-8") + } + + val postParam = encodedParamsList.joinToString (separator = "&") + + + val url = URL(serverURL) + + with(url.openConnection() as HttpURLConnection) { + requestMethod = "POST" + + val wr = OutputStreamWriter(outputStream) + wr.write(postParam) + wr.flush() + + BufferedReader(InputStreamReader(inputStream)).use { + val response = StringBuffer() + + var inputLIne = it.readLine() + while(inputLIne != null) { + response.append(inputLIne) + inputLIne = it.readLine() + } + + it.close() + Log.v(TAG,response.toString()) + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/PlatformBridge.kt b/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/PlatformBridge.kt new file mode 100644 index 00000000..ff7fdd1d --- /dev/null +++ b/android/app/src/main/java/com/cloud/diplomaticquarterapp/utils/PlatformBridge.kt @@ -0,0 +1,47 @@ +package com.cloud.diplomaticquarterapp.utils + +import android.util.Log +import com.cloud.diplomaticquarterapp.FlutterMainActivity +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +class PlatformBridge(binaryMessenger: BinaryMessenger, flutterMainActivity: FlutterMainActivity) { + private var binaryMessenger = binaryMessenger + private var mainActivity = flutterMainActivity + + private lateinit var channel: MethodChannel + + companion object { + private const val CHANNEL = "HMG-Platform-Bridge" + private const val METHOD_CONNECT_WIFI = "connectHMGGuestWifi" + private const val METHOD_SHOW_LOADING = "loading" + } + + fun create(){ + channel = MethodChannel(binaryMessenger, CHANNEL) + channel.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result -> + if (methodCall.method == METHOD_CONNECT_WIFI) { + (methodCall.arguments as ArrayList<*>).let { + require(it.size > 0 && (it.get(0) is String),lazyMessage = { + "Missing or invalid arguments (Must have one argument 'String at 0'" + }) + + val patientId = it.get(0).toString() + HMG_Wifi(mainActivity) + .connectToWifiNetworkWith(patientId) + .completionListener = { status, message -> + + Log.v(this.javaClass.simpleName, "$status | $message") + + } + } + + }else if (methodCall.method == METHOD_CONNECT_WIFI) { + + }else { + result.notImplemented() + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/MainActivity.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/MainActivity.kt index b8a87c8c..ac33db7c 100644 --- a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/MainActivity.kt +++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/MainActivity.kt @@ -1,11 +1,21 @@ package com.cloud.diplomaticquarterapp +import android.os.Bundle +import android.os.PersistableBundle import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodCall class MainActivity: FlutterFragmentActivity() { + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - GeneratedPluginRegistrant.registerWith(flutterEngine); + GeneratedPluginRegistrant.registerWith(flutterEngine); } } \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_flutter_main.xml b/android/app/src/main/res/layout/activity_flutter_main.xml new file mode 100644 index 00000000..61249b30 --- /dev/null +++ b/android/app/src/main/res/layout/activity_flutter_main.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..73862c41 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 8e56476b..84dd6f91 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.4.10' repositories { google() jcenter() diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index a1cd00a5..d5a1e7da 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -5c29d18b2483146f4513132fbdc2a003 \ No newline at end of file +c948a9de8d5fb4b791dcd366c30ba789 \ No newline at end of file diff --git a/ios/Podfile b/ios/Podfile index a2bf220d..77760fe1 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -12,6 +12,7 @@ project 'Runner', { # pod 'FBSDKCoreKit' # pod 'FBSDKLoginKit' +pod 'NVActivityIndicatorView' def parse_KV_file(file, separator='=') file_abs_path = File.expand_path(file) @@ -87,4 +88,4 @@ post_install do |installer| config.build_settings['ENABLE_BITCODE'] = 'NO' end end -end \ No newline at end of file +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6307254e..1094393e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -120,6 +120,9 @@ PODS: - Flutter - native_progress_hud (0.0.1): - Flutter + - NVActivityIndicatorView (5.1.1): + - NVActivityIndicatorView/Base (= 5.1.1) + - NVActivityIndicatorView/Base (5.1.1) - path_provider (0.0.1): - Flutter - path_provider_linux (0.0.1): @@ -201,6 +204,7 @@ DEPENDENCIES: - maps_launcher (from `.symlinks/plugins/maps_launcher/ios`) - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - native_progress_hud (from `.symlinks/plugins/native_progress_hud/ios`) + - NVActivityIndicatorView - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) @@ -236,6 +240,7 @@ SPEC REPOS: - GoogleMaps - GoogleUtilities - nanopb + - NVActivityIndicatorView - PromisesObjC - Protobuf - Reachability @@ -381,6 +386,7 @@ SPEC CHECKSUMS: nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc native_device_orientation: e24d00be281de72996640885d80e706142707660 native_progress_hud: f95f5529742b36a3c7fdecfa88dc018319e39bf9 + NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 @@ -410,6 +416,6 @@ SPEC CHECKSUMS: wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 webview_flutter: d2b4d6c66968ad042ad94cbb791f5b72b4678a96 -PODFILE CHECKSUM: 8e8c706676125c48908ca559e285c906e5d20397 +PODFILE CHECKSUM: fd41bba6db38332890981ce38f0747bdb94c61f2 COCOAPODS: 1.10.0 diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index 00abc512..de2d580c 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,16 +1,16 @@ - + - + - + - + @@ -18,12 +18,85 @@ + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Controllers/MainFlutterVC.swift b/ios/Runner/Controllers/MainFlutterVC.swift new file mode 100644 index 00000000..ba67f0a5 --- /dev/null +++ b/ios/Runner/Controllers/MainFlutterVC.swift @@ -0,0 +1,31 @@ +// +// MainFlutterVC.swift +// Runner +// +// Created by ZiKambrani on 25/03/1442 AH. +// + +import UIKit +import Flutter + +class MainFlutterVC: FlutterViewController { + var root_view:MainViewController? + + override func viewDidLoad() { + super.viewDidLoad() + + } + + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/ios/Runner/Controllers/MainViewController.swift b/ios/Runner/Controllers/MainViewController.swift new file mode 100644 index 00000000..94a355f9 --- /dev/null +++ b/ios/Runner/Controllers/MainViewController.swift @@ -0,0 +1,96 @@ +// +// MainViewController.swift +// Runner +// +// Created by ZiKambrani on 26/03/1442 AH. +// + +import UIKit +import NVActivityIndicatorView + +class MainViewController: UIViewController { + @IBOutlet weak var lblLoadingText: UILabel! + @IBOutlet weak var loading: NVActivityIndicatorView! + + override func viewDidLoad() { + super.viewDidLoad() + print(loading) + } + + func createBridge(flutterViewController:FlutterViewController){ + let connectHMGGuestWifi = FlutterMethodChannel(name: "HMG-Platform-Bridge",binaryMessenger: flutterViewController.binaryMessenger) + connectHMGGuestWifi.setMethodCallHandler { (methodCall, result) in + if methodCall.method == "connectHMGGuestWifi"{ + self.connectWifi(result: result) + }else if methodCall.method == "loading"{ + self.showLoading(flutterMethodCall: methodCall) + }else{ + + } + print("") + } + } + + + // Connect HMG-Guest Wifi and Internet + func connectWifi(result: @escaping FlutterResult){ + showLoading(message: "Connecting...") + HMG_GUEST.shared.connect { (status, message) in + result(status ? 1 : 0) + self.showLoading(false); + if status{ + self.showMessage(title:"Congratulations", message:message) + }else{ + self.showMessage(title:"Ooops,", message:message) + } + } + } + + + // Loading/Progress + private func showLoading(flutterMethodCall:FlutterMethodCall){ + if let args = flutterMethodCall.arguments as? [Any], + let message = args.first as? String, let show = args.last as? Bool{ + showLoading(message: message, show) + }else{ + assert(true, "Missing or invalid arguments (Must have two argument 'String at 0' and Boolean at 1)") + } + } + func showLoading(message:String = "Please wait...", _ show:Bool = true){ + DispatchQueue.main.async { + if show{ + self.lblLoadingText.text = message + self.loading.superview?.isHidden = false + self.loading.startAnimating() + }else{ + self.lblLoadingText.text = "" + self.loading.superview?.isHidden = true + self.loading.stopAnimating() + } + } + } + + + // Message Dailog + func showMessage(title:String, message:String){ + DispatchQueue.main.async { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert ) + alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil)) + self.present(alert, animated: true) { + + } + } + } + + + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let flutterVC = segue.destination as? MainFlutterVC{ + flutterVC.root_view = self + createBridge(flutterViewController: flutterVC) + } + } + +} diff --git a/ios/Runner/Helper/GlobalHelper.swift b/ios/Runner/Helper/GlobalHelper.swift new file mode 100644 index 00000000..3506e26d --- /dev/null +++ b/ios/Runner/Helper/GlobalHelper.swift @@ -0,0 +1,9 @@ +// +// GlobalHelper.swift +// Runner +// +// Created by ZiKambrani on 29/03/1442 AH. +// + +import UIKit + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index b807d9c6..1d97a387 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,11 @@ + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -22,22 +27,22 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS - io.flutter.embedded_views_preview - + io.flutter.embedded_views_preview + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main - NSMicrophoneUsageDescription - Need microphone access for uploading videos - NSCameraUsageDescription - Need camera access for uploading images - NSLocationUsageDescription - Need location access for updating nearby friends - NSLocationWhenInUseUsageDescription - This app will use your location to show cool stuffs near you. - NSPhotoLibraryUsageDescription - Need photo library access for uploading images + NSMicrophoneUsageDescription + Need microphone access for uploading videos + NSCameraUsageDescription + Need camera access for uploading images + NSLocationUsageDescription + Need location access for updating nearby friends + NSLocationWhenInUseUsageDescription + This app will use your location to show cool stuffs near you. + NSPhotoLibraryUsageDescription + Need photo library access for uploading images UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 903def2a..ea0c25e7 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.developer.networking.HotspotConfiguration + + com.apple.developer.networking.wifi-info + diff --git a/ios/Runner/WifiConnect/HMG_GUEST.swift b/ios/Runner/WifiConnect/HMG_GUEST.swift new file mode 100644 index 00000000..a578309f --- /dev/null +++ b/ios/Runner/WifiConnect/HMG_GUEST.swift @@ -0,0 +1,129 @@ +// +// HMG_GUEST.swift +// HMG-iOS-Wifi +// +// Created by ZiKambrani on 23/03/1442 AH. +// Copyright © 1442 ZiKambrani. All rights reserved. +// + +import UIKit +import NetworkExtension +import SystemConfiguration.CaptiveNetwork + + +class HMG_GUEST{ + static let shared = HMG_GUEST() + private let SSID = "HMG-GUEST" + private let USER = "1301" + private let PASS = "8928" + + var complete:((_ status:Bool, _ message:String) -> Void)! + func connect(completion:@escaping ((_ status:Bool, _ message:String) -> Void)){ + complete = completion + + if isAlreadyConnected() { + hasInternet { (has) in + if has == true{ + self.complete(true, "You already connected to internet") + return + }else{ + self.authenticate() + } + } + }else{ + connect() + } + } + + private func connect() { + let hotspotConfig = NEHotspotConfiguration(ssid: SSID) + hotspotConfig.joinOnce = true + + NEHotspotConfigurationManager.shared.apply(hotspotConfig) {[weak self] (error) in + guard let self = self else { return; } + + if let error = error { + self.complete(false, error.localizedDescription ?? "Error connecting to HMG wifi network" ) + }else{ + _ = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in + self.authenticate() + } + } + } + } + + func authenticate(){ + + func callLogin(){ + + let parameters = "Login=Log%20In&cmd=authenticate&password=1820&user=2300" + let postData = parameters.data(using: .utf8) + + var request = URLRequest(url: URL(string: "https://captiveportal-login.hmg.com/cgi-bin/login")!,timeoutInterval: 5) + request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + request.httpMethod = "POST" + request.httpBody = postData + + let task = URLSession.shared.dataTask(with: request) { data, response, error in + // guard let data = data else { + // self.complete(false, "Error at authentication") + // return + // } + + self.hasInternet { (has) in + self.complete(has, has ? "Successfully connected to the internet" : "Authentication failed or you are already using your credentials on another device") + } + } + task.resume() + + } + + self.hasInternet { (has) in + if has == true{ + self.complete(true, "Your internet account is already authenticated") + }else{ + callLogin() + } + } + + } + + private func isAlreadyConnected() -> Bool{ + var currentSSID: String? + if let interfaces = CNCopySupportedInterfaces() as NSArray? { + for interface in interfaces { + if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? { + currentSSID = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String + break + } + } + } + + print("CurrentConnectedSSID: \(currentSSID)") + return currentSSID == SSID + } + + + func hasInternet( completion:@escaping ((Bool)->Void)){ + + let testUrl = "https://captive.apple.com" + var request = URLRequest(url: URL(string: testUrl)!,timeoutInterval: 5) + request.httpMethod = "GET" + let task = URLSession.shared.dataTask(with: request) { data, response, error in + guard let data = data else { + completion(false) + return + } + let resp = String(data: data, encoding: .utf8)! + if resp.contains("Success"){ + completion(true) + }else{ + completion(false) + } + + } + task.resume() + } + +} diff --git a/lib/core/viewModels/project_view_model.dart b/lib/core/viewModels/project_view_model.dart index ea06c8ad..407c181b 100644 --- a/lib/core/viewModels/project_view_model.dart +++ b/lib/core/viewModels/project_view_model.dart @@ -4,11 +4,17 @@ import 'package:connectivity/connectivity.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/core/viewModels/base_view_model.dart'; import 'package:diplomaticquarterapp/locator.dart'; +import 'package:diplomaticquarterapp/uitl/PlatformBridge.dart'; import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; class ProjectViewModel extends BaseViewModel { + // Platform Bridge + PlatformBridge platformBridge() { + return PlatformBridge(); + } + AppSharedPreferences sharedPref = AppSharedPreferences(); Locale _appLocale; String currentLanguage = 'en'; @@ -21,7 +27,7 @@ class ProjectViewModel extends BaseViewModel { dynamic get searchValue => searchvalue; Locale get appLocal => _appLocale; - LocaleType get localeType => isArabic? LocaleType.en:LocaleType.ar; + LocaleType get localeType => isArabic ? LocaleType.en : LocaleType.ar; bool get isArabic => _isArabic; // BaseViewModel baseViewModel = locator() StreamSubscription subscription; @@ -51,7 +57,9 @@ class ProjectViewModel extends BaseViewModel { currentLanguage = await sharedPref.getString(APP_LANGUAGE); _appLocale = Locale(currentLanguage ?? 'en'); _isArabic = currentLanguage != null - ? currentLanguage == 'ar' ? true : false + ? currentLanguage == 'ar' + ? true + : false : true; notifyListeners(); } diff --git a/lib/main.dart b/lib/main.dart index 9cb5cb78..9597f72b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,12 @@ import 'core/viewModels/project_view_model.dart'; import 'locator.dart'; import 'package:diplomaticquarterapp/services/robo_search/event_provider.dart'; +@pragma('vm:entry-point') +void customMainDartMethod() { + setupLocator(); + runApp(MyApp()); +} + void main() async { setupLocator(); runApp(MyApp()); @@ -67,7 +73,8 @@ class MyApp extends StatelessWidget { hintColor: Colors.grey[400], disabledColor: Colors.grey[300], errorColor: Color.fromRGBO(235, 80, 60, 1.0), - scaffoldBackgroundColor: HexColor('#E9E9E9'), // Colors.grey[100], + scaffoldBackgroundColor: + HexColor('#E9E9E9'), // Colors.grey[100], textSelectionColor: Color.fromRGBO(80, 100, 253, 0.5), textSelectionHandleColor: Colors.grey, canvasColor: Colors.white, diff --git a/lib/pages/AlHabibMedicalService/h2o/widgets/h20_floating_action_button.dart b/lib/pages/AlHabibMedicalService/h2o/widgets/h20_floating_action_button.dart index 7eaed1a8..253530b2 100644 --- a/lib/pages/AlHabibMedicalService/h2o/widgets/h20_floating_action_button.dart +++ b/lib/pages/AlHabibMedicalService/h2o/widgets/h20_floating_action_button.dart @@ -12,21 +12,19 @@ import 'package:flutter/material.dart'; import '../add_custom_amount.dart'; class H20FloatingActionButton extends StatefulWidget { - const H20FloatingActionButton({ - Key key, - @required AnimationController controller, - @required this.model - - }) : - super(key: key); + const H20FloatingActionButton( + {Key key, @required AnimationController controller, @required this.model}) + : super(key: key); final H2OViewModel model; @override - _H20FloatingActionButtonState createState() => _H20FloatingActionButtonState(); + _H20FloatingActionButtonState createState() => + _H20FloatingActionButtonState(); } -class _H20FloatingActionButtonState extends State with TickerProviderStateMixin { +class _H20FloatingActionButtonState extends State + with TickerProviderStateMixin { AnimationController _controller; @override void initState() { @@ -39,9 +37,13 @@ class _H20FloatingActionButtonState extends State with @override Widget build(BuildContext context) { - void showConfirmMessage(int amount, H2OViewModel model) { - showDialog(context: context, child: ConfirmAddAmountDialog(model: model,amount:amount,)); + showDialog( + context: context, + child: ConfirmAddAmountDialog( + model: model, + amount: amount, + )); } return Container( @@ -184,16 +186,15 @@ class ActionButton extends StatelessWidget { curve: new Interval(0.0, 1.0 - 0 / 6 / 2.0, curve: Curves.easeOut), ), child: new FloatingActionButton( - heroTag: null, - backgroundColor: Colors.white, - mini: true, - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 14.0, color: Colors.grey), - ), - onPressed: onTap - ), + heroTag: null, + backgroundColor: Colors.white, + mini: true, + child: Text( + text, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14.0, color: Colors.grey), + ), + onPressed: onTap), ), ); } diff --git a/lib/pages/landing/landing_page.dart b/lib/pages/landing/landing_page.dart index aa8e25d1..49a5c547 100644 --- a/lib/pages/landing/landing_page.dart +++ b/lib/pages/landing/landing_page.dart @@ -123,7 +123,7 @@ class _LandingPageState extends State with WidgetsBindingObserver { AppGlobal.context = context; }); _requestIOSPermissions(); - pageController = PageController(keepPage: true); + pageController = PageController(keepPage: true); // _firebaseMessaging.setAutoInitEnabled(true); // // if (Platform.isIOS) { @@ -359,20 +359,22 @@ class _LandingPageState extends State with WidgetsBindingObserver { ), drawer: SafeArea(child: AppDrawer()), extendBody: true, - body: PageView( - physics: NeverScrollableScrollPhysics(), - controller: pageController, - children: [ - HomePage( - goToMyProfile: () { - _changeCurrentTab(1); - }, - ), - MedicalProfilePage(), - MyAdmissionsPage(), - ToDo(), - BookingOptions() - ], // Please do not remove the BookingOptions from this array + body: SafeArea( + child: PageView( + physics: NeverScrollableScrollPhysics(), + controller: pageController, + children: [ + HomePage( + goToMyProfile: () { + _changeCurrentTab(1); + }, + ), + MedicalProfilePage(), + MyAdmissionsPage(), + ToDo(), + BookingOptions() + ], // Please do not remove the BookingOptions from this array + ), ), bottomNavigationBar: BottomNavBar( changeIndex: _changeCurrentTab, diff --git a/lib/pages/medical/medical_profile_page.dart b/lib/pages/medical/medical_profile_page.dart index 61f22aca..c321fcd8 100644 --- a/lib/pages/medical/medical_profile_page.dart +++ b/lib/pages/medical/medical_profile_page.dart @@ -75,18 +75,19 @@ class _MedicalProfilePageState extends State { fit: BoxFit.cover, width: double.infinity, ), - if(model.authenticatedUserObject.isLogin) - ListView.builder( - itemBuilder: (context, index) => TimeLineWidget( - isUp: index % 2 == 1, - appoitmentAllHistoryResul: model - .appoitmentAllHistoryResultList[index], + if (model.authenticatedUserObject.isLogin) + ListView.builder( + itemBuilder: (context, index) => + TimeLineWidget( + isUp: index % 2 == 1, + appoitmentAllHistoryResul: model + .appoitmentAllHistoryResultList[index], + ), + itemCount: model + .appoitmentAllHistoryResultList.length, + scrollDirection: Axis.horizontal, + reverse: !projectViewModel.isArabic, ), - itemCount: - model.appoitmentAllHistoryResultList.length, - scrollDirection: Axis.horizontal, - reverse: !projectViewModel.isArabic, - ), ], ), ), @@ -192,8 +193,10 @@ class _MedicalProfilePageState extends State { Expanded( flex: 1, child: InkWell( - onTap: () => Navigator.push(context, - FadePage(page: ActiveMedicationsPage())), + onTap: () => Navigator.push( + context, + FadePage( + page: ActiveMedicationsPage())), child: MedicalProfileItem( title: TranslationBase.of(context) .myMedical, @@ -298,9 +301,11 @@ class _MedicalProfilePageState extends State { Expanded( flex: 1, child: InkWell( - onTap:()=> Navigator.push(context, FadePage(page: AllergiesPage())) , + onTap: () => Navigator.push(context, + FadePage(page: AllergiesPage())), child: MedicalProfileItem( - title: TranslationBase.of(context).allergies, + title: + TranslationBase.of(context).allergies, imagePath: 'my_allergies_icon.png', subTitle: TranslationBase.of(context) .allergiesSubtitle, @@ -408,10 +413,12 @@ class _MedicalProfilePageState extends State { flex: 1, child: InkWell( //TODO - onTap: () { - Navigator.push( - context, FadePage(page: SmartWatchInstructions())); - }, + onTap: () { + Navigator.push( + context, + FadePage( + page: SmartWatchInstructions())); + }, child: MedicalProfileItem( title: TranslationBase.of(context) .smartWatches, @@ -440,12 +447,13 @@ class _MedicalProfilePageState extends State { Expanded( flex: 1, child: InkWell( - onTap: (){ + onTap: () { Navigator.push(context, FadePage(page: AskDoctorHomPage())); }, child: MedicalProfileItem( - title: TranslationBase.of(context).askYour, + title: + TranslationBase.of(context).askYour, imagePath: 'ask_doctor_icon.png', subTitle: TranslationBase.of(context) .askYourSubtitle, @@ -457,11 +465,16 @@ class _MedicalProfilePageState extends State { Expanded( flex: 1, child: InkWell( - //TODO -// onTap: () { -// Navigator.push( -// context, FadePage(page: DoctorHomePage())); -// }, + onTap: () { + if (projectViewModel.user == null) { + projectViewModel + .platformBridge() + .connectHMGGuestWifi("1231755"); + } else { + Navigator.push(context, + FadePage(page: DoctorHomePage())); + } + }, child: MedicalProfileItem( title: TranslationBase.of(context).internet, diff --git a/lib/uitl/PlatformBridge.dart b/lib/uitl/PlatformBridge.dart new file mode 100644 index 00000000..e4e0b6d4 --- /dev/null +++ b/lib/uitl/PlatformBridge.dart @@ -0,0 +1,29 @@ +import 'package:flutter/services.dart'; + +class PlatformBridge { + static const platform = const MethodChannel("HMG-Platform-Bridge"); + + // Method Names + static const wifi_connect_method = "connectHMGGuestWifi"; + static const show_loading_method = "loading"; + + Future connectHMGGuestWifi(String patientId) { + print("Invoking platform method: $wifi_connect_method"); + try { + return platform.invokeMethod(wifi_connect_method, [patientId]); + } on PlatformException catch (e) { + print(e); + return Future.error(e); + } + } + + void showLoading(String message, bool show) async { + print("Invoking platform method: $show_loading_method"); + try { + final int result = + await platform.invokeMethod(show_loading_method, [message, show]); + } on PlatformException catch (e) { + print(e); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index f43d4d93..a9ea6ecf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,8 @@ description: A new Flutter application. version: 1.0.0+1 environment: - sdk: ">=2.6.0 <3.0.0" + sdk: ">=2.8.0 <3.0.0" #">=2.6.0 <3.0.0" + dependencies: flutter: