Day07 - Login to Ptt

今天来处理登入的流程。

送出登入的方式很简单,使用WebSocketClient的send方法即可:

login.setOnClickListener {
    (requireActivity() as MainActivity).showLoading(R.string.startLogin) // 显示登入中的progress
    val id = idInput.text.toString() // 取得输入的帐号
    val pwd = pwdInput.text.toString() // 取得输入的密码
    PttClient.getInstance().send("$id\r\n$pwd\r\n".toByteArray(BIG5))
}

另外如我在Day04中提到的,Ptt预设是使用Big-5的文字编码,我也没做另外的尝试,所以这边在送出时也需要转成Big-5编码的byte array。

val BIG5 = Charset.forName("big5")!!

Ptt在登入时一般会有以下几种状况

  • 正常登入成功
    没任何状况的话,正常登入後会先显示以下画面
    https://ithelp.ithome.com.tw/upload/images/20210921/20124602S0AbgDHpai.png

  • 帐号或密码错误
    https://ithelp.ithome.com.tw/upload/images/20210921/20124602H19AbHgiHp.png

  • 成功但重复登入(原本已有帐号登入中)
    https://ithelp.ithome.com.tw/upload/images/20210921/20124602Nnln0iaCXa.png

  • 成功登入,先前有尝试登入但输入错密码的纪录
    https://ithelp.ithome.com.tw/upload/images/20210921/20124602tBWuBqjXAa.png

  • 最终登入完成的画面
    https://ithelp.ithome.com.tw/upload/images/20210921/20124602hEQZeqPzLq.png

针对这几种Case,我使用以下几个Pattern来监听帐密送出後的回传状况:

private val loginResultPattern = arrayOf(
    "请按任意键继续",
    "您想删除其他重复登入的连线吗?",
    "密码不对或无此帐号。请检查大小写及有无输入错误。",
    "您要删除以上错误尝试的记录吗?",
    "【主功能表】"
)

在上方帐密送出後,开启协程等待Ptt Server的回传。

viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
    while (true) {
        if (isWaitingForUserReply) continue
        when (PttClient.getInstance().expect(loginResultPattern)) {
            // ...
        }
    }
}

isWaitingForUserReply的用途为後续可能会有需使用者回覆的选项,在等待回覆时我不希望去呼叫到expect方法并且重复进入when中。後续内容皆在上述协程的when内

接下来需针对不同的Pattern回覆做对应处理

  • 正常登入成功
// ...
0 -> { // "请按任意键继续"
    if (hasPressedAnyKey) continue
    hasPressedAnyKey = true
    PttClient.getInstance().send("\r\n".toByteArray(BIG5))
    delay(500L)
}
// ...

直接送出enter并等待後续的回传,为了避免重复送出导致页面跑掉,所以这边多设了一个判断Flag。

  • 帐号或密码错误
// ...
2 -> { // "密码不对或无此帐号。请检查大小写及有无输入错误。"
    withContext(Dispatchers.Main) {
        (requireActivity() as MainActivity).dismissLoading()
        AlertDialog.Builder(requireContext())
            .setTitle("密码不对或无此帐号。")
            .setMessage("请检查大小写及有无输入错误。")
            .setPositiveButton(android.R.string.ok, null)
            .show()
    }
    break
}
// ...

跳出AlertDialog告知使用者、关闭loading状态,并且退出此协程。

  • 成功但重复登入(原本已有帐号登入中)
// ...
1 -> { // "您想删除其他重复登入的连线吗?[Y/n]"
    if (hasAskedForMultiUsers) continue
    hasAskedForMultiUsers = true
    isWaitingForUserReply = true
    withContext(Dispatchers.Main) {
        (requireActivity() as MainActivity).dismissLoading()
        AlertDialog.Builder(requireContext())
            .setTitle("注意: 您有其它连线已登入此帐号。")
            .setMessage("您想删除其他重复登入的连线吗?")
            .setPositiveButton("是") { _, _ ->
                (requireActivity() as MainActivity)
                    .showLoading(R.string.startLogin)
                PttClient.getInstance().send("y\r\n".toByteArray(BIG5))
                requireView().postDelayed(Runnable {
                    isWaitingForUserReply = false
                }, 1000L)
            }
            .setNegativeButton("否") { _, _ ->
                (requireActivity() as MainActivity)
                    .showLoading(R.string.startLogin)
                PttClient.getInstance().send("n\r\n".toByteArray(BIG5))
                requireView().postDelayed(Runnable {
                    isWaitingForUserReply = false
                }, 1000L)
            }
            .setCancelable(false)
            .show()
    }
}
// ...

这边就是先前提到需让使用者回覆的状态,关闭loading状态後跳出AlertDialog让使用者选择是否要踢出站上的帐号。
使用者选择後再把Loading状态开启并把isWaitingForUserReply设回false,由於Ptt处理这个动作会花比较多的时间,所以我是设1秒後再呼叫expect。
同样的为了确保不会有重复跳出Dialog的状况,所以多设了一个hasAskedForMultiUsers的Flag判断。

  • 成功登入,先前有尝试登入但输入错密码的纪录
// ...
3 -> { // "您要删除以上错误尝试的记录吗?"
    if (hasSentNotDeleteRecord) continue
    hasSentNotDeleteRecord = true
    PttClient.getInstance().send("n\r\n".toByteArray(BIG5))
    delay(1000L)
}
// ...

由於我并未显示尝试错误的内容出来,因此也不打算在我的App内把错误纪录删掉,於是直接回送"n"来保留纪录。

  • 最终登入完成的画面
// ...
4 -> { // "【主功能表】"
    withContext(Dispatchers.Main) {
        (requireActivity() as MainActivity).dismissLoading()
        NavHostFragment.findNavController(this@LoginFragment)
            .navigate(R.id.action_loginFragment_to_searchArticleFragment)
    }
    break
}
// ...

确认登入成功後就能进到下一页,准备搜寻文章了!


<<:  Day7 - 2D渲染环境基础篇 III[ 变形与阵列运算 ] - 成为Canvas Ninja ~ 理解2D渲染的精髓

>>:  [Day07] TS:什麽是 Utility Types?

6 用 GenServer 做 server?

GenServer 跟我们游戏有什麽关西? 我来试试看直接套用我们的场景来解释 GenServer ...

Day10 - 子元件透过 emit event 触发父元件事件

重新认识 Vue.js | Kuro Hsu 2-2 元件之间的沟通传递 元件与自订事件 paren...

【Day 16】深度学习(Deep Learning)--- Tip(一)

深度学习流程 我们知道深度学习是三个步骤,首先定义一个function set和structure,...

Day 02 网页和Blazor介绍

笔者对网站的认知为前端、後端及资料库,使用者在浏览器画面按下按钮或是送出表格,触发前端事件,将收集起...

[GMI/GMA] 透过移动装置连上 Genero Web App

至目前的章节为止,已经可以执行 Genero FGL的程序在 Windows/MAC/Linux ...