Przeglądaj źródła

feat 切换首页类型(50%)

RobinTan1024 4 lat temu
rodzic
commit
63f57b56f7
22 zmienionych plików z 388 dodań i 217 usunięć
  1. 8 3
      app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionManager.kt
  2. 1 0
      app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionStatus.kt
  3. 2 0
      app/src/main/java/com/doverfuelingsolutions/issp/utils/sp/SPKeys.kt
  4. 2 0
      app/src/main/java/com/doverfuelingsolutions/issp/utils/sp/SPUtil.kt
  5. 114 19
      app/src/main/java/com/doverfuelingsolutions/issp/view/MainActivity.kt
  6. 0 2
      app/src/main/java/com/doverfuelingsolutions/issp/view/PreferenceActivity.kt
  7. 3 10
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentBasic.kt
  8. 2 1
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentNozzle.kt
  9. 1 1
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentOrderList.kt
  10. 1 1
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPayScanCode.kt
  11. 1 1
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentPayType.kt
  12. 55 6
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentReconnect.kt
  13. 6 155
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentSelect.kt
  14. 75 0
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/router/FragmentRouter.kt
  15. 7 0
      app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/router/OnRouteChangeListener.kt
  16. 53 0
      app/src/main/java/com/doverfuelingsolutions/issp/view/util/LoginTokenRefresher.kt
  17. 5 2
      app/src/main/java/com/doverfuelingsolutions/issp/view/widget/FragmentToolbar.kt
  18. 11 2
      app/src/main/res/layout/fragment_reconnect.xml
  19. 10 0
      app/src/main/res/values/arrays.xml
  20. 2 0
      app/src/main/res/values/keys.xml
  21. 5 1
      app/src/main/res/values/strings.xml
  22. 24 13
      app/src/main/res/xml/root_preferences.xml

+ 8 - 3
app/src/main/java/com/doverfuelingsolutions/issp/fusion/FusionManager.kt

@@ -61,12 +61,17 @@ object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
         }
         when (state) {
             FdcClient.FdcClientState.Connected -> {
-                onFusionStatus?.onFusionStatus(FusionStatus.Connected)
                 coroutineIO.launch {
                     val deferred1 = async { loginToFusion() }
                     val deferred2 = async { getPumpInfo() }
-                    deferred1.await()
-                    deferred2.await()
+                    val result1 = deferred1.await()
+                    val result2 = deferred2.await()
+
+                    if (result1 && result2.success) {
+                        onFusionStatus?.onFusionStatus(FusionStatus.Connected)
+                    } else {
+                        onFusionStatus?.onFusionStatus(FusionStatus.ConnectedWithoutInfo)
+                    }
                 }
             }
             FdcClient.FdcClientState.Connecting -> {

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

@@ -2,6 +2,7 @@ package com.doverfuelingsolutions.issp.fusion
 
 enum class FusionStatus {
     Connected,
+    ConnectedWithoutInfo,
     Connecting,
     Disconnected,
 }

+ 2 - 0
app/src/main/java/com/doverfuelingsolutions/issp/utils/sp/SPKeys.kt

@@ -32,6 +32,8 @@ class SPKeys {
         val FUEL_IP: String = StringUtil.get(R.string.sp_fuel_ip)
         val FUEL_PORT: String = StringUtil.get(R.string.sp_fuel_port)
 
+        val ORDER_CHOOSE: String = StringUtil.get(R.string.sp_choose_order_way)
+
         val FUEL_BARCODE: String = StringUtil.get(R.string.sp_fuel_barcode)
         val ORDER_UNSETTLED: String = StringUtil.get(R.string.sp_order_unsettled)
         val ORDER_GENERATED: String = StringUtil.get(R.string.sp_order_generated)

+ 2 - 0
app/src/main/java/com/doverfuelingsolutions/issp/utils/sp/SPUtil.kt

@@ -84,6 +84,8 @@ object SPUtil {
             putString(SPKeys.FUEL_IP, WayneApiConfig.FUEL_IP_DEFAULT)
             putString(SPKeys.FUEL_PORT, WayneApiConfig.FUEL_PORT_DEFAULT)
 
+            putString(SPKeys.ORDER_CHOOSE, "all")
+
             commit()
         }
     }

+ 114 - 19
app/src/main/java/com/doverfuelingsolutions/issp/view/MainActivity.kt

@@ -9,7 +9,6 @@ import androidx.activity.viewModels
 import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.app.AppCompatActivity
 import androidx.databinding.DataBindingUtil
-import androidx.fragment.app.Fragment
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.lifecycleScope
@@ -23,19 +22,28 @@ 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.sp.SPUtil
 import com.doverfuelingsolutions.issp.view.fragment.FragmentReconnect
+import com.doverfuelingsolutions.issp.view.fragment.router.FragmentRouter
 import com.doverfuelingsolutions.issp.view.fragment.FragmentSelect
+import com.doverfuelingsolutions.issp.view.fragment.router.OnRouteChangeListener
 import com.doverfuelingsolutions.issp.view.util.LoadingDialogBuilder
+import com.doverfuelingsolutions.issp.view.util.LoginTokenRefresher
 import com.wayne.www.waynelib.fdc.FdcClient
 import com.youth.banner.adapter.BannerImageAdapter
 import com.youth.banner.holder.BannerImageHolder
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
 import java.io.File
 import java.util.*
 
 class MainActivity : AppCompatActivity(),
     View.OnClickListener,
-    OnFusionStatus {
+    View.OnLongClickListener,
+    OnFusionStatus,
+    OnRouteChangeListener {
 
     companion object {
         fun start(context: Context) {
@@ -44,22 +52,23 @@ class MainActivity : AppCompatActivity(),
     }
 
     private val binding: ActivityMainBinding by lazy {
-        DataBindingUtil.setContentView(
-            this,
-            R.layout.activity_main
-        )
+        DataBindingUtil.setContentView(this, R.layout.activity_main)
     }
     private val mainViewModel: MainViewModel by viewModels()
+    val fragmentRouter: FragmentRouter by lazy { FragmentRouter(this, R.id.fragmentBox) }
 
     private var dialogFusionLinking: AlertDialog? = null
-
     private var isBlockBackPress = false
 
+    private val mLoginTokenRefresher = LoginTokenRefresher()
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         binding.lifecycleOwner = this
         binding.mainViewModel = mainViewModel
         binding.handler = this
+        binding.clock.setOnLongClickListener(this)
+
 
         supportActionBar?.hide()
         initView()
@@ -67,6 +76,10 @@ class MainActivity : AppCompatActivity(),
 
         FusionManager.onFusionStatus = this
         lifecycle.addObserver(FusionManager)
+
+        fragmentRouter.setOnRouteChangeListener(this)
+
+        mLoginTokenRefresher.start(this)
     }
 
     override fun onResume() {
@@ -81,6 +94,7 @@ class MainActivity : AppCompatActivity(),
         super.onDestroy()
 
         FusionManager.onFusionStatus = null
+        mLoginTokenRefresher.stop()
     }
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -145,6 +159,16 @@ class MainActivity : AppCompatActivity(),
         }
     }
 
+    override fun onLongClick(v: View?): Boolean {
+        return when (v) {
+            binding.clock -> {
+                PreferenceActivity.startForResult(this)
+                true
+            }
+            else -> false
+        }
+    }
+
     override fun onFusionStatus(status: FusionStatus) {
         DFSLog.i("Fusion output status: ${status.name.toLowerCase(Locale.CHINESE)}")
         lifecycleScope.launch {
@@ -154,15 +178,23 @@ class MainActivity : AppCompatActivity(),
                     setFusionLinkingLoading()
                 }
                 FusionStatus.Connected -> {
+                    // TODO 直接油枪模式
                     DFSToastUtil.success(R.string.connect_fusion_success)
-                    setFragment(FragmentSelect())
+                    fragmentRouter.push(FragmentSelect())
+                }
+                FusionStatus.ConnectedWithoutInfo -> {
+                    DFSToastUtil.fail(R.string.get_fuel_failed)
+                    fragmentRouter.push(FragmentReconnect.build {
+                        DFSToastUtil.success(R.string.get_fuel_success)
+                        fragmentRouter.push(FragmentSelect())
+                    })
                 }
                 FusionStatus.Disconnected -> {
                     // Disconnected reason
                     // 1. Network problem.
                     // 2. Communication exception and failed retry.
                     DFSToastUtil.fail(R.string.disconnect_fusion)
-                    setFragment(FragmentReconnect.build {
+                    fragmentRouter.push(FragmentReconnect.build {
                         setFusionLinkingLoading()
                     })
                 }
@@ -170,6 +202,21 @@ class MainActivity : AppCompatActivity(),
         }
     }
 
+    override fun onRouteForward() {
+        mLoginTokenRefresher.stop()
+    }
+
+    override fun onRouteBackward(isBottom: Boolean) {
+        if (isBottom) {
+            mLoginTokenRefresher.start(this)
+            handleUnsolvedOrder()
+        }
+    }
+
+    fun blockBackPress(isBlock: Boolean) {
+        isBlockBackPress = isBlock
+    }
+
     private fun initView() {
         mainViewModel.stationName.value = GlobalData.businessName.get()
         if (GlobalData.stationLogoFileName.get().isNotEmpty()) {
@@ -209,18 +256,66 @@ class MainActivity : AppCompatActivity(),
         }
     }
 
-    fun setFragment(fragment: Fragment, back: Boolean = false) {
-        supportFragmentManager.beginTransaction().run {
-            replace(R.id.fragmentBox, fragment)
-            if (back) {
-                addToBackStack(fragment::class.java.simpleName)
+    // TODO 当本地未处理订单无法按预期处理时(如本地锁的单,但在服务器上被其他渠道解锁了,造成解锁失败;消单同理;)
+    private fun handleUnsolvedOrder() {
+        GlobalScope.launch(Dispatchers.IO) {
+            val lockList = SPUtil.getLockOrderList()
+            val unclearedList = SPUtil.getUnclearedOrderList()
+            // 解锁订单
+            lockList.let { list ->
+                if (list.isEmpty()) return@let
+
+                val deferredList = list.map { item ->
+                    val info = item.split("-")
+                    val pumpId = info[0].toInt()
+                    val transactionNo = info[1]
+                    val token = info[2].toInt()
+                    async {
+                        FusionManager.lockOrder(pumpId, transactionNo, token, false)
+                    }
+                }
+                val resultList = deferredList.map { item -> item.await() }
+                val remainLockList = resultList.mapIndexed { index, result ->
+                    val tip = "auto unlock order ${list[index]}"
+                    if (result.success) {
+                        DFSLog.d("$tip succeeded")
+                        null
+                    } else {
+                        DFSLog.d("$tip failed")
+                        list[index]
+                    }
+                }.filterNotNull()
+                SPUtil.replaceLockOrderList(remainLockList)
             }
-            commit()
-        }
-    }
+            // 消除订单
+            unclearedList.let { list ->
+                if (list.isEmpty()) return@let
 
-    fun blockBackPress(isBlock: Boolean) {
-        isBlockBackPress = isBlock
+                val deferredList = list.map { item ->
+                    val info = item.split("-")
+                    val pumpId = info[0].toInt()
+                    val transactionNo = info[1]
+                    val token = info[2].toInt()
+                    async {
+                        FusionManager.clearOrder(pumpId, transactionNo, token)
+                    }
+                }
+                val resultList = deferredList.map { item -> item.await() }
+                val remainLockList = resultList.mapIndexed { index, result ->
+                    val tip = "auto clear order ${list[index]}"
+                    if (result.success) {
+                        DFSLog.d("$tip succeeded")
+                        null
+                    } else {
+                        DFSLog.d("$tip failed")
+                        list[index]
+                    }
+                }.filterNotNull()
+                SPUtil.replaceUnclearedOrderList(remainLockList)
+            }
+            // 删除过期云订单
+            SPUtil.removeExpiredWayneOrder()
+        }
     }
 
     class MainViewModel : ViewModel() {

+ 0 - 2
app/src/main/java/com/doverfuelingsolutions/issp/view/PreferenceActivity.kt

@@ -80,8 +80,6 @@ class PreferenceActivity : AppCompatActivity(),
         } else {
             isForResult = false
         }
-
-        BuglyUtil.checkUpgrade()
     }
 
     override fun onResume() {

+ 3 - 10
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentBasic.kt

@@ -3,9 +3,9 @@ package com.doverfuelingsolutions.issp.view.fragment
 import androidx.annotation.StringRes
 import androidx.appcompat.app.AlertDialog
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
 import com.doverfuelingsolutions.issp.view.widget.FragmentToolbar
 import com.doverfuelingsolutions.issp.utils.StringUtil
+import com.doverfuelingsolutions.issp.view.MainActivity
 import com.doverfuelingsolutions.issp.view.util.LoadingDialogBuilder
 
 abstract class FragmentBasic : Fragment() {
@@ -28,17 +28,10 @@ abstract class FragmentBasic : Fragment() {
     }
 
     fun home() {
-        val fragmentManager = requireActivity().supportFragmentManager
-        val deep = fragmentManager.backStackEntryCount
-        if (deep > 0)
-            fragmentManager.popBackStack(
-                fragmentManager.getBackStackEntryAt(0).id,
-                FragmentManager.POP_BACK_STACK_INCLUSIVE
-            )
+        (requireActivity() as MainActivity).fragmentRouter.popBottom()
     }
 
     fun back() {
-        requireActivity().supportFragmentManager.popBackStack()
-
+        (requireActivity() as MainActivity).fragmentRouter.pop()
     }
 }

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

@@ -47,6 +47,7 @@ class FragmentNozzle private constructor(private val pumpList: List<PumpInfo>) :
             binding.smartRefreshLayout.setRefreshHeader(ClassicsHeader(context))
             binding.smartRefreshLayout.setOnRefreshListener { handleNozzleRefresh() }
 
+            // binding.fragmentToolbar.countdownTime = -1
             setToolBar(binding.fragmentToolbar)
         }
         return binding.root
@@ -72,7 +73,7 @@ class FragmentNozzle private constructor(private val pumpList: List<PumpInfo>) :
                 nozzleInfo.physicalId.toString()
             } == nozzleList
         }?.let {
-            (activity as MainActivity).setFragment(FragmentOrderList.build(it.pumpId), true)
+            (activity as MainActivity).fragmentRouter.push(FragmentOrderList.build(it.pumpId))
         }
     }
 }

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

@@ -89,7 +89,7 @@ class FragmentOrderList private constructor(private val pumpId: Int) : FragmentB
                         DFSToastUtil.warn(R.string.order_lock_by_other)
                     } else {
                         selectedPosition = position
-                        (activity as MainActivity).setFragment(FragmentPayType.build(dc), true)
+                        (activity as MainActivity).fragmentRouter.push(FragmentPayType.build(dc))
                     }
                 }
                 type == 1 && dc.isLock -> {

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

@@ -107,7 +107,7 @@ class FragmentPayScanCode(private val dc: DeviceClass, private val posTrx: PosTr
                     }
                 }
 
-                (activity as MainActivity).setFragment(FragmentPayResult.build(dc, result.data.PosTrx), true)
+                (activity as MainActivity).fragmentRouter.push(FragmentPayResult.build(dc, result.data.PosTrx))
             } else {
                 // TODO 倒计时不足的问题
                 DFSToastUtil.fail(result.message)

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

@@ -87,7 +87,7 @@ class FragmentPayType private constructor(private val dc: DeviceClass): Fragment
             if (resultLock.success) {
                 dc.state = "Locked"
                 SPUtil.addLockOrder(dc)
-                (activity as MainActivity).setFragment(FragmentPayScanCode.build(dc, posTrx), true)
+                (activity as MainActivity).fragmentRouter.push(FragmentPayScanCode.build(dc, posTrx))
             } else {
                 DFSToastUtil.fail(StringUtil.get(R.string.fail_behave_reason, StringUtil.get(R.string.lock_order), resultLock.message))
             }

+ 55 - 6
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentReconnect.kt

@@ -5,29 +5,43 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.databinding.DataBindingUtil
-import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
 import com.doverfuelingsolutions.issp.R
 import com.doverfuelingsolutions.issp.databinding.FragmentReconnectBinding
 import com.doverfuelingsolutions.issp.fusion.FusionManager
+import com.doverfuelingsolutions.issp.utils.DFSToastUtil
 import com.doverfuelingsolutions.issp.view.PreferenceActivity
+import com.wayne.www.waynelib.fdc.FdcClient
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
 
-class FragmentReconnect private constructor(private val reconnectHandler: () -> Unit) : Fragment(), View.OnClickListener {
+class FragmentReconnect private constructor(
+    private val handler: () -> Unit,
+) : FragmentBasic(), View.OnClickListener {
 
     companion object {
 
-        fun build(reconnectHandler: () -> Unit): FragmentReconnect {
-            return FragmentReconnect(reconnectHandler)
+        fun build(handler: () -> Unit): FragmentReconnect {
+            return FragmentReconnect(handler)
         }
     }
 
     private lateinit var binding: FragmentReconnectBinding
 
-    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_reconnect, container, false)
             binding.lifecycleOwner = viewLifecycleOwner
             binding.handler = this
+
+            if (FusionManager.stateFusion == FdcClient.FdcClientState.Stopped || FusionManager.stateFusion == FdcClient.FdcClientState.MyAddReConnect) {
+                binding.buttonReconnect.visibility = View.VISIBLE
+                binding.buttonFetchInfo.visibility = View.GONE
+            } else {
+                binding.buttonReconnect.visibility = View.GONE
+                binding.buttonFetchInfo.visibility = View.VISIBLE
+            }
         }
         return binding.root
     }
@@ -36,11 +50,46 @@ class FragmentReconnect private constructor(private val reconnectHandler: () ->
         when (v) {
             binding.buttonReconnect -> {
                 FusionManager.restart(true)
-                reconnectHandler.invoke()
+                handler.invoke()
+            }
+            binding.buttonFetchInfo -> {
+                lifecycleScope.launch {
+                    val dialog = loading(R.string.in_get_fuel)
+                    if (checkFusion()) {
+                        handler.invoke()
+                    } else {
+                        DFSToastUtil.fail(R.string.get_fuel_failed)
+                    }
+                    dialog.dismiss()
+                }
             }
             binding.buttonPreference -> {
                 PreferenceActivity.startForResult(requireActivity())
             }
         }
     }
+
+    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
+    }
 }

+ 6 - 155
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentSelect.kt

@@ -4,27 +4,17 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import androidx.core.os.bundleOf
 import androidx.databinding.DataBindingUtil
 import androidx.fragment.app.viewModels
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
-import androidx.lifecycle.lifecycleScope
 import com.doverfuelingsolutions.issp.DFSApplication
 import com.doverfuelingsolutions.issp.R
-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.StringUtil
-import com.doverfuelingsolutions.issp.utils.log.DFSLog
-import com.doverfuelingsolutions.issp.utils.sp.SPUtil
-import com.doverfuelingsolutions.issp.view.LoginActivity
 import com.doverfuelingsolutions.issp.view.MainActivity
 import com.doverfuelingsolutions.issp.view.PreferenceActivity
-import kotlinx.android.synthetic.main.layout_loading.*
-import kotlinx.coroutines.*
 
 class FragmentSelect : FragmentBasic(),
     View.OnLongClickListener,
@@ -33,8 +23,6 @@ class FragmentSelect : FragmentBasic(),
     private lateinit var binding: FragmentSearchTypeBinding
     private val viewModel: SearchTypeViewModel by viewModels()
 
-    private var job: Job? = null
-
     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)
@@ -47,32 +35,13 @@ class FragmentSelect : FragmentBasic(),
         return binding.root
     }
 
-    override fun onStart() {
-        super.onStart()
-
-        handleUnsolvedOrder()
-        refreshLoginToken()
-    }
-
-    override fun onStop() {
-        super.onStop()
-
-        job?.let {
-            if (it.isActive) it.cancel()
-        }
-    }
-
     override fun onClick(v: View?) {
-        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)
-                }
+        when (v) {
+            binding.chooseNozzle -> {
+                (activity as MainActivity).fragmentRouter.push(FragmentNozzle.build(FusionManager.pumpList))
+            }
+            binding.chooseOil -> {
+                (activity as MainActivity).fragmentRouter.push(FragmentOrderList.build(-1))
             }
         }
     }
@@ -87,124 +56,6 @@ class FragmentSelect : FragmentBasic(),
         }
     }
 
-    // TODO 当本地未处理订单无法按预期处理时(如本地锁的单,但在服务器上被其他渠道解锁了,造成解锁失败;消单同理;)
-    private fun handleUnsolvedOrder() {
-        GlobalScope.launch(Dispatchers.IO) {
-            val lockList = SPUtil.getLockOrderList()
-            val unclearedList = SPUtil.getUnclearedOrderList()
-            // 解锁订单
-            lockList.let { list ->
-                if (list.isEmpty()) return@let
-
-                val deferredList = list.map { item ->
-                    val info = item.split("-")
-                    val pumpId = info[0].toInt()
-                    val transactionNo = info[1]
-                    val token = info[2].toInt()
-                    async {
-                        FusionManager.lockOrder(pumpId, transactionNo, token, false)
-                    }
-                }
-                val resultList = deferredList.map { item -> item.await() }
-                val remainLockList = resultList.mapIndexed { index, result ->
-                    val tip = "auto unlock order ${list[index]}"
-                    if (result.success) {
-                        DFSLog.d("$tip succeeded")
-                        null
-                    } else {
-                        DFSLog.d("$tip failed")
-                        list[index]
-                    }
-                }.filterNotNull()
-                SPUtil.replaceLockOrderList(remainLockList)
-            }
-            // 消除订单
-            unclearedList.let { list ->
-                if (list.isEmpty()) return@let
-
-                val deferredList = list.map { item ->
-                    val info = item.split("-")
-                    val pumpId = info[0].toInt()
-                    val transactionNo = info[1]
-                    val token = info[2].toInt()
-                    async {
-                        FusionManager.clearOrder(pumpId, transactionNo, token)
-                    }
-                }
-                val resultList = deferredList.map { item -> item.await() }
-                val remainLockList = resultList.mapIndexed { index, result ->
-                    val tip = "auto clear order ${list[index]}"
-                    if (result.success) {
-                        DFSLog.d("$tip succeeded")
-                        null
-                    } else {
-                        DFSLog.d("$tip failed")
-                        list[index]
-                    }
-                }.filterNotNull()
-                SPUtil.replaceUnclearedOrderList(remainLockList)
-            }
-            // 删除过期云订单
-            SPUtil.removeExpiredWayneOrder()
-        }
-    }
-
-    private fun refreshLoginToken() {
-        // 登录有效时长两小时,为了避免在操作过程出现接口验证失败
-        job = lifecycleScope.launch {
-            // 8min 余留,避免边缘时刻进入流程
-            val expireMoment = GlobalData.accessTokenExpire.get()
-            val delayTime = expireMoment - 480000 - System.currentTimeMillis()
-            if (delayTime > 0) {
-                delay(delayTime)
-            }
-            val result = SystemApi.login(GlobalData.accountName.get(), GlobalData.password.get())
-            if (result.success) {
-                DFSLog.d("loginToken refresh succeeded")
-                // 处理持续停留
-                refreshLoginToken()
-            } else {
-                DFSLog.d("loginToken refresh failed: ${result.message}")
-                GlobalData.isLogin = false
-                val msg = StringUtil.get(R.string.fail_refresh_login)
-                DFSToastUtil.fail(msg)
-                // 刷新失败返回登录界面
-                (activity as MainActivity).finish()
-                LoginActivity.start(
-                    requireContext(),
-                    bundleOf(
-                        Pair(LoginActivity.autoLogin, false),
-                        Pair(LoginActivity.loginMessage, msg),
-                    )
-                )
-            }
-        }
-    }
-
-    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 {

+ 75 - 0
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/router/FragmentRouter.kt

@@ -0,0 +1,75 @@
+package com.doverfuelingsolutions.issp.view.fragment.router
+
+import android.content.Context
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+import com.doverfuelingsolutions.issp.utils.log.DFSLog
+
+class FragmentRouter(
+    private val activity: AppCompatActivity,
+    private val fragmentViewId: Int,
+) : LifecycleObserver {
+
+    private var isNavigateForward = true
+    private var mOnRouteChangeListener: OnRouteChangeListener? = null
+    private val fragmentList = arrayListOf<Fragment>()
+    private val fragmentEvent = object : FragmentManager.FragmentLifecycleCallbacks() {
+        override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {
+            isNavigateForward = true
+            fragmentList.add(f)
+            mOnRouteChangeListener?.onRouteForward()
+        }
+
+        override fun onFragmentDetached(fm: FragmentManager, f: Fragment) {
+            isNavigateForward = false
+            fragmentList.remove(f)
+            mOnRouteChangeListener?.onRouteBackward(fragmentList.size <= 1)
+        }
+    }
+
+    init { activity.lifecycle.addObserver(this) }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    private fun create() {
+        activity.supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentEvent, false)
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    private fun destroy() {
+        mOnRouteChangeListener = null
+        activity.supportFragmentManager.unregisterFragmentLifecycleCallbacks(fragmentEvent)
+    }
+
+    fun isForward() = isNavigateForward
+    fun isBackward() = !isNavigateForward
+
+    fun push(fragment: Fragment) {
+        activity.supportFragmentManager.beginTransaction().run {
+            replace(fragmentViewId, fragment)
+            if (fragmentList.isNotEmpty()) addToBackStack(fragment.javaClass.simpleName)
+            commit()
+        }
+    }
+
+    fun pop() : Boolean {
+        if (fragmentList.size > 1) {
+            activity.supportFragmentManager.popBackStack()
+            return true
+        }
+
+        return false
+    }
+
+    fun popBottom() {
+        if (fragmentList.size > 1)
+            activity.supportFragmentManager.popBackStack(fragmentList[1].javaClass.simpleName, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+    }
+
+    fun setOnRouteChangeListener(listener: OnRouteChangeListener) {
+        mOnRouteChangeListener = listener
+    }
+}

+ 7 - 0
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/router/OnRouteChangeListener.kt

@@ -0,0 +1,7 @@
+package com.doverfuelingsolutions.issp.view.fragment.router
+
+interface OnRouteChangeListener {
+
+    fun onRouteForward() {}
+    fun onRouteBackward(isBottom: Boolean) {}
+}

+ 53 - 0
app/src/main/java/com/doverfuelingsolutions/issp/view/util/LoginTokenRefresher.kt

@@ -0,0 +1,53 @@
+package com.doverfuelingsolutions.issp.view.util
+
+import android.app.Activity
+import androidx.core.os.bundleOf
+import com.doverfuelingsolutions.issp.R
+import com.doverfuelingsolutions.issp.api.SystemApi
+import com.doverfuelingsolutions.issp.data.GlobalData
+import com.doverfuelingsolutions.issp.utils.DFSToastUtil
+import com.doverfuelingsolutions.issp.utils.StringUtil
+import com.doverfuelingsolutions.issp.utils.log.DFSLog
+import com.doverfuelingsolutions.issp.view.LoginActivity
+import kotlinx.coroutines.*
+
+class LoginTokenRefresher {
+
+    private var job: Job? = null
+
+    fun start(activity: Activity) {
+        // LoginToken is valid only for two hours and needs to be refreshed.
+        job = GlobalScope.launch(Dispatchers.IO) {
+            // 8 minutes left for payment process.
+            val expireMoment = GlobalData.accessTokenExpire.get()
+            val delayTime = expireMoment - 480_000 - System.currentTimeMillis()
+            if (delayTime > 0) delay(delayTime)
+            val result = SystemApi.login(GlobalData.accountName.get(), GlobalData.password.get())
+            if (result.success) {
+                DFSLog.d("loginToken refresh succeeded")
+                // In case view remains for more than 2 hours.
+                start(activity)
+            } else {
+                DFSLog.d("loginToken refresh failed: ${result.message}")
+                GlobalData.isLogin = false
+                val msg = StringUtil.get(R.string.fail_refresh_login)
+                DFSToastUtil.fail(msg)
+                // Back to login.
+                activity.finish()
+                LoginActivity.start(
+                    activity,
+                    bundleOf(
+                        Pair(LoginActivity.autoLogin, false),
+                        Pair(LoginActivity.loginMessage, msg),
+                    )
+                )
+            }
+        }
+    }
+
+    fun stop() {
+        job?.let {
+            if (it.isActive) it.cancel()
+        }
+    }
+}

+ 5 - 2
app/src/main/java/com/doverfuelingsolutions/issp/view/widget/FragmentToolbar.kt

@@ -37,7 +37,11 @@ class FragmentToolbar(context: Context, attrs: AttributeSet) : FrameLayout(conte
         }
 
     // 当值=-1时,不启用倒计时
-    private val countdownTime: Int
+    var countdownTime: Int = 60
+        set(value) {
+            field = value
+            isTimerVisible = value > 0
+        }
     private var currentTime: Int = 0
     private var countdownAutoStart = true
     private var countdownEvent: (() -> Unit)? = null
@@ -68,7 +72,6 @@ class FragmentToolbar(context: Context, attrs: AttributeSet) : FrameLayout(conte
             buttonHome.playSoundEffect(SoundEffectConstants.CLICK)
             homeHandler?.invoke()
         }
-        if (countdownTime <= 0) isTimerVisible = false
     }
 
     private lateinit var job: Job

+ 11 - 2
app/src/main/res/layout/fragment_reconnect.xml

@@ -18,17 +18,26 @@
             android:layout_width="400dp"
             android:layout_height="400dp"
             android:scaleType="centerCrop"
-            android:src="@drawable/ic_network_error" />
+            android:layout_marginBottom="120dp"
+            android:src="@drawable/ic_network_error"
+            android:contentDescription="@string/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/buttonFetchInfo"
+            style="@style/Widget.MaterialComponents.Button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:onClick="@{handler}"
+            android:text="@string/re_fetch_fusion_info" />
+
         <Button
             android:id="@+id/buttonPreference"
             style="@style/Widget.MaterialComponents.Button.TextButton"

+ 10 - 0
app/src/main/res/values/arrays.xml

@@ -9,4 +9,14 @@
         <item>reply</item>
         <item>reply_all</item>
     </string-array>
+
+    <string-array name="order_choose_names">
+        <item>枪号查询 + 油品查询</item>
+        <item>枪号查询</item>
+    </string-array>
+
+    <string-array name="order_choose_values">
+        <item>all</item>
+        <item>nozzle</item>
+    </string-array>
 </resources>

+ 2 - 0
app/src/main/res/values/keys.xml

@@ -28,4 +28,6 @@
     <string name="sp_order_uncleared">order_uncleared</string>
     <string name="sp_payment_id">payment_id</string>
     <string name="sp_payment_name">payment_name</string>
+
+    <string name="sp_choose_order_way">choose_order_way</string>
 </resources>

+ 5 - 1
app/src/main/res/values/strings.xml

@@ -56,6 +56,7 @@
     <string name="tip_opt_fail">操作失败</string>
     <string name="data_refresh">数据已刷新</string>
 
+    <!-- 配置 -->
     <string name="device_info">设备信息</string>
     <string name="device_hardware_sn">设备硬件编码</string>
     <string name="device_sn">设备SN编码</string>
@@ -74,6 +75,8 @@
     <string name="ip_address">IP地址</string>
     <string name="port">端口</string>
     <string name="not_set">未设置</string>
+    <string name="preference_settings">偏好设置</string>
+    <string name="order_choose_way">订单查询方式</string>
 
     <string name="select_search">请选择加油支付</string>
     <string name="select_oil">选择油品</string>
@@ -169,7 +172,8 @@
     <!-- Fusion -->
     <string name="fusion_in_connect">正在连接 Fusion…</string>
     <string name="reconnect">重新连接</string>
-    <string name="reconnect_fusion_tip">与 Fusion 的连接似乎不太稳定,正常尝试重连…</string>
+    <string name="re_fetch_fusion_info">重新加载信息</string>
+    <string name="reconnect_fusion_tip">正常尝试重连 Fusion …</string>
     <string name="connect_fusion_success">连接 Fusion 成功</string>
     <string name="disconnect_fusion">无法连接到 Fusion</string>
     <string name="fusion_login_failed">登录到 Fusion 失败…</string>

+ 24 - 13
app/src/main/res/xml/root_preferences.xml

@@ -1,16 +1,17 @@
-<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <PreferenceCategory app:title="@string/device_info">
 
         <EditTextPreference
-            app:key="@string/sp_sn_raw"
             app:enabled="false"
+            app:key="@string/sp_sn_raw"
             app:title="@string/device_hardware_sn"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
-            app:key="@string/sp_sn"
             app:enabled="false"
+            app:key="@string/sp_sn"
             app:title="@string/device_sn"
             app:useSimpleSummaryProvider="true" />
     </PreferenceCategory>
@@ -18,70 +19,80 @@
     <PreferenceCategory app:title="@string/cloud_info">
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_cloud_info"
             app:key="@string/sp_server_domain"
             app:title="@string/cloud_domain"
-            app:dialogTitle="@string/hint_cloud_info"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_port_input"
             app:key="@string/sp_server_port_base"
             app:title="@string/port_auth"
-            app:dialogTitle="@string/hint_port_input"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_port_input"
             app:key="@string/sp_server_port_trx"
             app:title="@string/port_trx"
-            app:dialogTitle="@string/hint_port_input"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_port_input"
             app:key="@string/sp_server_port_config"
             app:title="@string/port_config"
-            app:dialogTitle="@string/hint_port_input"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_port_input"
             app:key="@string/sp_server_port_assets"
             app:title="@string/port_assets"
-            app:dialogTitle="@string/hint_port_input"
             app:useSimpleSummaryProvider="true" />
     </PreferenceCategory>
 
     <PreferenceCategory app:title="@string/middle_info">
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_ip_input"
             app:key="@string/sp_middle_ip"
             app:title="@string/ip_address"
-            app:dialogTitle="@string/hint_ip_input"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_port_input"
             app:key="@string/sp_middle_port"
             app:title="@string/port"
-            app:dialogTitle="@string/hint_port_input"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_port_input"
             app:key="@string/sp_middle_workstation_id"
             app:title="@string/workstation_id"
-            app:dialogTitle="@string/hint_port_input"
             app:useSimpleSummaryProvider="true" />
     </PreferenceCategory>
 
     <PreferenceCategory app:title="@string/fuel_info">
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_ip_input"
             app:key="@string/sp_fuel_ip"
             app:title="@string/ip_address"
-            app:dialogTitle="@string/hint_ip_input"
             app:useSimpleSummaryProvider="true" />
 
         <EditTextPreference
+            app:dialogTitle="@string/hint_port_input"
             app:key="@string/sp_fuel_port"
             app:title="@string/port"
-            app:dialogTitle="@string/hint_port_input"
             app:useSimpleSummaryProvider="true" />
     </PreferenceCategory>
 
+    <PreferenceCategory app:title="@string/preference_settings">
+
+        <ListPreference
+            android:entries="@array/order_choose_names"
+            android:entryValues="@array/order_choose_values"
+            app:defaultValue="all"
+            app:key="@string/sp_choose_order_way"
+            app:title="@string/order_choose_way"
+            app:useSimpleSummaryProvider="true" />
+    </PreferenceCategory>
 </PreferenceScreen>