{
+ override fun build(errorCode: T): Matcher {
+ val matcher = matcherFactory(errorCode)
+ return object : Matcher {
+ override fun matches(throwable: Throwable): Boolean {
+ return matcher(throwable)
+ }
+ }
+ }
+ })
+ }
+
+ /**
+ * Bind an `errorCode` `Class` to a `Matcher`, using a `MatcherFactory`.
+ *
+ *
+ *
+ * For example, when we prefer using plain integers to refer to HTTP errors instead of
+ * checking the HTTPException status code every time.
+ *
+ *
+ *
+ * ```
+ * ErrorHandler
+ * .defaultErrorHandler()
+ * .bindClass(Integer::class) { errorCode ->
+ * Matcher { throwable ->
+ * return throwable is HttpException && throwable.code() == errorCode
+ * }
+ * }
+ *
+ * // ...
+ *
+ * ErrorHandler
+ * .create()
+ * .on(404) { throwable, handler ->
+ * showResourceNotFoundError()
+ * }
+ * .on(500) { throwable, handler ->
+ * showServerError()
+ * }
+ * ````
+ *
+ *
+ * @param the error code type
+ * @param errorCodeClass the errorCode class
+ * @param matcherFactory a factory that given an error code, provides a matcher to match the error against it
+ * @return the current `ErrorHandler` instance - to use in command chains
+ */
+ fun bindClass(
+ errorCodeClass: KClass,
+ matcherFactory: MatcherFactory
+ ): ErrorHandler {
+ errorCodeMap[ErrorCodeIdentifier(errorCodeClass)] = matcherFactory
+ return this
+ }
+
+ /**
+ * Kotlin <1.4 lambda compatibility for `[.bindClass(KClass, MatcherFactory)]`
+ */
+ fun bindClass(
+ errorCodeClass: KClass,
+ matcherFactory: (T) -> (Throwable) -> Boolean
+ ): ErrorHandler {
+ return bindClass(errorCodeClass, object : MatcherFactory {
+ override fun build(errorCode: T): Matcher {
+ val matcher = matcherFactory(errorCode)
+ return object : Matcher {
+ override fun matches(throwable: Throwable): Boolean {
+ return matcher(throwable)
+ }
+ }
+ }
+ })
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ protected fun getMatcherFactoryForErrorCode(errorCode: T): MatcherFactory? {
+ var matcherFactory: MatcherFactory?
+ matcherFactory = errorCodeMap[ErrorCodeIdentifier(errorCode)] as? MatcherFactory
+ if (matcherFactory != null) {
+ return matcherFactory
+ }
+
+ matcherFactory = errorCodeMap[ErrorCodeIdentifier(errorCode::class)] as? MatcherFactory
+ if (matcherFactory != null) {
+ return matcherFactory
+ }
+ return if (parentErrorHandler != null) {
+ parentErrorHandler?.getMatcherFactoryForErrorCode(errorCode)
+ } else null
+ }
+
+ /**
+ * Clear ErrorHandler instance from all its registered Actions and Matchers.
+ */
+ fun clear() {
+ actions.clear()
+ errorCodeMap.clear()
+ otherwiseActions.clear()
+ alwaysActions.clear()
+ localContext.get().clear()
+ }
+
+ private class Context {
+ private val keys = HashMap()
+ var handled = false
+ var skipDefaults = false
+ var skipFollowing = false
+ var skipAlways = false
+ operator fun get(key: Any?): Any? {
+ return keys[key]
+ }
+
+ fun put(key: String, value: Any): Any? {
+ return keys.put(key, value)
+ }
+
+ fun remove(key: Any?): Any? {
+ return keys.remove(key)
+ }
+
+ fun clear() {
+ keys.clear()
+ skipDefaults = false
+ skipFollowing = false
+ skipAlways = false
+ }
+ }
+
+ /**
+ * Used to identify an error code either by its "literal" value
+ * or by its Class.
+ *
+ *
+ * When using custom objects as error codes,
+ * make sure you implement [Object.equals] to allow ErrorHandler
+ * perform equality comparisons between instances.
+ */
+ private class ErrorCodeIdentifier {
+ private val errorCode: T?
+ private val errorCodeClass: KClass?
+
+ internal constructor(errorCode: T) {
+ this.errorCode = errorCode
+ this.errorCodeClass = null
+ }
+
+ internal constructor(errorCodeClass: KClass) {
+ this.errorCode = null
+ this.errorCodeClass = errorCodeClass
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || javaClass != other.javaClass) return false
+
+ val that = other as ErrorCodeIdentifier<*>
+
+ if (if (errorCode != null) errorCode != that.errorCode else that.errorCode != null) return false
+ return if (errorCodeClass != null) errorCodeClass == that.errorCodeClass else that.errorCodeClass == null
+ }
+
+ override fun hashCode(): Int {
+ var result = errorCode?.hashCode() ?: 0
+ result = 31 * result + (errorCodeClass?.hashCode() ?: 0)
+ return result
+ }
+ }
+
+ companion object {
+ private var defaultInstance: ErrorHandler? = null
+
+ /**
+ * Create a new @{link ErrorHandler}, isolated from the default one.
+ *
+ *
+ * In other words, designed to handle all errors by itself without delegating
+ * to the default error handler.
+ *
+ * @return returns a new `ErrorHandler` instance
+ */
+ @JvmStatic
+ fun createIsolated(): ErrorHandler {
+ return ErrorHandler()
+ }
+
+ /**
+ * Create a new @{link ErrorHandler}, that delegates to the default one, or the
+ * parent @{link ErrorHandler} passed in
+ *
+ * Any default actions, are always executed after the ones registered on this one.
+ *
+ * @param parentErrorHandler `ErrorHandler` to use as the parent
+ * @return returns a new `ErrorHandler` instance
+ */
+ @JvmStatic
+ fun create(parentErrorHandler: ErrorHandler? = null): ErrorHandler {
+ return ErrorHandler(parentErrorHandler ?: defaultErrorHandler())
+ }
+
+ /**
+ * Get the default @{link ErrorHandler}, a singleton object
+ * to which all other instances by default delegate to.
+ *
+ * @return the default @{link ErrorHandler} instance
+ */
+ @JvmStatic
+ @Synchronized
+ fun defaultErrorHandler(): ErrorHandler {
+ if (defaultInstance == null) {
+ defaultInstance =
+ ErrorHandler()
+ }
+ return defaultInstance!!
+ }
+ }
+}
+
+/**
+ * Wrapper around `[.on(KClass, Action)]` to allow action's `Throwable` parameter
+ * to be typed to the `Throwable` expected
+ */
+inline fun ErrorHandler.on(
+ noinline action: (T, ErrorHandler) -> Unit
+): ErrorHandler {
+ return on(T::class) { throwable, errorHandler ->
+ action(throwable as T, errorHandler)
+ }
+}
+
+/**
+ * Lazy `ErrorHandler` initializer which delegates to a parent, or the `defaultErrorHandler`
+ * if the parent is not supplied. Uses optional lambda function to add actions and bindings to
+ * the new `ErrorHandler`
+ *
+ * @param parentErrorHandler (optional) error handler to delegate default actions to
+ * @param apply (optional) apply function for adding actions and binding
+ * @return lazy initialized `ErrorHandler`
+ */
+inline fun errorHandler(
+ parentErrorHandler: ErrorHandler? = null,
+ noinline apply: (ErrorHandler.() -> Unit)? = null
+) = lazy {
+ val eh = ErrorHandler.create(parentErrorHandler)
+ apply?.invoke(eh)
+ return@lazy eh
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/ExceptionMatcher.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/ExceptionMatcher.kt
new file mode 100644
index 00000000..4ab02f8a
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/ExceptionMatcher.kt
@@ -0,0 +1,9 @@
+package com.hmg.hmgDr.errorhandler
+
+import kotlin.reflect.KClass
+
+class ExceptionMatcher(private val errorClass: KClass) : Matcher {
+ override fun matches(throwable: Throwable): Boolean {
+ return errorClass.isInstance(throwable)
+ }
+}
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/Matcher .kt b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/Matcher .kt
new file mode 100644
index 00000000..acf8b3c1
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/Matcher .kt
@@ -0,0 +1,5 @@
+package com.hmg.hmgDr.errorhandler
+
+interface Matcher {
+ fun matches(throwable: Throwable): Boolean
+}
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/MatcherFactory.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/MatcherFactory.kt
new file mode 100644
index 00000000..af296213
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/MatcherFactory.kt
@@ -0,0 +1,11 @@
+package com.hmg.hmgDr.errorhandler
+
+interface MatcherFactory {
+ /**
+ * Build a [Matcher] to match the given error code against an error
+ *
+ * @param errorCode the error code
+ * @return a new [Matcher]
+ */
+ fun build(errorCode: T): Matcher
+}
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/UnknownErrorCodeException.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/UnknownErrorCodeException.kt
new file mode 100644
index 00000000..46abc572
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/UnknownErrorCodeException.kt
@@ -0,0 +1,3 @@
+package com.hmg.hmgDr.errorhandler
+
+class UnknownErrorCodeException(val errorCode: Any) : RuntimeException()
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/retrofit/Range.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/retrofit/Range.kt
new file mode 100644
index 00000000..e6abc82e
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/retrofit/Range.kt
@@ -0,0 +1,45 @@
+package com.hmg.hmgDr.errorhandler.retrofit
+
+/**
+ * Range class for HTTP status codes
+ */
+class Range private constructor(val lowerBound: Int, val upperBound: Int) {
+
+ /**
+ * Checks if the passed httpStatusCode is contained in given range
+ *
+ * @param httpStatusCode the status code to check
+ * @return true if contains, otherwise false
+ */
+ operator fun contains(httpStatusCode: Int): Boolean {
+ return httpStatusCode in lowerBound..upperBound
+ }
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) return true
+ if (o == null || javaClass != o.javaClass) return false
+ val range =
+ o as Range
+ return if (lowerBound != range.lowerBound) false else upperBound == range.upperBound
+ }
+
+ override fun hashCode(): Int {
+ var result = lowerBound
+ result = 31 * result + upperBound
+ return result
+ }
+
+ companion object {
+ /**
+ * Creates a Range object with lower and upper bound
+ * @param lowerBound lower limit of Range
+ * @param upperBound upper limit of Range
+ *
+ * @return a Range instance
+ */
+ @JvmStatic
+ fun of(lowerBound: Int, upperBound: Int): Range {
+ return Range(lowerBound, upperBound)
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/retrofit/RetrofitMatcherFactory.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/retrofit/RetrofitMatcherFactory.kt
new file mode 100644
index 00000000..062fc30e
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/errorhandler/retrofit/RetrofitMatcherFactory.kt
@@ -0,0 +1,48 @@
+package com.hmg.hmgDr.errorhandler.retrofit
+
+import com.hmg.hmgDr.errorhandler.Matcher
+import com.hmg.hmgDr.errorhandler.MatcherFactory
+import retrofit2.adapter.rxjava.HttpException
+
+object RetrofitMatcherFactory {
+
+ /**
+ * Creates a [MatcherFactory] that checks HTTP statuses
+ *
+ * @return new MatcherFactory for Retrofit Rx HttpException that works with Integer
+ */
+ @JvmStatic
+ fun create(): MatcherFactory {
+ return object : MatcherFactory {
+ override fun build(errorCode: Int): Matcher {
+ return object : Matcher {
+ override fun matches(throwable: Throwable): Boolean {
+ return throwable is HttpException &&
+ throwable.code() == errorCode
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a [MatcherFactory] that checks if HTTP status is in given [Range]
+ *
+ * @return new MatcherFactory for Retrofit Rx HttpException that works with Range
+ */
+ @JvmStatic
+ fun createRange(): MatcherFactory {
+ return object : MatcherFactory {
+ override fun build(errorCode: Range): Matcher {
+ return object : Matcher {
+ override fun matches(throwable: Throwable): Boolean {
+ return throwable is HttpException &&
+ errorCode.contains(throwable.code())
+ }
+ }
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/FileUtil.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/FileUtil.kt
new file mode 100644
index 00000000..13fc2dd7
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/FileUtil.kt
@@ -0,0 +1,37 @@
+package com.hmg.hmgDr.globalErrorHandler
+
+import android.os.Environment
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.*
+
+
+object FileUtil {
+
+ val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
+
+ fun pushLog(body: String?) {
+ try {
+ val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
+ val time = SimpleDateFormat("HH:MM:SS", Locale.getDefault()).format(Date())
+ val root =
+ File(Environment.getExternalStorageDirectory(),"error_log_dir")
+ // if external memory exists and folder with name Notes
+ if (!root.exists()) {
+ root.mkdirs() // this will create folder.
+ }
+ val oldFile = File(root, "error" + sdf.format(Date()).toString() + ".txt") // old file
+ if (oldFile.exists()) oldFile.delete()
+ val filepath = File(root, "error$date.txt") // file path to save
+ val bufferedWriter = BufferedWriter(FileWriter(filepath, true))
+ bufferedWriter.append("\r\n")
+ bufferedWriter.append("\r\n").append(body).append(" Time : ").append(time)
+ bufferedWriter.flush()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/LoggingExceptionHandler.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/LoggingExceptionHandler.kt
new file mode 100644
index 00000000..cfbd26ca
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/LoggingExceptionHandler.kt
@@ -0,0 +1,39 @@
+package com.hmg.hmgDr.globalErrorHandler
+
+import android.content.Context
+import android.content.Intent
+import com.hmg.hmgDr.MainActivity
+import com.hmg.hmgDr.globalErrorHandler.FileUtil.pushLog
+
+
+class LoggingExceptionHandler(private val context: Context, ErrorFile: String) :
+ Thread.UncaughtExceptionHandler {
+ private val rootHandler: Thread.UncaughtExceptionHandler
+ override fun uncaughtException(t: Thread, e: Throwable) {
+ object : Thread() {
+ override fun run() {
+ pushLog("UnCaught Exception is thrown in $error$e")
+ try {
+ sleep(500)
+ val intent = Intent(context, MainActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+ context.startActivity(intent)
+ } catch (e1: Exception) {
+ e1.printStackTrace()
+ }
+ }
+ }.start()
+ rootHandler.uncaughtException(t, e)
+ }
+
+ companion object {
+ private val TAG = LoggingExceptionHandler::class.java.simpleName
+ lateinit var error: String
+ }
+
+ init {
+ error = "$ErrorFile.error "
+ rootHandler = Thread.getDefaultUncaughtExceptionHandler()
+ Thread.setDefaultUncaughtExceptionHandler(this)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEDefaultActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEDefaultActivity.kt
new file mode 100644
index 00000000..8ee4bbd0
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEDefaultActivity.kt
@@ -0,0 +1,6 @@
+package com.hmg.hmgDr.globalErrorHandler
+
+import androidx.appcompat.app.AppCompatActivity
+
+class UCEDefaultActivity : AppCompatActivity() {
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEFileProvider.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEFileProvider.kt
new file mode 100644
index 00000000..70376428
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEFileProvider.kt
@@ -0,0 +1,6 @@
+package com.hmg.hmgDr.globalErrorHandler
+
+import androidx.core.content.FileProvider
+
+class UCEFileProvider : FileProvider() {
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEHandler.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEHandler.kt
new file mode 100644
index 00000000..35654d1f
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/globalErrorHandler/UCEHandler.kt
@@ -0,0 +1,280 @@
+package com.hmg.hmgDr.globalErrorHandler
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.Date;
+import java.util.Deque;
+import java.util.Locale;
+import kotlin.system.exitProcess
+
+class UCEHandler(val builder: Builder) {
+
+ val EXTRA_STACK_TRACE = "EXTRA_STACK_TRACE"
+ val EXTRA_ACTIVITY_LOG = "EXTRA_ACTIVITY_LOG"
+ private val TAG = "UCEHandler"
+ private val UCE_HANDLER_PACKAGE_NAME = "com.rohitss.uceh"
+ private val DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os"
+ private val MAX_STACK_TRACE_SIZE = 131071 //128 KB - 1
+
+ private val MAX_ACTIVITIES_IN_LOG = 50
+ private val SHARED_PREFERENCES_FILE = "uceh_preferences"
+ private val SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp"
+ private val activityLog: Deque = ArrayDeque(MAX_ACTIVITIES_IN_LOG)
+ var COMMA_SEPARATED_EMAIL_ADDRESSES: String? = null
+
+ @SuppressLint("StaticFieldLeak")
+ private var application: Application? = null
+ private var isInBackground = true
+ private var isBackgroundMode = false
+ private var isUCEHEnabled = false
+ private var isTrackActivitiesEnabled = false
+ private var lastActivityCreated: WeakReference = WeakReference(null)
+
+ fun UCEHandler(builder: Builder) {
+ isUCEHEnabled = builder.isUCEHEnabled
+ isTrackActivitiesEnabled = builder.isTrackActivitiesEnabled
+ isBackgroundMode = builder.isBackgroundModeEnabled
+ COMMA_SEPARATED_EMAIL_ADDRESSES = builder.commaSeparatedEmailAddresses
+ setUCEHandler(builder.context)
+ }
+
+ private fun setUCEHandler(context: Context?) {
+ try {
+ if (context != null) {
+ val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
+ if (oldHandler != null && oldHandler.javaClass.name.startsWith(
+ UCE_HANDLER_PACKAGE_NAME
+ )
+ ) {
+ Log.e(TAG, "UCEHandler was already installed, doing nothing!")
+ } else {
+ if (oldHandler != null && !oldHandler.javaClass.name.startsWith(
+ DEFAULT_HANDLER_PACKAGE_NAME
+ )
+ ) {
+ Log.e(
+ TAG,
+ "You already have an UncaughtExceptionHandler. If you use a custom UncaughtExceptionHandler, it should be initialized after UCEHandler! Installing anyway, but your original handler will not be called."
+ )
+ }
+ application = context.getApplicationContext() as Application
+ //Setup UCE Handler.
+ Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler { thread, throwable ->
+ if (isUCEHEnabled) {
+ Log.e(
+ TAG,
+ "App crashed, executing UCEHandler's UncaughtExceptionHandler",
+ throwable
+ )
+ if (hasCrashedInTheLastSeconds(application!!)) {
+ Log.e(
+ TAG,
+ "App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?",
+ throwable
+ )
+ if (oldHandler != null) {
+ oldHandler.uncaughtException(thread, throwable)
+ return@UncaughtExceptionHandler
+ }
+ } else {
+ setLastCrashTimestamp(application!!, Date().getTime())
+ if (!isInBackground || isBackgroundMode) {
+ val intent = Intent(application, UCEDefaultActivity::class.java)
+ val sw = StringWriter()
+ val pw = PrintWriter(sw)
+ throwable.printStackTrace(pw)
+ var stackTraceString: String = sw.toString()
+ if (stackTraceString.length > MAX_STACK_TRACE_SIZE) {
+ val disclaimer = " [stack trace too large]"
+ stackTraceString = stackTraceString.substring(
+ 0,
+ MAX_STACK_TRACE_SIZE - disclaimer.length
+ ) + disclaimer
+ }
+ intent.putExtra(EXTRA_STACK_TRACE, stackTraceString)
+ if (isTrackActivitiesEnabled) {
+ val activityLogStringBuilder = StringBuilder()
+ while (!activityLog.isEmpty()) {
+ activityLogStringBuilder.append(activityLog.poll())
+ }
+ intent.putExtra(
+ EXTRA_ACTIVITY_LOG,
+ activityLogStringBuilder.toString()
+ )
+ }
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ application!!.startActivity(intent)
+ } else {
+ if (oldHandler != null) {
+ oldHandler.uncaughtException(thread, throwable)
+ return@UncaughtExceptionHandler
+ }
+ //If it is null (should not be), we let it continue and kill the process or it will be stuck
+ }
+ }
+ val lastActivity: Activity? = lastActivityCreated.get()
+ if (lastActivity != null) {
+ lastActivity.finish()
+ lastActivityCreated.clear()
+ }
+ killCurrentProcess()
+ } else oldHandler?.uncaughtException(thread, throwable)
+ })
+ application!!.registerActivityLifecycleCallbacks(object :
+ Application.ActivityLifecycleCallbacks {
+ val dateFormat: DateFormat =
+ SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
+ var currentlyStartedActivities = 0
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ if (activity.javaClass !== UCEDefaultActivity::class.java) {
+ lastActivityCreated = WeakReference(activity)
+ }
+ if (isTrackActivitiesEnabled) {
+ activityLog.add(
+ dateFormat.format(Date())
+ .toString() + ": " + activity.javaClass
+ .getSimpleName() + " created\n"
+ )
+ }
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ currentlyStartedActivities++
+ isInBackground = currentlyStartedActivities == 0
+ }
+
+ override fun onActivityResumed(activity: Activity) {
+ if (isTrackActivitiesEnabled) {
+ activityLog.add(
+ dateFormat.format(Date())
+ .toString() + ": " + activity.javaClass
+ .simpleName + " resumed\n"
+ )
+ }
+ }
+
+ override fun onActivityPaused(activity: Activity) {
+ if (isTrackActivitiesEnabled) {
+ activityLog.add(
+ dateFormat.format(Date())
+ .toString() + ": " + activity.javaClass
+ .simpleName + " paused\n"
+ )
+ }
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ currentlyStartedActivities--
+ isInBackground = currentlyStartedActivities == 0
+ }
+
+ override fun onActivitySaveInstanceState(
+ activity: Activity,
+ outState: Bundle
+ ) {}
+ override fun onActivityDestroyed(activity: Activity) {
+ if (isTrackActivitiesEnabled) {
+ activityLog.add(
+ dateFormat.format(Date())
+ .toString() + ": " + activity.javaClass
+ .simpleName + " destroyed\n"
+ )
+ }
+ }
+ })
+ }
+ Log.i(TAG, "UCEHandler has been installed.")
+ } else {
+ Log.e(TAG, "Context can not be null")
+ }
+ } catch (throwable: Throwable) {
+ Log.e(
+ TAG,
+ "UCEHandler can not be initialized. Help making it better by reporting this as a bug.",
+ throwable
+ )
+ }
+ }
+
+ /**
+ * INTERNAL method that tells if the app has crashed in the last seconds.
+ * This is used to avoid restart loops.
+ *
+ * @return true if the app has crashed in the last seconds, false otherwise.
+ */
+ private fun hasCrashedInTheLastSeconds(context: Context): Boolean {
+ val lastTimestamp = getLastCrashTimestamp(context)
+ val currentTimestamp: Long = Date().getTime()
+ return lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < 3000
+ }
+
+ @SuppressLint("ApplySharedPref")
+ private fun setLastCrashTimestamp(context: Context, timestamp: Long) {
+ context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).edit()
+ .putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp).commit()
+ }
+
+ private fun killCurrentProcess() {
+// Process.killProcess(Process.myPid())
+ exitProcess(10)
+ }
+
+ private fun getLastCrashTimestamp(context: Context): Long {
+ return context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
+ .getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1)
+ }
+
+ fun closeApplication(activity: Activity) {
+ activity.finish()
+ killCurrentProcess()
+ }
+
+ inner class Builder(context: Context) {
+ val context: Context
+ var isUCEHEnabled = true
+ var commaSeparatedEmailAddresses: String? = null
+ var isTrackActivitiesEnabled = false
+ var isBackgroundModeEnabled = true
+ fun setUCEHEnabled(isUCEHEnabled: Boolean): Builder {
+ this.isUCEHEnabled = isUCEHEnabled
+ return this
+ }
+
+ fun setTrackActivitiesEnabled(isTrackActivitiesEnabled: Boolean): Builder {
+ this.isTrackActivitiesEnabled = isTrackActivitiesEnabled
+ return this
+ }
+
+ fun setBackgroundModeEnabled(isBackgroundModeEnabled: Boolean): Builder {
+ this.isBackgroundModeEnabled = isBackgroundModeEnabled
+ return this
+ }
+
+ fun addCommaSeparatedEmailAddresses(commaSeparatedEmailAddresses: String?): Builder {
+ this.commaSeparatedEmailAddresses = commaSeparatedEmailAddresses ?: ""
+ return this
+ }
+
+ fun build() {
+ return UCEHandler(this)
+ }
+
+ init {
+ this.context = context
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/util/audio/CustomAudioDevice.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/util/audio/CustomAudioDevice.kt
new file mode 100644
index 00000000..ef654844
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/util/audio/CustomAudioDevice.kt
@@ -0,0 +1,467 @@
+package com.hmg.hmgDr.util.audio
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioFormat
+import android.media.AudioManager
+import android.media.AudioRecord
+import android.media.AudioTrack
+import android.media.MediaRecorder.AudioSource
+import android.os.Process
+import android.util.Log
+
+import com.opentok.android.BaseAudioDevice
+
+import java.nio.ByteBuffer
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+
+class CustomAudioDevice(context: Context) : BaseAudioDevice() {
+
+ private val m_context: Context = context
+ private var m_audioTrack: AudioTrack? = null
+ private var m_audioRecord: AudioRecord? = null
+
+ // Capture & render buffers
+ private var m_playBuffer: ByteBuffer? = null
+ private var m_recBuffer: ByteBuffer? = null
+ private val m_tempBufPlay: ByteArray
+ private val m_tempBufRec: ByteArray
+ private val m_rendererLock: ReentrantLock = ReentrantLock(true)
+ private val m_renderEvent: Condition = m_rendererLock.newCondition()
+
+ @Volatile
+ private var m_isRendering = false
+
+ @Volatile
+ private var m_shutdownRenderThread = false
+ private val m_captureLock: ReentrantLock = ReentrantLock(true)
+ private val m_captureEvent: Condition = m_captureLock.newCondition()
+
+ @Volatile
+ private var m_isCapturing = false
+
+ @Volatile
+ private var m_shutdownCaptureThread = false
+ private val m_captureSettings: AudioSettings
+ private val m_rendererSettings: AudioSettings
+
+ // Capturing delay estimation
+ private var m_estimatedCaptureDelay = 0
+
+ // Rendering delay estimation
+ private var m_bufferedPlaySamples = 0
+ private var m_playPosition = 0
+ private var m_estimatedRenderDelay = 0
+ private val m_audioManager: AudioManager
+ private var isRendererMuted = false
+
+ companion object {
+ private const val LOG_TAG = "opentok-defaultaudio"
+ private const val SAMPLING_RATE = 44100
+ private const val NUM_CHANNELS_CAPTURING = 1
+ private const val NUM_CHANNELS_RENDERING = 1
+ private const val MAX_SAMPLES = 2 * 480 * 2 // Max 10 ms @ 48 kHz
+ }
+
+ init {
+ try {
+ m_playBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
+ m_recBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "${e.message}.")
+ }
+ m_tempBufPlay = ByteArray(MAX_SAMPLES)
+ m_tempBufRec = ByteArray(MAX_SAMPLES)
+ m_captureSettings = AudioSettings(
+ SAMPLING_RATE,
+ NUM_CHANNELS_CAPTURING
+ )
+ m_rendererSettings = AudioSettings(
+ SAMPLING_RATE,
+ NUM_CHANNELS_RENDERING
+ )
+ m_audioManager = m_context
+ .getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ m_audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
+ }
+
+ override fun initCapturer(): Boolean {
+
+ // get the minimum buffer size that can be used
+ val minRecBufSize: Int = AudioRecord.getMinBufferSize(
+ m_captureSettings
+ .sampleRate,
+ if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
+ AudioFormat.ENCODING_PCM_16BIT
+ )
+
+ // double size to be more safe
+ val recBufSize = minRecBufSize * 2
+
+ // release the object
+ if (m_audioRecord != null) {
+ m_audioRecord!!.release()
+ m_audioRecord = null
+ }
+ try {
+ m_audioRecord = AudioRecord(
+ AudioSource.VOICE_COMMUNICATION,
+ m_captureSettings.sampleRate,
+ if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
+ AudioFormat.ENCODING_PCM_16BIT, recBufSize
+ )
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "${e.message}.")
+ return false
+ }
+
+ // check that the audioRecord is ready to be used
+ if (m_audioRecord!!.state != AudioRecord.STATE_INITIALIZED) {
+ Log.i(
+ LOG_TAG, "Audio capture is not initialized "
+ + m_captureSettings.sampleRate
+ )
+ return false
+ }
+ m_shutdownCaptureThread = false
+ Thread(m_captureThread).start()
+ return true
+ }
+
+ override fun destroyCapturer(): Boolean {
+ m_captureLock.lock()
+ // release the object
+ m_audioRecord?.release()
+ m_audioRecord = null
+ m_shutdownCaptureThread = true
+ m_captureEvent.signal()
+ m_captureLock.unlock()
+ return true
+ }
+
+ override fun getEstimatedCaptureDelay(): Int {
+ return m_estimatedCaptureDelay
+ }
+
+ override fun startCapturer(): Boolean {
+ // start recording
+ try {
+ m_audioRecord!!.startRecording()
+ } catch (e: IllegalStateException) {
+ e.printStackTrace()
+ return false
+ }
+ m_captureLock.lock()
+ m_isCapturing = true
+ m_captureEvent.signal()
+ m_captureLock.unlock()
+ return true
+ }
+
+ override fun stopCapturer(): Boolean {
+ m_captureLock.lock()
+ try {
+ // only stop if we are recording
+ if (m_audioRecord!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
+ // stop recording
+ try {
+ m_audioRecord!!.stop()
+ } catch (e: IllegalStateException) {
+ e.printStackTrace()
+ return false
+ }
+ }
+ } finally {
+ // Ensure we always unlock
+ m_isCapturing = false
+ m_captureLock.unlock()
+ }
+ return true
+ }
+
+ private val m_captureThread = Runnable {
+ val samplesToRec = SAMPLING_RATE / 100
+ var samplesRead = 0
+ try {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ while (!m_shutdownCaptureThread) {
+ m_captureLock.lock()
+ samplesRead = try {
+ if (!m_isCapturing) {
+ m_captureEvent.await()
+ continue
+ } else {
+ if (m_audioRecord == null) {
+ continue
+ }
+ val lengthInBytes = ((samplesToRec shl 1)
+ * NUM_CHANNELS_CAPTURING)
+ val readBytes: Int = m_audioRecord!!.read(
+ m_tempBufRec, 0,
+ lengthInBytes
+ )
+ m_recBuffer!!.rewind()
+ m_recBuffer!!.put(m_tempBufRec)
+ (readBytes shr 1) / NUM_CHANNELS_CAPTURING
+ }
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "RecordAudio try failed: " + e.message)
+ continue
+ } finally {
+ // Ensure we always unlock
+ m_captureLock.unlock()
+ }
+ audioBus.writeCaptureData(m_recBuffer, samplesRead)
+ m_estimatedCaptureDelay = samplesRead * 1000 / SAMPLING_RATE
+ }
+ }
+
+ override fun initRenderer(): Boolean {
+
+ // get the minimum buffer size that can be used
+ val minPlayBufSize: Int = AudioTrack.getMinBufferSize(
+ m_rendererSettings
+ .sampleRate,
+ if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
+ AudioFormat.ENCODING_PCM_16BIT
+ )
+ var playBufSize = minPlayBufSize
+ if (playBufSize < 6000) {
+ playBufSize *= 2
+ }
+
+ // release the object
+ if (m_audioTrack != null) {
+ m_audioTrack!!.release()
+ m_audioTrack = null
+ }
+ try {
+ m_audioTrack = AudioTrack(
+ AudioManager.STREAM_VOICE_CALL,
+ m_rendererSettings.sampleRate,
+ if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
+ AudioFormat.ENCODING_PCM_16BIT, playBufSize,
+ AudioTrack.MODE_STREAM
+ )
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "${e.message}.")
+ return false
+ }
+
+ // check that the audioRecord is ready to be used
+ if (m_audioTrack!!.state != AudioTrack.STATE_INITIALIZED) {
+ Log.i(
+ LOG_TAG, "Audio renderer not initialized "
+ + m_rendererSettings.sampleRate
+ )
+ return false
+ }
+ m_bufferedPlaySamples = 0
+ setOutputMode(OutputMode.SpeakerPhone)
+ m_shutdownRenderThread = false
+ Thread(m_renderThread).start()
+ return true
+ }
+
+ override fun destroyRenderer(): Boolean {
+ m_rendererLock.lock()
+ // release the object
+ m_audioTrack!!.release()
+ m_audioTrack = null
+ m_shutdownRenderThread = true
+ m_renderEvent.signal()
+ m_rendererLock.unlock()
+ unregisterHeadsetReceiver()
+ m_audioManager.isSpeakerphoneOn = false
+ m_audioManager.mode = AudioManager.MODE_NORMAL
+ return true
+ }
+
+ override fun getEstimatedRenderDelay(): Int {
+ return m_estimatedRenderDelay
+ }
+
+ override fun startRenderer(): Boolean {
+ // start playout
+ try {
+ m_audioTrack!!.play()
+ } catch (e: IllegalStateException) {
+ e.printStackTrace()
+ return false
+ }
+ m_rendererLock.lock()
+ m_isRendering = true
+ m_renderEvent.signal()
+ m_rendererLock.unlock()
+ return true
+ }
+
+ override fun stopRenderer(): Boolean {
+ m_rendererLock.lock()
+ try {
+ // only stop if we are playing
+ if (m_audioTrack!!.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
+ // stop playout
+ try {
+ m_audioTrack!!.stop()
+ } catch (e: IllegalStateException) {
+ e.printStackTrace()
+ return false
+ }
+
+ // flush the buffers
+ m_audioTrack!!.flush()
+ }
+ } finally {
+ // Ensure we always unlock, both for success, exception or error
+ // return.
+ m_isRendering = false
+ m_rendererLock.unlock()
+ }
+ return true
+ }
+
+ private val m_renderThread = Runnable {
+ val samplesToPlay = SAMPLING_RATE / 100
+ try {
+ Process
+ .setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ while (!m_shutdownRenderThread) {
+ m_rendererLock.lock()
+ try {
+ if (!m_isRendering) {
+ m_renderEvent.await()
+ continue
+ } else {
+ m_rendererLock.unlock()
+
+ // Don't lock on audioBus calls
+ m_playBuffer!!.clear()
+ val samplesRead: Int = audioBus.readRenderData(
+ m_playBuffer, samplesToPlay
+ )
+
+ // Log.d(LOG_TAG, "Samples read: " + samplesRead);
+ m_rendererLock.lock()
+ if (!isRendererMuted) {
+ // After acquiring the lock again
+ // we must check if we are still playing
+ if (m_audioTrack == null
+ || !m_isRendering
+ ) {
+ continue
+ }
+ val bytesRead = ((samplesRead shl 1)
+ * NUM_CHANNELS_RENDERING)
+ m_playBuffer!!.get(m_tempBufPlay, 0, bytesRead)
+ val bytesWritten: Int = m_audioTrack!!.write(
+ m_tempBufPlay, 0,
+ bytesRead
+ )
+
+ // increase by number of written samples
+ m_bufferedPlaySamples += ((bytesWritten shr 1)
+ / NUM_CHANNELS_RENDERING)
+
+ // decrease by number of played samples
+ val pos: Int = m_audioTrack!!.getPlaybackHeadPosition()
+ if (pos < m_playPosition) {
+ // wrap or reset by driver
+ m_playPosition = 0
+ }
+ m_bufferedPlaySamples -= pos - m_playPosition
+ m_playPosition = pos
+
+ // we calculate the estimated delay based on the
+ // buffered samples
+ m_estimatedRenderDelay = (m_bufferedPlaySamples * 1000
+ / SAMPLING_RATE)
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Exception: " + e.message)
+ e.printStackTrace()
+ } finally {
+ m_rendererLock.unlock()
+ }
+ }
+ }
+
+ override fun getCaptureSettings(): AudioSettings {
+ return m_captureSettings
+ }
+
+ override fun getRenderSettings(): AudioSettings {
+ return m_rendererSettings
+ }
+
+ /**
+ * Communication modes handling
+ */
+ override fun setOutputMode(mode: OutputMode): Boolean {
+ super.setOutputMode(mode)
+ if (mode == OutputMode.Handset) {
+ unregisterHeadsetReceiver()
+ m_audioManager.isSpeakerphoneOn = false
+ } else {
+ m_audioManager.isSpeakerphoneOn = true
+ registerHeadsetReceiver()
+ }
+ return true
+ }
+
+ private val m_headsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent) {
+ if (intent.action!!.compareTo(Intent.ACTION_HEADSET_PLUG) == 0) {
+ val state: Int = intent.getIntExtra("state", 0)
+ m_audioManager.isSpeakerphoneOn = state == 0
+ }
+ }
+ }
+ private var m_receiverRegistered = false
+ private fun registerHeadsetReceiver() {
+ if (!m_receiverRegistered) {
+ val receiverFilter = IntentFilter(
+ Intent.ACTION_HEADSET_PLUG
+ )
+ m_context.registerReceiver(m_headsetReceiver, receiverFilter)
+ m_receiverRegistered = true
+ }
+ }
+
+ private fun unregisterHeadsetReceiver() {
+ if (m_receiverRegistered) {
+ try {
+ m_context.unregisterReceiver(m_headsetReceiver)
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ }
+ m_receiverRegistered = false
+ }
+ }
+
+ override fun onPause() {
+ if (outputMode == OutputMode.SpeakerPhone) {
+ unregisterHeadsetReceiver()
+ }
+ }
+
+ override fun onResume() {
+ if (outputMode == OutputMode.SpeakerPhone) {
+ registerHeadsetReceiver()
+ }
+ }
+
+ fun setRendererMute(isRendererMuted: Boolean) {
+ this.isRendererMuted = isRendererMuted
+ }
+}
diff --git a/android/app/src/main/res/layout/default_error_activity.xml b/android/app/src/main/res/layout/default_error_activity.xml
new file mode 100644
index 00000000..21af70f2
--- /dev/null
+++ b/android/app/src/main/res/layout/default_error_activity.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index bc694f7c..ed85970e 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -4,7 +4,6 @@
الوقت المتبقي بالثانيه:
Settings
Cancel
-
- Hello blank fragment
+ An unexpected error has occurred.\nHelp developers by providing error details.\nThank you for your support.
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/provider_paths.xml b/android/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 00000000..8d13fa17
--- /dev/null
+++ b/android/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart
index 73c31f91..0f8f0061 100644
--- a/lib/core/service/VideoCallService.dart
+++ b/lib/core/service/VideoCallService.dart
@@ -51,12 +51,14 @@ class VideoCallService extends BaseService {
onCallConnected: onCallConnected,
onCallDisconnected: onCallDisconnected,
onCallEnd: () {
- WidgetsBinding.instance.addPostFrameCallback((_) async {
+ // mosa todo uncomment
+ /* WidgetsBinding.instance.addPostFrameCallback((_) async {
GifLoaderDialogUtils.showMyDialog(
locator().navigatorKey.currentContext);
endCall(
patient.vcId,
false,
+
).then((value) {
GifLoaderDialogUtils.hideDialog(
locator().navigatorKey.currentContext);
@@ -68,7 +70,7 @@ class VideoCallService extends BaseService {
"patient": patient,
});
});
- });
+ });*/
},
onCallNotRespond: (SessionStatusModel sessionStatusModel) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -94,16 +96,17 @@ class VideoCallService extends BaseService {
}
Future endCall(int vCID, bool isPatient) async {
- hasError = false;
- await getDoctorProfile(isGetProfile: true);
- EndCallReq endCallReq = new EndCallReq();
- endCallReq.doctorId = doctorProfile.doctorID;
- endCallReq.generalid = 'Cs2020@2016\$2958';
- endCallReq.vCID = vCID;
- endCallReq.isDestroy = isPatient;
- await _liveCarePatientServices.endCall(endCallReq);
- if (_liveCarePatientServices.hasError) {
- error = _liveCarePatientServices.error;
- }
+ // mosa todo uncomment
+ // hasError = false;
+ // await getDoctorProfile(isGetProfile: true);
+ // EndCallReq endCallReq = new EndCallReq();
+ // endCallReq.doctorId = doctorProfile.doctorID;
+ // endCallReq.generalid = 'Cs2020@2016\$2958';
+ // endCallReq.vCID = vCID;
+ // endCallReq.isDestroy = isPatient;
+ // await _liveCarePatientServices.endCall(endCallReq);
+ // if (_liveCarePatientServices.hasError) {
+ // error = _liveCarePatientServices.error;
+ // }
}
}
diff --git a/lib/core/viewModel/authentication_view_model.dart b/lib/core/viewModel/authentication_view_model.dart
index cae415da..5ef5bf70 100644
--- a/lib/core/viewModel/authentication_view_model.dart
+++ b/lib/core/viewModel/authentication_view_model.dart
@@ -255,7 +255,7 @@ class AuthenticationViewModel extends BaseViewModel {
/// add token to shared preferences in case of send activation code is success
setDataAfterSendActivationSuccess(SendActivationCodeForDoctorAppResponseModel sendActivationCodeForDoctorAppResponseModel) {
print("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
- // DrAppToastMsg.showSuccesToast("_VerificationCode_ : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
+ // DrAppToastMsg.showSuccesToast("mosa_VerificationCode_ : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
sharedPref.setString(VIDA_AUTH_TOKEN_ID,
sendActivationCodeForDoctorAppResponseModel.vidaAuthTokenID);
sharedPref.setString(VIDA_REFRESH_TOKEN_ID,