iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Mobile Development

花30天做個Android小專案系列 第 7

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
}
// ...

確認登入成功後就能進到下一頁,準備搜尋文章了!


上一篇
Day06 - Parsing Ptt(補充)
下一篇
Day08 - 尋找看板
系列文
花30天做個Android小專案30

尚未有邦友留言

立即登入留言