iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Mobile Development

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

Day08 - 尋找看板

今天來做搜尋看板的部分,首先Layout我先簡單的放一個EditText以及Button,點擊Button後將輸入的內容送出做搜尋:

Layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SearchArticleFragment">

    <EditText
        android:id="@+id/searchBoardInput"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/one_and_half_grid_unit"
        android:layout_marginTop="@dimen/one_and_half_grid_unit"
        android:layout_marginEnd="@dimen/half_grid_unit"
        android:background="@color/text_normal"
        android:padding="@dimen/one_grid_unit"
        android:textColor="@color/background"
        app:layout_constraintEnd_toStartOf="@id/searchBoard"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Gossip" />

    <ImageView
        android:id="@+id/searchBoard"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/text_normal"
        android:padding="@dimen/half_grid_unit"
        android:layout_marginEnd="@dimen/one_and_half_grid_unit"
        android:src="@drawable/ic_baseline_location_searching"
        app:layout_constraintBottom_toBottomOf="@id/searchBoardInput"
        app:layout_constraintEnd_toEndOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

OnClick事件

searchBoard.setOnClickListener {
    val board = searchBoardInput.text.toString().trim()
    searchBoardInput.setText(board)
    if (board.isEmpty()) return@setOnClickListener
    PttClient.getInstance().send("s$board ")
    //...
}

搜尋的起始是小寫的英文字母s,接著就是輸入板名+空白鍵。以輸入"a"為例,在Ptt按上述操作輸入後的畫面如下:
https://ithelp.ithome.com.tw/upload/images/20210922/20124602LARAvpZtP7.png

接著要做的事便是將回傳畫面下方的看板列表給解析出來。

Pattern - 所有搜尋結果頁面

首先可以看到上圖的最後一行顯示"按空白鍵可列出更多項目",這代表以"a"搜尋還有更多的結果無法在一個畫面內呈現。若持續點擊空白鍵把結果走到最後的話,畫面的呈現如下:
https://ithelp.ithome.com.tw/upload/images/20210922/20124602y4m7HnF60i.png
唯一的差別就只有最後一行消失了。

於是為了確保解析出所有搜尋結果頁面,我先設定了以下Pattern:

private val searchBoardPattern = arrayOf(
    "按空白鍵可列出更多項目",
    "相關資訊一覽表"
)

如果先匹配到"按空白鍵可列出更多項目",代表還有內容需要解析,反之代表最後一頁。

viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
    val boardList = mutableListOf<String>()
    do {
        val ret = PttClient.getInstance().expect(searchBoardPattern)
        Log.d(mTag, "expect: ret=$ret")
        if (ret == 0 || ret == 1) {
            // ... -> 解析看板名稱
        }
        if (ret == 0) {
            PttClient.getInstance().send(" ")
            delay(100L)
        }
    } while (ret == 0)
    // ... -> 回歸主頁面
}

Pattern - 所有看板

接下來要做的是上面程式碼註解中的解析看板名稱部分,解析出來的內容我會放到上方已宣告的boardList中。一樣我們先看畫面內容:
https://ithelp.ithome.com.tw/upload/images/20210922/20124602UQco1Rkw3H.png
基本上要做的事情就如上圖的標記,拿到頁面的字串後分割出第3~22行的內容,並且將每行內容以長度22做分割。

val searchResult = PttClient.getInstance().getScreen().split("\n")
for (i in 3..22) {
    val splitResult = arrayOf(
        searchResult[i].substring(0, 22).trim(),
        searchResult[i].substring(22, 44).trim(),
        searchResult[i].substring(44).trim()
    )
    splitResult.forEach {
        if (it.isNotEmpty()) {
            boardList.add(it)
        }
    }
}

trim完之後不為空的內容就加入到boardList之中,以前面的"a"來搜尋的話,下中斷點能看到以下的結果:
https://ithelp.ithome.com.tw/upload/images/20210922/20124602g8FdDKCqVE.png

回歸主頁面

在搜尋完成後我預計要做的事是將結果呈現並讓使用者做選擇,在此之前我想要先確保我的Ptt頁面是在主列表中,這樣在後續的操作會比較方便。而要離開搜尋的話需要送出Enter,但有時候當我搜尋的內容本來就有能匹配到的看板的話,按下Enter可能會直接進入該看板中,如下:
https://ithelp.ithome.com.tw/upload/images/20210922/201246024MBE3pBEf9.png
此時點擊Enter會直接進入該看板:
https://ithelp.ithome.com.tw/upload/images/20210922/201246027i2a7G6x3h.png
並且有些看板會有進版畫面:
https://ithelp.ithome.com.tw/upload/images/20210922/20124602HFj9vRdDjP.png

因此在送出Enter後我會有以下Pattern需要判斷:

private val cancelSearchBoardPattern = arrayOf(
    "【主功能表】",
    "請按任意鍵繼續", // 進板畫面
    "文章選讀", // 看板內
)

最後就簡單了。

PttClient.getInstance().send("\r\n")
var ret = PttClient.getInstance().expect(cancelSearchBoardPattern)
while (ret != 0 && ret != -1) {
    PttClient.getInstance().send("qq")
    delay(50L)
    ret = PttClient.getInstance().expect(cancelSearchBoardPattern)
}
// PttClient.getInstance().printScreen()

一次送兩個q是為了能更快速的回到主列表。

補充

因為不想每次送出內容都還要另外加toByteArray(Charset)來轉換成big5編碼的byte array,所以我有複寫了PttClient的send方法,之後直接送字串就好了。

override fun send(text: String?) {
    text?.run {
        send(toByteArray(BIG5))
    }
}

今天的內容就到這邊了,明天是想把搜尋的結果呈現給User做點選,應該內容會比較少一點。


上一篇
Day07 - Login to Ptt
下一篇
Day09 - 使用PopupWindow顯示搜尋結果
系列文
花30天做個Android小專案30

尚未有邦友留言

立即登入留言