Ver Fonte

refractor 优化Fusion相关逻辑

RobinTan1024 há 4 anos atrás
pai
commit
a2d29ecd8c
27 ficheiros alterados com 668 adições e 397 exclusões
  1. 6 1
      app/build.gradle
  2. 0 9
      app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionError.kt
  3. 96 79
      app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionManager.kt
  4. 7 0
      app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionStatus.kt
  5. 0 10
      app/src/main/java/com/doverfuelingsolutions/issp/fusion/callback/OnFusionEvent.kt
  6. 8 0
      app/src/main/java/com/doverfuelingsolutions/issp/fusion/callback/OnFusionStatus.kt
  7. 1 1
      app/src/main/java/com/doverfuelingsolutions/issp/utils/WindowUtil.kt
  8. 55 62
      app/src/main/java/com/doverfuelingsolutions/issp/view/MainActivity.kt
  9. 11 8
      app/src/main/java/com/doverfuelingsolutions/issp/view/PreferenceActivity.kt
  10. 0 60
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentAction.kt
  11. 6 37
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentBasic.kt
  12. 1 1
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentLoading.kt
  13. 1 1
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentNozzle.kt
  14. 11 12
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentOrderList.kt
  15. 2 3
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPayScanCode.kt
  16. 9 6
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPayType.kt
  17. 0 6
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPreferenceWrapper.kt
  18. 46 0
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentReconnect.kt
  19. 43 31
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentSelect.kt
  20. 36 0
      app/src/main/java/com/doverfuelingsolutions/issp/view/util/LoadingDialogBuilder.kt
  21. 12 13
      app/src/main/java/com/doverfuelingsolutions/issp/view/widget/FragmentToolbar.kt
  22. 243 0
      app/src/main/res/drawable/ic_network_error.xml
  23. 0 43
      app/src/main/res/layout/fragment_action.xml
  24. 41 0
      app/src/main/res/layout/fragment_reconnect.xml
  25. 2 1
      app/src/main/res/layout/layout_loading.xml
  26. 27 12
      app/src/main/res/values/strings.xml
  27. 4 1
      waynelib_/src/main/java/com/wayne/www/waynelib/fdc/FdcClient.java

+ 6 - 1
app/build.gradle

@@ -43,7 +43,6 @@ dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     implementation 'androidx.core:core-ktx:1.3.2'
     implementation 'androidx.appcompat:appcompat:1.2.0'
-    implementation "androidx.fragment:fragment-ktx:1.3.0-beta02"
     implementation 'com.google.android.material:material:1.2.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
     implementation 'androidx.preference:preference-ktx:1.1.1'
@@ -63,6 +62,12 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
 
+    def lifecycleVersion = '2.2.0'
+    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
+    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
+    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
+
+
     def work_version = "2.4.0"
     implementation "androidx.work:work-runtime-ktx:$work_version"
 

+ 0 - 9
app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionError.kt

@@ -1,9 +0,0 @@
-package com.doverfuelingsolutions.issp.fusion
-
-enum class FusionError(val value: Int) {
-    Success(0),
-    Timeout(1),
-    Login(2),
-    GetNozzleInfo(3),
-    WrongAddress(4),
-}

+ 96 - 79
app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionManager.kt

@@ -8,7 +8,7 @@ import com.doverfuelingsolutions.issp.api.FuelInfoApi
 import com.doverfuelingsolutions.issp.api.dto.DFSResult
 import com.doverfuelingsolutions.issp.api.entity.PumpInfo
 import com.doverfuelingsolutions.issp.data.GlobalData
-import com.doverfuelingsolutions.issp.fusion.callback.OnFusionEvent
+import com.doverfuelingsolutions.issp.fusion.callback.OnFusionStatus
 import com.doverfuelingsolutions.issp.utils.StringUtil
 import com.doverfuelingsolutions.issp.utils.thread.ThreadUtil
 import com.doverfuelingsolutions.issp.utils.ValidateUtil
@@ -29,58 +29,81 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
     OnFdcServiceResponseReceivedListener,
     OnFdcMessageReceivedListener {
 
+    private var isInitialized = false
     var stateFusion: FdcClient.FdcClientState = FdcClient.FdcClientState.Stopped
-    private const val timeoutMax = 8000
-    var firstLink = false
+    private const val timeoutMax = 4000
+    private var reconnectTime = System.currentTimeMillis()
+
+    var isLogin = false
+        private set
+    var isFetchPumpInfo = false
         private set
-    private var isLogin = false
     val pumpList = arrayListOf<PumpInfo>()
 
     private val coroutineIO = CoroutineScope(Dispatchers.IO)
 
-    var onFusionEvent: OnFusionEvent? = null
+    var onFusionStatus: OnFusionStatus? = null
 
     override fun onFdcClientStateChanged(sender: FdcClient?, state: FdcClient.FdcClientState?) {
-        DFSLog.d("Fusion: state = ${state?.name?.toLowerCase(Locale.CHINA)}")
+        DFSLog.d("Fusion: state = ${state?.name?.toLowerCase(Locale.CHINESE)}")
         if (sender == null || state == null) return
 
         stateFusion = state
         when (state) {
             FdcClient.FdcClientState.Connected -> {
-                firstLink = true
-                loginFetchInfo()
+                onFusionStatus?.onFusionStatus(FusionStatus.Connected)
+                coroutineIO.launch {
+                    val deferred1 = async { loginToFusion() }
+                    val deferred2 = async { getPumpInfo() }
+                    deferred1.await()
+                    deferred2.await()
+                }
+            }
+            FdcClient.FdcClientState.Connecting -> {
+                onFusionStatus?.onFusionStatus(FusionStatus.Connecting)
+            }
+            FdcClient.FdcClientState.DisconnectedByHeartbeat -> {
+                onFusionStatus?.onFusionStatus(FusionStatus.Connecting)
+                coroutineIO.launch {
+                    // Try restart if current client remains Connecting or DisconnectedByHeartbeat after delay.
+                    delay(2000)
+                    if (stateFusion == FdcClient.FdcClientState.Connecting || stateFusion == FdcClient.FdcClientState.DisconnectedByHeartbeat) {
+                        // Result of restart has to be MyAddReConnect or Connected so the custom Connecting status triggered above can't last forever.
+                        restart()
+                    }
+                }
+            }
+            FdcClient.FdcClientState.Stopped -> {
+                // Try restart if client is not stopped manually.
+                if (isInitialized) {
+                    onFusionStatus?.onFusionStatus(FusionStatus.Connecting)
+                    // Result of restart has to be MyAddReConnect or Connected.
+                    restart()
+                }
             }
-            // 中途断网重连
-            FdcClient.FdcClientState.Connecting -> onFusionEvent?.onFusionReconnect()
             FdcClient.FdcClientState.MyAddReConnect -> {
-                if (!firstLink) {
-                    onFusionEvent?.onFusionInit(
-                        FusionError.WrongAddress,
-                        StringUtil.get(R.string.wrong_fusion_address)
-                    )
+                // Status MyAddReConnect means client socket has been closed.
+                // Restart might or might not work.
+                // Prevent infinite loop cause restart could also triggered MyAddReConnect.
+                val time = System.currentTimeMillis()
+                if (time - reconnectTime > 20_000) {
+                    onFusionStatus?.onFusionStatus(FusionStatus.Connecting)
+                    reconnectTime = time
+                    restart()
                 } else {
-                    onFusionEvent?.onFusionReconnect()
+                    onFusionStatus?.onFusionStatus(FusionStatus.Disconnected)
                 }
             }
-            else -> {
-            }
         }
     }
 
-    override fun onServiceResponseReceived(sender: FdcClient?, serviceResponse: ServiceResponse?) {
-        if (sender == null || serviceResponse == null) return
-
-        // DFSLog.v("onServiceResponseReceived")
-    }
-
-    override fun onFdcMessageReceived(sender: FdcClient?, fdcMessage: FdcMessage?) {
-        if (sender == null || fdcMessage == null) return
+    override fun onServiceResponseReceived(sender: FdcClient?, serviceResponse: ServiceResponse?) {}
 
-        // DFSLog.v("onFdcMessageReceived")
-    }
+    override fun onFdcMessageReceived(sender: FdcClient?, fdcMessage: FdcMessage?) {}
 
     @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
     fun initialize() {
+        isInitialized = true
         FdcClient.getDefault().addOnFdcClientStateChangedListeners(this)
         FdcClient.getDefault().addOnFdcServiceResponseReceivedListeners(this)
         FdcClient.getDefault().addOnFdcMessageReceivedListeners(this)
@@ -91,20 +114,14 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
             SPUtil.getString(SPKeys.MIDDLE_WORKSTATION_ID).toInt(),
             null
         )
-
-        coroutineIO.launch {
-            delay(12000)
-            if (stateFusion == FdcClient.FdcClientState.Stopped) {
-                onFusionEvent?.onFusionInit(
-                    FusionError.Timeout,
-                    StringUtil.get(R.string.connect_timeout)
-                )
-            }
-        }
     }
 
     @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
     fun close() {
+        isInitialized = false
+        isLogin = false
+        isFetchPumpInfo = false
+
         val manager = this
         coroutineIO.launch {
             logout()
@@ -112,7 +129,6 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
             FdcClient.getDefault().removeOnFdcServiceResponseReceivedListeners(manager)
             FdcClient.getDefault().removeOnFdcMessageReceivedListeners(manager)
             FdcClient.getDefault().stop()
-            firstLink = false
         }
     }
 
@@ -125,46 +141,35 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
     }
 
     /**
-     * 初始化流程中的登录+获取油品信息
+     * Login to Fusion and retry if first login failed.
      */
-    fun loginFetchInfo() {
+    suspend fun loginToFusion(): Boolean = suspendCoroutine {
         coroutineIO.launch {
-            if (!isLogin) {
-                val success = if (login()) true else {
-                    DFSLog.d("Fusion: retry login after logout")
-                    logout()
-                    delay(7000)
-                    login()
-                }
-                if (!success) {
-                    isLogin = false
-                    onFusionEvent?.onFusionInit(
-                        FusionError.Login,
-                        StringUtil.get(R.string.login_fail)
-                    )
-                    return@launch
-                } else {
-                    isLogin = true
-                }
+            isLogin = if (login()) true else {
+                DFSLog.d("Fusion: retry login after logout")
+                logout()
+                login()
             }
+            it.resume(isLogin)
 
-            val resultPumpList = requestPumpInfo()
-            if (resultPumpList.success && resultPumpList.data != null) {
-                if (pumpList.isNotEmpty()) pumpList.clear()
-                pumpList.addAll(resultPumpList.data)
-                onFusionEvent?.onFusionInit(FusionError.Success)
-            } else {
-                onFusionEvent?.onFusionInit(
-                    FusionError.GetNozzleInfo,
-                    StringUtil.get(R.string.fail_get_nozzle)
-                )
-            }
+            // FIXME pumpList is not set
+//            val resultPumpList = requestPumpInfo()
+//            if (resultPumpList.success && resultPumpList.data != null) {
+//                if (pumpList.isNotEmpty()) pumpList.clear()
+//                pumpList.addAll(resultPumpList.data)
+//                onFusionStatus?.onFusionInit(FusionStatus.Success)
+//            } else {
+//                onFusionStatus?.onFusionInit(
+//                    FusionStatus.GetNozzleInfo,
+//                    StringUtil.get(R.string.fail_get_nozzle)
+//                )
+//            }
         }
     }
 
     /**
-     * 获取所有未结算加油订单
-     * @param pumpId -1表示全部FP,其他代表某个FP
+     * Fetch all uncleared order.
+     * @param pumpId -1 means all pumps, others mean specified pump.
      */
     suspend fun getAllUnsettledOrder(pumpId: Int = -1) = suspendCoroutine<DFSResult<List<DeviceClass>>> {
             ThreadUtil.io {
@@ -198,12 +203,12 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
                             it.resume(DFSResult.success(resultDetailList))
                         }
                     }
-                }, timeoutMax)
+                }, 8000)
             }
         }
 
     /**
-     * 挂起函数:获取订单详情
+     * Fetch order detail.
      */
     private suspend fun getOrderDetail(deviceClass: DeviceClass) = suspendCoroutine<DFSResult<DeviceClass>> {
             getOrderDetailAsync(deviceClass) { asyncResult ->
@@ -212,7 +217,7 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
         }
 
     /**
-     * 获取订单详情
+     * Fetch order detail.
      */
     private fun getOrderDetailAsync(dcRaw: DeviceClass, callback: (result: DFSResult<DeviceClass>) -> Unit) {
         ThreadUtil.io {
@@ -249,8 +254,7 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
     }
 
     /**
-     * 消单
-     * @desc 返回的 dc.isLock 不会发生改变 FIXME
+     * Clear order in server. Also remember mark order status as 'Cleared'.
      */
     suspend fun clearOrder(dc: DeviceClass): DFSResult<Boolean> {
         return if (dc.isClear) {
@@ -282,7 +286,7 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
         }
 
     /**
-     * 锁定/解锁订单
+     * Lock or unlock order.
      */
     suspend fun lockOrder(dc: DeviceClass, lock: Boolean): DFSResult<Boolean> {
         return if (dc.isLock == lock) {
@@ -324,7 +328,19 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
             }
         }
 
-    suspend fun requestPumpInfo() = FuelInfoApi.requestPumpInfo()
+    /**
+     * Proxy method to get pump and nozzle data.
+     */
+    suspend fun getPumpInfo(): DFSResult<List<PumpInfo>> {
+        val resultPumpList = FuelInfoApi.requestPumpInfo()
+        if (resultPumpList.success && resultPumpList.data != null) {
+            isFetchPumpInfo = true
+            if (pumpList.isNotEmpty()) pumpList.clear()
+            pumpList.addAll(resultPumpList.data)
+        }
+
+        return resultPumpList
+    }
 
     private suspend fun login() = suspendCoroutine<Boolean> {
         val port = SPUtil.getString(SPKeys.MIDDLE_PORT).toInt()
@@ -333,7 +349,7 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
             if (response != null && response is ServiceResponseLogOn) {
                 val status = response.singleFdcData.fdcStatus
                 if (status.equals(FusionConstants.CODE_SUCCESS, true)) {
-                    DFSLog.i("Fusion: login succeeded")
+                    DFSLog.d("Fusion: login succeeded")
                     it.resume(true)
                 } else {
                     DFSLog.e("Fusion: login failed")
@@ -344,6 +360,7 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
     }
 
     private suspend fun logout() = suspendCoroutine<Boolean> {
+        isLogin = false
         FdcClient.getDefault().sendLogOffRequestAsync({ _, response ->
             val status = response?.singleFdcData?.fdcStatus
             DFSLog.v("Fusion: try logout response = $status")

+ 7 - 0
app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionStatus.kt

@@ -0,0 +1,7 @@
+package com.doverfuelingsolutions.issp.fusion
+
+enum class FusionStatus {
+    Connected,
+    Connecting,
+    Disconnected,
+}

+ 0 - 10
app/src/main/java/com/doverfuelingsolutions/issp/fusion/callback/OnFusionEvent.kt

@@ -1,10 +0,0 @@
-package com.doverfuelingsolutions.issp.fusion.callback
-
-import com.doverfuelingsolutions.issp.fusion.FusionError
-
-interface OnFusionEvent {
-
-    fun onFusionInit(code: FusionError, msg: String = "")
-
-    fun onFusionReconnect()
-}

+ 8 - 0
app/src/main/java/com/doverfuelingsolutions/issp/fusion/callback/OnFusionStatus.kt

@@ -0,0 +1,8 @@
+package com.doverfuelingsolutions.issp.fusion.callback
+
+import com.doverfuelingsolutions.issp.fusion.FusionStatus
+
+interface OnFusionStatus {
+
+    fun onFusionStatus(status: FusionStatus)
+}

+ 1 - 1
app/src/main/java/com/doverfuelingsolutions/issp/utils/ActivityUtil.kt → app/src/main/java/com/doverfuelingsolutions/issp/utils/WindowUtil.kt

@@ -9,7 +9,7 @@ import androidx.lifecycle.lifecycleScope
 import com.doverfuelingsolutions.issp.utils.log.DFSLog
 import kotlinx.coroutines.delay
 
-class ActivityUtil {
+class WindowUtil {
 
     companion object {
 

+ 55 - 62
app/src/main/java/com/doverfuelingsolutions/issp/view/MainActivity.kt

@@ -4,8 +4,8 @@ import android.content.Context
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
-import android.view.View
 import androidx.activity.viewModels
+import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.app.AppCompatActivity
 import androidx.databinding.DataBindingUtil
 import androidx.fragment.app.Fragment
@@ -13,28 +13,27 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.lifecycleScope
 import com.doverfuelingsolutions.issp.R
-import com.doverfuelingsolutions.issp.api.SystemApi
 import com.doverfuelingsolutions.issp.data.GlobalData
 import com.doverfuelingsolutions.issp.databinding.ActivityMainBinding
-import com.doverfuelingsolutions.issp.fusion.FusionError
 import com.doverfuelingsolutions.issp.fusion.FusionManager
-import com.doverfuelingsolutions.issp.fusion.callback.OnFusionEvent
-import com.doverfuelingsolutions.issp.utils.ActivityUtil
-import com.doverfuelingsolutions.issp.utils.NetworkUtil
-import com.doverfuelingsolutions.issp.utils.StringUtil
+import com.doverfuelingsolutions.issp.fusion.FusionStatus
+import com.doverfuelingsolutions.issp.fusion.callback.OnFusionStatus
+import com.doverfuelingsolutions.issp.utils.DFSToastUtil
+import com.doverfuelingsolutions.issp.utils.WindowUtil
 import com.doverfuelingsolutions.issp.utils.log.DFSLog
-import com.doverfuelingsolutions.issp.utils.thread.ThreadUtil
-import com.doverfuelingsolutions.issp.view.fragment.FragmentAction
 import com.doverfuelingsolutions.issp.view.fragment.FragmentLoading
+import com.doverfuelingsolutions.issp.view.fragment.FragmentReconnect
 import com.doverfuelingsolutions.issp.view.fragment.FragmentSelect
+import com.doverfuelingsolutions.issp.view.util.LoadingDialogBuilder
 import com.wayne.www.waynelib.fdc.FdcClient
 import com.youth.banner.adapter.BannerImageAdapter
 import com.youth.banner.holder.BannerImageHolder
 import kotlinx.coroutines.launch
 import java.io.File
+import java.util.*
 
 class MainActivity : AppCompatActivity(),
-    OnFusionEvent {
+    OnFusionStatus {
 
     companion object {
         fun start(context: Context) {
@@ -50,6 +49,8 @@ class MainActivity : AppCompatActivity(),
     }
     private val mainViewModel: MainViewModel by viewModels()
 
+    private var dialogFusionLinking: AlertDialog? = null
+
     private var isBlockBackPress = false
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -57,18 +58,20 @@ class MainActivity : AppCompatActivity(),
         binding.lifecycleOwner = this
         binding.mainViewModel = mainViewModel
         initView()
-        initFusion()
+
+        FusionManager.onFusionStatus = this
+        lifecycle.addObserver(FusionManager)
     }
 
     override fun onResume() {
         super.onResume()
-        ActivityUtil.setFullscreen(this)
+        WindowUtil.setFullscreen(this)
     }
 
     override fun onDestroy() {
         super.onDestroy()
 
-        FusionManager.onFusionEvent = null
+        FusionManager.onFusionStatus = null
     }
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -78,18 +81,24 @@ class MainActivity : AppCompatActivity(),
             when (requestCode) {
                 PreferenceActivity.codeRequestResult -> {
                     data?.let {
-                        val isMiddleModified =
-                            it.getBooleanExtra(PreferenceActivity.isMiddleModified, false)
-                        val isFuelModified =
-                            it.getBooleanExtra(PreferenceActivity.isFuelModified, false)
+                        val isMiddleModified = it.getBooleanExtra(PreferenceActivity.isMiddleModified, false)
+                        val isFuelModified = it.getBooleanExtra(PreferenceActivity.isFuelModified, false)
                         if (!isFuelModified && !isMiddleModified) return@let
 
-                        setFragment(FragmentLoading.build(R.string.in_reconnect_fusion))
                         if (isMiddleModified || FusionManager.stateFusion != FdcClient.FdcClientState.Connected) {
+                            onFusionStatus(FusionStatus.Connecting)
                             FusionManager.restart()
                         } else if (isFuelModified && FusionManager.stateFusion == FdcClient.FdcClientState.Connected) {
-                            setFragment(FragmentLoading.build(R.string.in_get_fuel))
-                            FusionManager.loginFetchInfo()
+                            val dialog = LoadingDialogBuilder(this@MainActivity)
+                                .setLoadingText(R.string.in_get_fuel)
+                                .show()
+                            val result = FusionManager.getPumpInfo()
+                            dialog.dismiss()
+                            if (result.success) {
+                                DFSToastUtil.success(R.string.get_fuel_success)
+                            } else {
+                                DFSToastUtil.fail(R.string.get_fuel_failed)
+                            }
                         }
                     }
                 }
@@ -101,55 +110,46 @@ class MainActivity : AppCompatActivity(),
         if (!isBlockBackPress) super.onBackPressed()
     }
 
-    override fun onFusionInit(code: FusionError, msg: String) {
+    override fun onFusionStatus(status: FusionStatus) {
+        DFSLog.i("Fusion status: ${status.name.toLowerCase(Locale.CHINESE)}")
         lifecycleScope.launch {
-            // WrongAddress Timeout 需要修改中控信息,返回时如已修改则重启Fusion
-            // GetNozzleInfo 不重启Fusion,重新请求加油信息即可
-            when (code) {
-                FusionError.Success -> {
-                    setFragment(FragmentSelect())
-                }
-                FusionError.WrongAddress, FusionError.Timeout, FusionError.GetNozzleInfo -> {
-                    val buttonText = if (code == FusionError.GetNozzleInfo) {
-                        StringUtil.get(R.string.go_check_fuel)
+            dialogFusionLinking?.hide()
+            when (status) {
+                FusionStatus.Connecting -> {
+                    // Don't dismiss dialog, just show & hide for reuse.
+                    if (dialogFusionLinking == null) {
+                        dialogFusionLinking = LoadingDialogBuilder(this@MainActivity)
+                            .setTitle(R.string.fusion_in_connect)
+                            .setLoadingText(R.string.might_take_minutes)
+                            .setCancelable(false)
+                            .show()
+                        dialogFusionLinking?.window?.let { WindowUtil.setFullscreen(it) }
                     } else {
-                        StringUtil.get(R.string.go_check_fusion)
+                        dialogFusionLinking?.show()
                     }
-                    setFragment(FragmentAction.build(StringUtil.get(R.string.connect_fail_reason, msg), buttonText) {
-                        PreferenceActivity.startForResult(this@MainActivity)
-                    })
                 }
-                FusionError.Login -> {
-                    setFragment(FragmentAction.build(StringUtil.get(R.string.connect_fail_reason, msg), StringUtil.get(R.string.retry_login)) {
-                        FusionManager.loginFetchInfo()
+                FusionStatus.Connected -> {
+                    DFSToastUtil.success(R.string.connect_fusion_success)
+                    setFragment(FragmentSelect())
+                }
+                FusionStatus.Disconnected -> {
+                    // Disconnected reason
+                    // 1. Network problem.
+                    // 2. Communication exception and failed retry.
+                    DFSToastUtil.fail(R.string.disconnect_fusion)
+                    setFragment(FragmentReconnect.build {
+                        onFusionStatus(FusionStatus.Connecting)
                     })
                 }
             }
         }
     }
 
-    override fun onFusionReconnect() {
-        lifecycleScope.launch {
-            if (NetworkUtil.isAvailable(this@MainActivity)) {
-                FragmentLoading.build(R.string.in_reconnect_fusion)
-            } else {
-                FragmentLoading.build(R.string.network_error)
-            }
-        }
-    }
-
     private fun initView() {
         supportActionBar?.hide()
         mainViewModel.stationName.value = GlobalData.businessName.get()
         if (GlobalData.stationLogoFileName.get().isNotEmpty())
-            binding.stationLogo.setImageURI(
-                Uri.fromFile(
-                    File(
-                        filesDir,
-                        GlobalData.stationLogoFileName.get()
-                    )
-                )
-            )
+            binding.stationLogo.setImageURI(Uri.fromFile(File(filesDir, GlobalData.stationLogoFileName.get())))
         binding.banner.run {
             addBannerLifecycleObserver(this@MainActivity)
             scrollTime = 500
@@ -165,13 +165,6 @@ class MainActivity : AppCompatActivity(),
                     }
                 }
         }
-
-        setFragment(FragmentLoading.build(R.string.in_connect_fusion))
-    }
-
-    private fun initFusion() {
-        FusionManager.onFusionEvent = this
-        lifecycle.addObserver(FusionManager)
     }
 
     fun setFragment(fragment: Fragment, back: Boolean = false) {

+ 11 - 8
app/src/main/java/com/doverfuelingsolutions/issp/view/PreferenceActivity.kt

@@ -5,7 +5,6 @@ import android.app.DownloadManager
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
-import android.os.Build
 import android.os.Bundle
 import android.os.Environment
 import android.view.MenuItem
@@ -23,7 +22,7 @@ import com.doverfuelingsolutions.issp.api.FuelInfoApi
 import com.doverfuelingsolutions.issp.api.SystemApi
 import com.doverfuelingsolutions.issp.api.WayneApiConfig
 import com.doverfuelingsolutions.issp.databinding.ActivityPreferenceBinding
-import com.doverfuelingsolutions.issp.utils.ActivityUtil
+import com.doverfuelingsolutions.issp.utils.WindowUtil
 import com.doverfuelingsolutions.issp.utils.AppUtil
 import com.doverfuelingsolutions.issp.utils.DFSToastUtil
 import com.doverfuelingsolutions.issp.utils.StringUtil
@@ -35,7 +34,6 @@ import com.doverfuelingsolutions.issp.view.fragment.FragmentPreference
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import kotlinx.coroutines.launch
 import java.io.File
-import java.util.*
 
 class PreferenceActivity : AppCompatActivity(),
     View.OnClickListener {
@@ -95,7 +93,7 @@ class PreferenceActivity : AppCompatActivity(),
     override fun onResume() {
         super.onResume()
 
-        ActivityUtil.setFullscreen(this)
+        WindowUtil.setFullscreen(this)
     }
 
     override fun onBackPressed() {
@@ -116,10 +114,15 @@ class PreferenceActivity : AppCompatActivity(),
     }
 
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
-        when (item.itemId) {
-            android.R.id.home -> onBackPressed()
+        return when (item.itemId) {
+            android.R.id.home -> {
+                onBackPressed()
+                true
+            }
+            else -> {
+                super.onOptionsItemSelected(item)
+            }
         }
-        return super.onOptionsItemSelected(item)
     }
 
     override fun onClick(v: View?) {
@@ -187,7 +190,7 @@ class PreferenceActivity : AppCompatActivity(),
                 .setView(dialogView)
                 .setCancelable(false)
                 .show()
-            dialog.window?.let { ActivityUtil.setFullscreen(it) }
+            dialog.window?.let { WindowUtil.setFullscreen(it) }
             val actionButton = dialogView.findViewById<Button>(R.id.close)
             actionButton.setOnClickListener { dialog.dismiss() }
 

+ 0 - 60
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentAction.kt

@@ -1,60 +0,0 @@
-package com.doverfuelingsolutions.issp.view.fragment
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.databinding.DataBindingUtil
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.doverfuelingsolutions.issp.R
-import com.doverfuelingsolutions.issp.databinding.FragmentActionBinding
-import com.doverfuelingsolutions.issp.utils.StringUtil
-
-class FragmentAction private constructor(
-    private val tip: String,
-    private val buttonText: String,
-    private val handler: () -> Unit,
-) : Fragment(), View.OnClickListener {
-
-    companion object {
-
-        fun build(msg: String, buttonText: String, handler: () -> Unit): FragmentAction {
-            return FragmentAction(msg, buttonText, handler)
-        }
-    }
-
-    private lateinit var binding: FragmentActionBinding
-    private val viewModel: ActionViewModel by viewModels()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        if (!this::binding.isInitialized) {
-            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_action, container, false)
-            binding.lifecycleOwner = viewLifecycleOwner
-            binding.viewModel = viewModel
-            binding.handler = this
-        }
-        viewModel.actionText.value = tip
-        viewModel.actionButtonText.value = buttonText
-        return binding.root
-    }
-
-    override fun onClick(v: View?) {
-        when (v) {
-            binding.actionButton -> {
-                handler.invoke()
-            }
-        }
-    }
-
-    class ActionViewModel : ViewModel() {
-        val actionText = MutableLiveData(StringUtil.get(R.string.connect_timeout))
-        val actionButtonText = MutableLiveData(StringUtil.get(R.string.go_check_fusion))
-    }
-}

+ 6 - 37
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentBasic.kt

@@ -1,25 +1,15 @@
 package com.doverfuelingsolutions.issp.view.fragment
 
-import android.view.LayoutInflater
-import android.view.View
 import androidx.annotation.StringRes
 import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.widget.AppCompatTextView
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
-import androidx.lifecycle.lifecycleScope
-import com.doverfuelingsolutions.issp.R
 import com.doverfuelingsolutions.issp.view.widget.FragmentToolbar
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.doverfuelingsolutions.issp.utils.StringUtil
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import com.doverfuelingsolutions.issp.view.util.LoadingDialogBuilder
 
 abstract class FragmentBasic : Fragment() {
 
-    private var basicDialog: AlertDialog? = null
-    private var displayAfterDelay = false
-
     private var mToolbar: FragmentToolbar? = null
 
     fun setToolBar(toolbar: FragmentToolbar) {
@@ -30,32 +20,11 @@ abstract class FragmentBasic : Fragment() {
     }
 
     fun loading(@StringRes string: Int) = loading(StringUtil.get(string))
-    private fun loading(msg: String) {
-        displayAfterDelay = true
-        lifecycleScope.launch {
-            // 避免异步响应过快造成闪屏
-            delay(500)
-            // 在 delay 时,可能已手动关闭
-            if (displayAfterDelay) {
-                mToolbar?.pauseCountdown()
-
-                val view = LayoutInflater.from(requireContext()).inflate(R.layout.fragment_loading, null)
-                view.findViewById<AppCompatTextView>(R.id.loadingTip)?.text = msg
-                basicDialog = MaterialAlertDialogBuilder(requireContext())
-                    .setCancelable(false)
-                    .setView(view)
-                    .show()
-            }
-        }
-    }
-
-    fun hideLoading() {
-        mToolbar?.resumeCountdown()
-
-        displayAfterDelay = false
-        basicDialog?.let {
-            if (isVisible) it.dismiss()
-        }
+    private fun loading(msg: String): AlertDialog {
+        return LoadingDialogBuilder(requireContext())
+            .setLoadingText(msg)
+            .setCancelable(true)
+            .show()
     }
 
     fun home() {

+ 1 - 1
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentLoading.kt

@@ -36,7 +36,7 @@ class FragmentLoading private constructor(private val msg: String) : Fragment()
         return binding.root
     }
 
-    class FragmentHolderViewModel(msg: String = StringUtil.get(R.string.in_connect_fusion)) : ViewModel() {
+    class FragmentHolderViewModel(msg: String = StringUtil.get(R.string.fusion_in_connect)) : ViewModel() {
         val loadingTip = MutableLiveData(msg)
     }
 }

+ 1 - 1
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentNozzle.kt

@@ -58,7 +58,7 @@ class FragmentNozzle private constructor(private val pumpList: List<PumpInfo>) :
 
     private fun handleNozzleRefresh() {
         lifecycleScope.launch {
-            val resultNozzles = FusionManager.requestPumpInfo()
+            val resultNozzles = FusionManager.getPumpInfo()
             if (resultNozzles.success && resultNozzles.data != null) {
                 if (FusionManager.pumpList.isNotEmpty()) FusionManager.pumpList.clear()
                 FusionManager.pumpList.addAll(resultNozzles.data)

+ 11 - 12
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentOrderList.kt

@@ -13,7 +13,6 @@ import com.doverfuelingsolutions.issp.R
 import com.doverfuelingsolutions.issp.databinding.FragmentNozzleOrdersBinding
 import com.doverfuelingsolutions.issp.fusion.FusionManager
 import com.doverfuelingsolutions.issp.utils.DFSToastUtil
-import com.doverfuelingsolutions.issp.utils.log.DFSLog
 import com.doverfuelingsolutions.issp.utils.sp.SPUtil
 import com.doverfuelingsolutions.issp.view.MainActivity
 import com.doverfuelingsolutions.issp.view.adapter.ChooseListAdapter
@@ -44,14 +43,6 @@ class FragmentOrderList private constructor(private val pumpId: Int) : FragmentB
         ChooseListAdapter(oilNameList, this::selectOilName, true)
     }
 
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        lifecycleScope.launch {
-            loadListData()
-        }
-    }
-
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
         if (!this::binding.isInitialized) {
             binding = DataBindingUtil.inflate(inflater, R.layout.fragment_nozzle_orders, container, false)
@@ -75,6 +66,11 @@ class FragmentOrderList private constructor(private val pumpId: Int) : FragmentB
 
             setToolBar(binding.fragmentToolbar)
         }
+
+        lifecycleScope.launch {
+            loadListData()
+        }
+
         return binding.root
     }
 
@@ -105,13 +101,15 @@ class FragmentOrderList private constructor(private val pumpId: Int) : FragmentB
 
     private fun handleRefresh() {
         lifecycleScope.launch {
-            hideLoading()
+            loadRemoteData()
             binding.smartRefreshLayout.finishRefresh(200, true, false)
         }
     }
 
     private suspend fun loadListData() {
-        if (orderList.isEmpty()) loading(R.string.in_get_order)
+        val dialog = loading(R.string.in_get_order)
+        binding.fragmentToolbar.stopCountdown()
+
         if (this::binding.isInitialized) binding.orderListView.scrollTo(0, 0)
 
         // -1 选油品,非 -1 选枪
@@ -135,7 +133,8 @@ class FragmentOrderList private constructor(private val pumpId: Int) : FragmentB
 
         // remote data
         loadRemoteData()
-        hideLoading()
+        dialog.dismiss()
+        binding.fragmentToolbar.resumeCountdown()
     }
 
     private suspend fun loadRemoteData() {

+ 2 - 3
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPayScanCode.kt

@@ -22,7 +22,6 @@ import com.wayne.www.waynelib.fdc.message.DeviceClass
 import com.wayne.www.waynelib.webservice.entity.PosTrx
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
 
 class FragmentPayScanCode(private val dc: DeviceClass, private val posTrx: PosTrx) : FragmentBasic() {
@@ -87,9 +86,9 @@ class FragmentPayScanCode(private val dc: DeviceClass, private val posTrx: PosTr
                 return@launch
             }
 
-            loading(R.string.in_pay)
+            val dialog = loading(R.string.in_pay)
             val result = SystemApi.pay(posTrx, scanValue)
-            hideLoading()
+            dialog.dismiss()
             DFSLog.i(result)
 
             if (result.success && result.data != null) {

+ 9 - 6
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPayType.kt

@@ -13,8 +13,6 @@ import com.doverfuelingsolutions.issp.databinding.FragmentPayTypeBinding
 import com.doverfuelingsolutions.issp.fusion.FusionManager
 import com.doverfuelingsolutions.issp.utils.DFSToastUtil
 import com.doverfuelingsolutions.issp.utils.StringUtil
-import com.doverfuelingsolutions.issp.utils.thread.ThreadUtil
-import com.doverfuelingsolutions.issp.utils.log.DFSLog
 import com.doverfuelingsolutions.issp.utils.sp.SPUtil
 import com.doverfuelingsolutions.issp.view.MainActivity
 import com.wayne.www.waynelib.fdc.message.DeviceClass
@@ -51,7 +49,9 @@ class FragmentPayType private constructor(private val dc: DeviceClass): Fragment
     }
 
     private fun prepareBeforePay() {
-        loading(R.string.in_process_order)
+        val dialog = loading(R.string.in_process_order)
+        binding.fragmentToolbar.stopCountdown()
+
         lifecycleScope.launch {
             val resultAccumulationInfo = FuelInfoApi.accumulationInfo(dc)
             if (resultAccumulationInfo.success && resultAccumulationInfo.data != null) {
@@ -59,7 +59,8 @@ class FragmentPayType private constructor(private val dc: DeviceClass): Fragment
                 dc.volumeTotalizer = BigDecimal(resultAccumulationInfo.data.VolumeTotalizer)
                 dc.saleEndTime = resultAccumulationInfo.data.SaleEndTime
             } else {
-                hideLoading()
+                dialog.dismiss()
+                binding.fragmentToolbar.resumeCountdown()
                 DFSToastUtil.fail(R.string.fail_get_accumulation)
                 return@launch
             }
@@ -72,7 +73,8 @@ class FragmentPayType private constructor(private val dc: DeviceClass): Fragment
                     posTrx = resultReportPayment.data
                     SPUtil.addWayneOrder(dc, posTrx)
                 } else {
-                    hideLoading()
+                    dialog.dismiss()
+                    binding.fragmentToolbar.resumeCountdown()
                     DFSToastUtil.fail(StringUtil.get(R.string.fail_behave_reason, StringUtil.get(R.string.make_cloud_order), resultReportPayment.message))
                 }
             }
@@ -80,7 +82,8 @@ class FragmentPayType private constructor(private val dc: DeviceClass): Fragment
             // 锁定订单
             if (posTrx == null) return@launch
             val resultLock = FusionManager.lockOrder(dc, true)
-            hideLoading()
+            dialog.dismiss()
+            binding.fragmentToolbar.resumeCountdown()
             if (resultLock.success) {
                 dc.state = "Locked"
                 SPUtil.addLockOrder(dc)

+ 0 - 6
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPreferenceWrapper.kt

@@ -1,6 +0,0 @@
-package com.doverfuelingsolutions.issp.view.fragment
-
-import androidx.fragment.app.Fragment
-import com.doverfuelingsolutions.issp.R
-
-class FragmentPreferenceWrapper : Fragment(R.layout.fragment_preference_wrapper)

+ 46 - 0
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentReconnect.kt

@@ -0,0 +1,46 @@
+package com.doverfuelingsolutions.issp.view.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import com.doverfuelingsolutions.issp.R
+import com.doverfuelingsolutions.issp.databinding.FragmentReconnectBinding
+import com.doverfuelingsolutions.issp.fusion.FusionManager
+import com.doverfuelingsolutions.issp.view.PreferenceActivity
+
+class FragmentReconnect private constructor(private val reconnectHandler: () -> Unit) : Fragment(), View.OnClickListener {
+
+    companion object {
+
+        fun build(reconnectHandler: () -> Unit): FragmentReconnect {
+            return FragmentReconnect(reconnectHandler)
+        }
+    }
+
+    private lateinit var binding: FragmentReconnectBinding
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        if (!this::binding.isInitialized) {
+            binding =
+                DataBindingUtil.inflate(inflater, R.layout.fragment_reconnect, container, false)
+            binding.lifecycleOwner = viewLifecycleOwner
+            binding.handler = this
+        }
+        return binding.root
+    }
+
+    override fun onClick(v: View?) {
+        when (v) {
+            binding.buttonReconnect -> {
+                FusionManager.restart()
+                reconnectHandler.invoke()
+            }
+            binding.buttonPreference -> {
+                PreferenceActivity.startForResult(requireActivity())
+            }
+        }
+    }
+}

+ 43 - 31
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentSelect.kt

@@ -16,13 +16,15 @@ import com.doverfuelingsolutions.issp.api.SystemApi
 import com.doverfuelingsolutions.issp.data.GlobalData
 import com.doverfuelingsolutions.issp.databinding.FragmentSearchTypeBinding
 import com.doverfuelingsolutions.issp.fusion.FusionManager
+import com.doverfuelingsolutions.issp.utils.DFSToastUtil
 import com.doverfuelingsolutions.issp.utils.log.DFSLog
 import com.doverfuelingsolutions.issp.utils.sp.SPUtil
 import com.doverfuelingsolutions.issp.view.MainActivity
 import com.doverfuelingsolutions.issp.view.PreferenceActivity
+import kotlinx.android.synthetic.main.layout_loading.*
 import kotlinx.coroutines.*
 
-class FragmentSelect : Fragment(),
+class FragmentSelect : FragmentBasic(),
     View.OnLongClickListener,
     View.OnClickListener {
 
@@ -31,31 +33,22 @@ class FragmentSelect : Fragment(),
 
     private var job: Job? = null
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
         if (!this::binding.isInitialized) {
-            binding =
-                DataBindingUtil.inflate(inflater, R.layout.fragment_search_type, container, false)
+            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_type, container, false)
             binding.lifecycleOwner = this
             binding.viewModel = viewModel
             binding.handler = this
             binding.deviceNum.setOnLongClickListener(this)
         }
-        return binding.root
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
 
-        handleUnsolvedOrder()
+        return binding.root
     }
 
     override fun onStart() {
         super.onStart()
 
+        handleUnsolvedOrder()
         refreshLoginToken()
     }
 
@@ -68,18 +61,16 @@ class FragmentSelect : Fragment(),
     }
 
     override fun onClick(v: View?) {
-        when (v) {
-            binding.chooseNozzle -> {
-                (activity as MainActivity).setFragment(
-                    FragmentNozzle.build(FusionManager.pumpList),
-                    true
-                )
-            }
-            binding.chooseOil -> {
-                (activity as MainActivity).setFragment(
-                    FragmentOrderList.build(-1),
-                    true
-                )
+        lifecycleScope.launch {
+            if (!checkFusion()) return@launch
+
+            when (v) {
+                binding.chooseNozzle -> {
+                    (activity as MainActivity).setFragment(FragmentNozzle.build(FusionManager.pumpList), true)
+                }
+                binding.chooseOil -> {
+                    (activity as MainActivity).setFragment(FragmentOrderList.build(-1), true)
+                }
             }
         }
     }
@@ -95,12 +86,10 @@ class FragmentSelect : Fragment(),
     }
 
     // TODO 当本地未处理订单无法按预期处理时(如本地锁的单,但在服务器上被其他渠道解锁了,造成解锁失败;消单同理;)
-    /* 未处理的订单 */
     private fun handleUnsolvedOrder() {
         GlobalScope.launch(Dispatchers.IO) {
             val lockList = SPUtil.getLockOrderList()
             val unclearedList = SPUtil.getUnclearedOrderList()
-            DFSLog.i("try handle orders triggered by this device: locked ${lockList.size}, uncleared ${unclearedList.size}")
             // 解锁订单
             lockList.let { list ->
                 if (list.isEmpty()) return@let
@@ -116,7 +105,7 @@ class FragmentSelect : Fragment(),
                 }
                 val resultList = deferredList.map { item -> item.await() }
                 val remainLockList = resultList.mapIndexed { index, result ->
-                    val tip = "unlock order「${list[index]}」"
+                    val tip = "auto unlock order ${list[index]}"
                     if (result.success) {
                         DFSLog.d("$tip succeeded")
                         null
@@ -142,7 +131,7 @@ class FragmentSelect : Fragment(),
                 }
                 val resultList = deferredList.map { item -> item.await() }
                 val remainLockList = resultList.mapIndexed { index, result ->
-                    val tip = "clear order「${list[index]}」"
+                    val tip = "auto clear order ${list[index]}"
                     if (result.success) {
                         DFSLog.d("$tip succeeded")
                         null
@@ -158,7 +147,6 @@ class FragmentSelect : Fragment(),
         }
     }
 
-    /* 刷新登录状态 */
     private fun refreshLoginToken() {
         // 登录有效时长两小时,为了避免在操作过程出现接口验证失败
         job = lifecycleScope.launch {
@@ -181,6 +169,30 @@ class FragmentSelect : Fragment(),
         }
     }
 
+    private suspend fun checkFusion(): Boolean {
+        if (!FusionManager.isLogin) {
+            val dialog = loading(R.string.in_login_fusion)
+            val success = FusionManager.loginToFusion()
+            dialog.dismiss()
+            if (!success) {
+                DFSToastUtil.fail(R.string.fusion_login_failed)
+                return false
+            }
+        }
+
+        if (!FusionManager.isFetchPumpInfo) {
+            val dialog = loading(R.string.in_get_fuel)
+            val result = FusionManager.getPumpInfo()
+            dialog.dismiss()
+            if (!result.success) {
+                DFSToastUtil.fail(R.string.get_fuel_failed)
+                return false
+            }
+        }
+
+        return true
+    }
+
     class SearchTypeViewModel : ViewModel() {
         val deviceNum = MutableLiveData(GlobalData.serialNumber.get())
         val version: MutableLiveData<String> by lazy {

+ 36 - 0
app/src/main/java/com/doverfuelingsolutions/issp/view/util/LoadingDialogBuilder.kt

@@ -0,0 +1,36 @@
+package com.doverfuelingsolutions.issp.view.util
+
+import android.content.Context
+import android.view.View
+import android.widget.TextView
+import androidx.annotation.StringRes
+import com.doverfuelingsolutions.issp.R
+import com.doverfuelingsolutions.issp.utils.StringUtil
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+class LoadingDialogBuilder(context: Context) : MaterialAlertDialogBuilder(context) {
+
+    // loading view
+    private val loadingView = View.inflate(context, R.layout.layout_loading, null)
+    private val loadingText: TextView = loadingView.findViewById(R.id.loadingTip)
+    init {
+        setView(loadingView)
+    }
+
+    override fun setTitle(titleId: Int): LoadingDialogBuilder {
+        super.setTitle(titleId)
+        return this
+    }
+
+    override fun setCancelable(cancelable: Boolean): LoadingDialogBuilder {
+        super.setCancelable(cancelable)
+        return this
+    }
+
+    fun setLoadingText(@StringRes string: Int) = setLoadingText(StringUtil.get(string))
+
+    fun setLoadingText(string: String): LoadingDialogBuilder {
+        loadingText.text = string
+        return this
+    }
+}

+ 12 - 13
app/src/main/java/com/doverfuelingsolutions/issp/view/widget/FragmentToolbar.kt

@@ -64,14 +64,14 @@ class FragmentToolbar(context: Context, attrs: AttributeSet) : FrameLayout(conte
 
     private lateinit var job: Job
 
-    override fun onWindowVisibilityChanged(visibility: Int) {
-        super.onWindowVisibilityChanged(visibility)
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        startCountdown()
+    }
 
-        if (visibility == VISIBLE) {
-            startCountdown()
-        } else {
-            stopCountdown()
-        }
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        stopCountdown()
     }
 
     private fun startCountdown(repeatTime: Int = countdownTime) {
@@ -84,21 +84,20 @@ class FragmentToolbar(context: Context, attrs: AttributeSet) : FrameLayout(conte
                 currentTime = num
                 countdownNum.text = num.toString()
                 delay(1000)
-                if (num <= 1) countdownEvent?.invoke()
+                if (num <= 1) {
+                    stopCountdown()
+                    countdownEvent?.invoke()
+                }
             }
         }
     }
 
-    private fun stopCountdown() {
+    fun stopCountdown() {
         if (this::job.isInitialized && job.isActive) {
             job.cancel()
         }
     }
 
-    fun pauseCountdown() {
-        job.cancel()
-    }
-
     fun resumeCountdown() {
         startCountdown(currentTime)
     }

+ 243 - 0
app/src/main/res/drawable/ic_network_error.xml

@@ -0,0 +1,243 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="178dp"
+    android:height="130dp"
+    android:viewportWidth="178"
+    android:viewportHeight="130">
+  <group>
+    <clip-path
+        android:pathData="M0,0h178v130h-178z"/>
+    <path
+        android:pathData="M28.466,21.937 L65.066,11.142L65.066,104.921L28.466,115.716Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="11.142"
+            android:startX="46.766"
+            android:endY="115.716"
+            android:endX="46.766"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFE9E9E9"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M28.466,21.935 L10.597,10.824v93.78l17.868,11.111Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="10.824"
+            android:startX="19.5315"
+            android:endY="115.715"
+            android:endX="19.5315"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFDCDCDC"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M28.463,21.988 L65.069,11.138 47.191,0.001l-36.6,10.845Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="0.001"
+            android:startX="37.83"
+            android:endY="21.988"
+            android:endX="37.83"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFF1F1F1"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M126.158,23.156 L161.899,12.617L161.899,104.177L126.158,114.717Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="12.617"
+            android:startX="144.0285"
+            android:endY="114.717"
+            android:endX="144.0285"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFF1F1F1"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M126.158,23.155 L108.71,12.306L108.71,103.868l17.448,10.848Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="12.306"
+            android:startX="117.434"
+            android:endY="114.715996"
+            android:endX="117.434"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFDEDEDE"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M126.158,23.207 L161.899,12.617 144.442,1.738 108.701,12.327Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="1.738"
+            android:startX="135.3"
+            android:endY="23.207"
+            android:endX="135.3"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFF1F1F1"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M55.678,49.716 L86.517,40.579L86.517,119.96L55.678,129.098Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="40.579"
+            android:startX="71.0975"
+            android:endY="129.098"
+            android:endX="71.0975"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFE8E8E8"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M55.679,49.716l-15.054,-9.4L40.625,119.693l15.054,9.4Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="40.316"
+            android:startX="48.152"
+            android:endY="129.093"
+            android:endX="48.152"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFDCDCDC"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M55.678,49.717 L86.517,40.58 71.455,31.193 40.616,40.33Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="54.62586"
+            android:startX="50.392914"
+            android:endY="31.193"
+            android:endX="79.44824"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFFFFFFF"/>
+          <item android:offset="1" android:color="#FFF5F5F5"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M178,129.549L0,129.549A51.361,51.361 0,0 1,10.453 108.597,74.532 74.532,0 0,1 30.195,91.786 103.3,103.3 0,0 1,57.089 80.607a127.515,127.515 0,0 1,63.823 0,103.3 103.3,0 0,1 26.894,11.179 74.532,74.532 0,0 1,19.741 16.811A51.363,51.363 0,0 1,178 129.549Z">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="75.19723"
+            android:startX="89"
+            android:endY="129.549"
+            android:endX="89"
+            android:type="linear">
+          <item android:offset="0" android:color="#FFC5C5C5"/>
+          <item android:offset="1" android:color="#00FFFFFF"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M111.257,96.182a45.253,45.253 0,0 0,3.843 -18.3A45.3,45.3 0,0 0,104.394 48.593L109.633,44.182a52.041,52.041 0,0 1,8.18 13.333A52,52 0,0 1,121.926 77.882a52,52 0,0 1,-4.113 20.369q-0.145,0.342 -0.294,0.682ZM97.442,90.116A30.245,30.245 0,0 0,100.01 77.882,30.279 30.279,0 0,0 92.854,58.306l5.174,-4.355a36.966,36.966 0,0 1,5.809 9.468A36.935,36.935 0,0 1,106.758 77.882a36.929,36.929 0,0 1,-2.921 14.465q-0.1,0.243 -0.209,0.484ZM83.482,83.982a15.084,15.084 0,0 0,1.281 -6.1A15.1,15.1 0,0 0,81.194 68.12l5.228,-4.4a21.871,21.871 0,0 1,3.438 5.6A21.863,21.863 0,0 1,91.589 77.882a21.857,21.857 0,0 1,-1.728 8.561q-0.061,0.144 -0.124,0.287Z"
+        android:fillColor="#ababab"/>
+    <path
+        android:pathData="M110.345,97.094a45.253,45.253 0,0 0,3.843 -18.3A45.3,45.3 0,0 0,103.482 49.505L108.721,45.094a52.041,52.041 0,0 1,8.18 13.333A52,52 0,0 1,121.014 78.794a52,52 0,0 1,-4.113 20.369q-0.145,0.342 -0.294,0.682ZM96.53,91.028A30.245,30.245 0,0 0,99.098 78.794,30.279 30.279,0 0,0 91.942,59.218l5.174,-4.355a36.966,36.966 0,0 1,5.809 9.468A36.935,36.935 0,0 1,105.846 78.794a36.929,36.929 0,0 1,-2.921 14.465q-0.1,0.243 -0.209,0.484ZM82.57,84.894a15.084,15.084 0,0 0,1.281 -6.1A15.1,15.1 0,0 0,80.282 69.032l5.228,-4.4a21.871,21.871 0,0 1,3.438 5.6A21.863,21.863 0,0 1,90.677 78.794a21.857,21.857 0,0 1,-1.728 8.561q-0.061,0.144 -0.124,0.287Z"
+        android:fillColor="#b2b2b2"/>
+    <path
+        android:pathData="M65,78a4.5,5 0,1 0,9 0a4.5,5 0,1 0,-9 0z"
+        android:fillColor="#ababab"/>
+    <path
+        android:pathData="M65.122,79.573l6.243,2.816 -13.541,11.6 -6.07,-3.642Z"
+        android:strokeAlpha="0.63"
+        android:fillAlpha="0.63">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="91.03372"
+            android:startX="55.872314"
+            android:endY="80.87044"
+            android:endX="68.05074"
+            android:type="linear">
+          <item android:offset="0" android:color="#00F1F1F1"/>
+          <item android:offset="1" android:color="#FFA2A2A2"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M68.5,78.5m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"
+        android:fillColor="#b2b2b2"/>
+    <path
+        android:pathData="M82.566,84.882l6.243,2.816 -13.541,11.6 -6.07,-3.642Z"
+        android:strokeAlpha="0.63"
+        android:fillAlpha="0.63">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="96.34273"
+            android:startX="73.316315"
+            android:endY="86.17944"
+            android:endX="85.49474"
+            android:type="linear">
+          <item android:offset="0" android:color="#00F1F1F1"/>
+          <item android:offset="1" android:color="#FFA2A2A2"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M96.533,91.007l6.208,2.733 -13.506,11.683 -6.07,-3.642Z"
+        android:strokeAlpha="0.63"
+        android:fillAlpha="0.63">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="102.46773"
+            android:startX="87.27596"
+            android:endY="92.30444"
+            android:endX="99.432655"
+            android:type="linear">
+          <item android:offset="0" android:color="#00F1F1F1"/>
+          <item android:offset="1" android:color="#FFA2A2A2"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M110.326,97.074l6.314,2.769 -13.612,11.648 -6.07,-3.642Z"
+        android:strokeAlpha="0.63"
+        android:fillAlpha="0.63">
+      <aapt:attr name="android:fillColor">
+        <gradient 
+            android:startY="108.535515"
+            android:startX="101.09122"
+            android:endY="98.37153"
+            android:endX="113.31374"
+            android:type="linear">
+          <item android:offset="0" android:color="#00F1F1F1"/>
+          <item android:offset="1" android:color="#FFA2A2A2"/>
+        </gradient>
+      </aapt:attr>
+    </path>
+    <path
+        android:pathData="M101,74m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
+        android:strokeWidth="1"
+        android:fillColor="#fff"
+        android:strokeColor="#00000000"/>
+    <path
+        android:pathData="M101,74m-11.5,0a11.5,11.5 0,1 1,23 0a11.5,11.5 0,1 1,-23 0"
+        android:strokeWidth="1"
+        android:fillColor="#00000000"
+        android:strokeColor="#b2b2b2"/>
+    <path
+        android:pathData="M95.697,69.197L95.697,69.197A1,1 0,0 1,97.111 69.197L106.304,78.389A1,1 119.276,0 1,106.304 79.804L106.304,79.804A1,1 119.276,0 1,104.889 79.804L95.697,70.611A1,1 0,0 1,95.697 69.197z"
+        android:fillColor="#b2b2b2"/>
+    <path
+        android:pathData="M106.303,69.197L106.303,69.197A1,1 0,0 1,106.303 70.611L97.111,79.804A1,1 119.426,0 1,95.696 79.804L95.696,79.804A1,1 119.426,0 1,95.696 78.389L104.889,69.197A1,1 0,0 1,106.303 69.197z"
+        android:fillColor="#b2b2b2"/>
+  </group>
+</vector>

+ 0 - 43
app/src/main/res/layout/fragment_action.xml

@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <data>
-
-        <variable
-            name="viewModel"
-            type="com.doverfuelingsolutions.issp.view.fragment.FragmentAction.ActionViewModel" />
-
-        <variable
-            name="handler"
-            type="com.doverfuelingsolutions.issp.view.fragment.FragmentAction" />
-    </data>
-
-    <androidx.constraintlayout.widget.ConstraintLayout style="@style/match">
-
-        <!--info + button-->
-        <androidx.appcompat.widget.LinearLayoutCompat
-            style="@style/wrap"
-            android:gravity="center"
-            android:orientation="vertical"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent">
-
-            <androidx.appcompat.widget.AppCompatTextView
-                style="@style/wrap"
-                android:layout_marginBottom="60dp"
-                android:text="@{viewModel.actionText, default=@string/connect_fail}"
-                android:textSize="26sp" />
-
-            <Button
-                android:id="@+id/actionButton"
-                style="@style/Widget.MaterialComponents.Button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:onClick="@{handler::onClick}"
-                android:text="@{viewModel.actionButtonText, default=@string/go_check_fusion}" />
-        </androidx.appcompat.widget.LinearLayoutCompat>
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</layout>

+ 41 - 0
app/src/main/res/layout/fragment_reconnect.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <data>
+
+        <variable
+            name="handler"
+            type="com.doverfuelingsolutions.issp.view.fragment.FragmentReconnect" />
+    </data>
+
+    <LinearLayout
+        style="@style/match"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="400dp"
+            android:layout_height="400dp"
+            android:scaleType="centerCrop"
+            android:src="@drawable/ic_network_error" />
+
+        <Button
+            android:id="@+id/buttonReconnect"
+            style="@style/Widget.MaterialComponents.Button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="120dp"
+            android:onClick="@{handler}"
+            android:text="@string/reconnect" />
+
+        <Button
+            android:id="@+id/buttonPreference"
+            style="@style/Widget.MaterialComponents.Button.TextButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="40dp"
+            android:onClick="@{handler}"
+            android:text="@string/check_preference" />
+    </LinearLayout>
+</layout>

+ 2 - 1
app/src/main/res/layout/layout_loading.xml

@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     style="@style/wrap"
     android:gravity="center"
     android:orientation="vertical"
+    android:paddingHorizontal="80dp"
+    android:paddingVertical="40dp"
     app:layout_constraintBottom_toBottomOf="parent"
     app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintStart_toStartOf="parent"

+ 27 - 12
app/src/main/res/values/strings.xml

@@ -1,13 +1,8 @@
 <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
     <string name="app_name">油站自助支付</string>
 
-    <string name="account_name">账号名</string>
-    <string name="password">密码</string>
-    <string name="login">登录</string>
-    <string name="preference">应用设置</string>
+    <!-- common -->
     <string name="blank"> </string>
-    <string name="back_desktop">退回桌面</string>
-
     <string name="start">开始</string>
     <string name="confirm">确认</string>
     <string name="cancel">取消</string>
@@ -18,14 +13,27 @@
     <string name="success">成功</string>
     <string name="finish">完成</string>
 
+    <string name="fail_behave_reason">%1$s失败:%2$s</string>
+
+    <string name="might_take_minutes">这可能需要花费一些时间,请您耐心等待。</string>
 
-    <!-- 请求部分 -->
+    <!-- login -->
+    <string name="account_name">账号名</string>
+    <string name="password">密码</string>
+    <string name="login">登录</string>
+
+    <!-- preference -->
+    <string name="preference">应用设置</string>
+    <string name="check_preference">检查应用设置</string>
+    <string name="back_desktop">退回桌面</string>
+
+    <!-- network -->
     <string name="response_null">响应为空</string>
     <string name="network_error_host_error">网络异常或者域名不正确</string>
     <string name="data_null">数据为空</string>
     <string name="fail_request">请求失败</string>
+    <string name="network_error">网络异常</string>
 
-    <string name="fail_behave_reason">%1$s失败:%2$s</string>
 
     <string name="fail_operate">操作失败</string>
     <string name="in_login">登录中&#8230;</string>
@@ -35,7 +43,6 @@
     <string name="login_tip">登录提示</string>
     <string name="login_tip_detail">请先登录</string>
     <string name="input_not_right">填写数据不正确</string>
-    <string name="network_error">网络异常</string>
     <string name="return_data_error">请求返回数据不正确</string>
     <string name="account_name_password_error">账号名或密码错误</string>
     <string name="bu_null">BU值为空</string>
@@ -72,9 +79,6 @@
     <string name="device_num_is">设备编码:%1$s</string>
     <string name="version_is">版本号:%1$s</string>
 
-    <string name="in_connect_fusion">正在连接中控系统&#8230;</string>
-    <string name="in_reconnect_fusion">正在重新连接中控系统&#8230;</string>
-    <string name="in_get_fuel">正在获取油品信息&#8230;</string>
     <string name="connect_fail_reason">连接中控系统异常:%1$s</string>
     <string name="fail_get_nozzle">获取枪油品信息失败</string>
     <string name="connect_timeout">连接中控超时</string>
@@ -145,6 +149,17 @@
     <string name="click_close">点击关闭</string>
     <string name="cant_find_local_file">未找到本地文件</string>
 
+    <!-- Fusion -->
+    <string name="fusion_in_connect">正在连接 Fusion…</string>
+    <string name="reconnect">重新连接</string>
+    <string name="connect_fusion_success">连接 Fusion 成功</string>
+    <string name="disconnect_fusion">无法连接到 Fusion</string>
+    <string name="fusion_login_failed">登录到 Fusion 失败…</string>
+    <string name="in_login_fusion">正在登录到 Fusion 中…</string>
+    <string name="in_get_fuel">正在获取油品信息…</string>
+    <string name="get_fuel_failed">获取油品信息失败</string>
+    <string name="get_fuel_success">获取油品信息成功</string>
+
     <string name="format_date">yyyy-MM-dd HH:mm:ss</string>
 
     <string name="toastSuccess">✔️ %1$s</string>

+ 4 - 1
waynelib_/src/main/java/com/wayne/www/waynelib/fdc/FdcClient.java

@@ -196,6 +196,7 @@ public class FdcClient implements OnFdcServiceResponseReceivedListener {
 
     public enum FdcClientState {
         // disconnected since timed out for receive other peer's heartbeat. need re-init the tcp to other peer.
+        // Robin's remark: client might reconnect successfully after a short delay. Failure is also possible.
         DisconnectedByHeartbeat,
         // connected to cashPos server.
         Connected,
@@ -203,7 +204,9 @@ public class FdcClient implements OnFdcServiceResponseReceivedListener {
         Connecting,
         //Fdc client fully stopped, and no connection will be made.
         Stopped,
-        MyAddReConnect,//LzPeng增加的状态,如果网络异常会去重新连接View
+        // Robin's remark: it means client has been closed if MyAddReConnect is triggered.
+        // 供应商 LzPeng 增加的状态,如果网络异常显示一个View,让用户手动重新连接
+        MyAddReConnect,
     }
 
     private FdcClient() {