Day27 - 登出及连线中断

今天来做登出的功能以及连线中断的处理。

Navigation Action

不论是登出还是中断连线,在App中我最後都会希望回到登入画面,因此在nav_main.xml中增加了Action:

<action
    android:id="@+id/action_to_loginFragment"
    app:destination="@id/loginFragment"
    app:enterAnim="@anim/nav_default_enter_anim"
    app:exitAnim="@anim/nav_default_exit_anim"
    app:popEnterAnim="@anim/nav_default_pop_enter_anim"
    app:popExitAnim="@anim/nav_default_pop_exit_anim"
    app:popUpTo="@id/searchArticleFragment"
    app:popUpToInclusive="true" />

此Action为Global Action,是放在navigation tag下而非fragment tag。

登出

登出发生的时间点比较单纯,预期是在SearchArticleFragment页面点击返回按钮的时候,所以先用onBackPressedDispatcher拦截返回事件并在里面跳出Dialog询问是否登出:

// ...
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,
    object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            AlertDialog.Builder(requireActivity())
                .setTitle("登出")
                .setMessage("是否登出Ptt?")
                .setPositiveButton(android.R.string.ok) { _, _ ->
                    //TODO: Logout Process
                }
                .setNegativeButton(android.R.string.cancel, null)
                .show()
        }
    })
// ...

Logout Process

// ...
val activity = (requireActivity() as MainActivity)
activity.showLoading("Logout...")
PttClient.getInstance().end()
activity.startPttClient {
    viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
        activity.dismissLoading()
        if (it == 0) {
            NavHostFragment.findNavController(this@SearchArticleFragment)
                .navigate(R.id.action_to_loginFragment)
        } else {
            activity.showConnectionErrorDialog()
        }
    }
}
// ...

可以看到登出的部分我不打算走Ptt的指令流程。直接将WebSocket连线切断後重连就好了。MainActivity.startPttClient是稍微改写WelcomeFragment页的程序码,并将PttClient.expect的值做为callback回传。

MainActivity.startPttClient

public val startPattern = arrayOf(
    "请输入代号,或以 guest 参观,或以 new 注册:"
)

public fun startPttClient(callback: ((Int) -> Unit)) {
    lifecycleScope.launch(Dispatchers.IO) {
        delay(500)
        PttClient.getInstance().start()
        callback(PttClient.getInstance().expect(startPattern))
    }
}

这边做完後WelcomeFragment使用到的地方也一起做了修改:

private fun startPttClient() {
    if (checkOverlayDisplayPermission()) {
        (requireActivity() as MainActivity).startPttClient {
            viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
                delay(500)
                withContext(Dispatchers.Main) {
                    if (it == 0) {
                        val extras = FragmentNavigator.Extras.Builder()
                            .addSharedElement(binding.logo, "logo")
                            .build()

                        NavHostFragment.findNavController(this@WelcomeFragment)
                            .navigate(
                                R.id.action_welcomeFragment_to_loginFragment,
                                null,
                                null,
                                extras
                            )
                    } else {
                        (requireActivity() as MainActivity).showConnectionErrorDialog()
                    }
                }
            }
        }
    } else {
        requestOverlayDisplayPermission()
    }
}

连线中断

连线中断的模拟我是使用另外登入同帐号的方法。

首先在PttClient中加入判断用的Flag和Callback

companion object {
    // ...
    private var isClosedByUser = false
    public var unexpectedClosedCallback: (() -> Unit)? = null
    // ...
}

isClosedByUser flag是用来标注这次的onClose事件是否是发生在App主动断线(如:登出)时:

public fun start() {
    // ...
    isClosedByUser = false
    // ...
}
public fun end() {
    isClosedByUser = true
    // ...
}

并且在WebSocketClientonClose中呼叫unexpectedClosedCallback

override fun onClose(code: Int, reason: String?, remote: Boolean) {
    if (!isClosedByUser) {
        unexpectedClosedCallback?.invoke()
    }
}

在MainActivty中赋值unexpectedClosedCallback

PttClient.unexpectedClosedCallback = {
    lifecycleScope.launch(Dispatchers.Main) {
        AlertDialog.Builder(this@MainActivity)
            .setTitle("Disconnected")
            .setMessage("Reconnect to Ptt?")
            .setPositiveButton(android.R.string.ok) { _, _ ->
                showLoading("Reconnecting...")
                PttClient.getInstance().end()
                startPttClient {
                    lifecycleScope.launch(Dispatchers.Main) {
                        dismissLoading()
                        if (it == 0) {
                            findNavController(R.id.nav_host_fragment)
                                .navigate(R.id.action_to_loginFragment)
                        } else {
                            showConnectionErrorDialog()
                        }
                    }
                }
            }
            .setNegativeButton(android.R.string.cancel) { _, _ -> finish() }
            .setCancelable(false)
            .show()
    }
}

基本上就是走关闭→重启→回到登入页的流程,若不重连线或是再连线失败的话就关闭App。

在ObserveService中赋值unexpectedClosedCallback

如果是在悬浮视窗中断线的话,我选择的做法是关闭Service并发送Notification告知使用者连线中断。
以下内容写在ObserveServiceonCreate方法中:

PttClient.unexpectedClosedCallback = {
    val manager = NotificationManagerCompat.from(this)
    if (manager.getNotificationChannel(notificationChannelIdImportant) == null) {
        val channel = NotificationChannelCompat.Builder(
            notificationChannelIdImportant,
            NotificationManager.IMPORTANCE_HIGH
        ).setName("CA_HIGH").build()
        manager.createNotificationChannel(channel)
    }

    val builder = NotificationCompat.Builder(this, notificationChannelIdImportant)
        .setSmallIcon(R.drawable.ic_outline_track)
        .setContentTitle("Ptt connection has closed")
    manager.notify(notificationIdImportant, builder.build())
    stopSelf()
}

今日功能画面

今天测试的时间比较长,所以使用了三倍速播放,看不清楚又很有兴趣的话,就麻烦多看几次罗...XD
https://i.imgur.com/UkqU6TB.gif


<<:  [Day28] Flutter with GetX Socket.io

>>:  Day 27 LeetCode 212. Word Search II

Mind Map 与 Roadmap

Mindmap (思维导图、心智图) 是一个被广泛用在任何领域上的一种作图法,可以帮我们归类思绪、分...

[day-19] 认识Python的资料结构!(Part .6)

再熟悉不过的字串,也算是资料结构?   我们常常使用的字串,也算是一种有顺序关系的「序列容器」,因此...

IOS、Python自学心得30天 Day-17 learning rate

前言: 经过多次的测试和训练 val_accuracy 在最後几乎都是处於0.6500左右的状态 所...

Twinkle Tray 多显示器屏幕亮度调节工具

Twinkle Tray是一款支持多显示器的屏幕亮度调节工具,让你可以在一块屏幕上调节所有的显示器亮...

第一位华人Tableau Zen Master 经验分享:成功管控Tableau的三部曲

在11/19 Tableau有一场线上的大中华区直播分享会,不知道大家有没有追里面的内容? 以下分享...