Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions app/src/main/java/com/raofflineproxy/ui/CachedGamesFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class CachedGamesFragment : Fragment() {
private val collapsedConsoleIds = mutableSetOf<Int>()
private var currentGames: List<CachedGame> = emptyList()
private var gamesAdapter: CachedGamesAdapter? = null
private var headerAdapter: CachedGamesHeaderAdapter? = null
private var headerState = CachedGamesHeaderAdapter.HeaderState()
private var searchOpen = false
private var searchQuery = ""
private var queueDialog: AlertDialog? = null

private val romFolderPickerLauncher = registerForActivityResult(
Expand All @@ -65,7 +69,7 @@ class CachedGamesFragment : Fragment() {
onHeaderClick = { consoleId ->
if (!collapsedConsoleIds.remove(consoleId)) collapsedConsoleIds.add(consoleId)
saveCollapsedState()
gamesAdapter?.submitList(buildGroupedList(currentGames, collapsedConsoleIds))
submitGames()
},
onDelete = viewModel::deleteCachedGame,
onDeleteConsole = { header ->
Expand Down Expand Up @@ -108,8 +112,20 @@ class CachedGamesFragment : Fragment() {
.create()
.also { it.setCanceledOnTouchOutside(false) }
.show()
},
onSearchChanged = { query ->
searchQuery = query
publishHeaderState(headerState)
submitGames()
},
onSearchToggled = { open ->
searchOpen = open
if (!open) searchQuery = ""
publishHeaderState(headerState)
submitGames()
}
)
this.headerAdapter = headerAdapter

view.findViewById<RecyclerView>(R.id.rv_cached_games).apply {
layoutManager = LinearLayoutManager(requireContext())
Expand All @@ -121,7 +137,7 @@ class CachedGamesFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.cachedGames.collect { games ->
currentGames = games
gamesAdapter?.submitList(buildGroupedList(games, collapsedConsoleIds))
submitGames()
}
}

Expand All @@ -146,7 +162,7 @@ class CachedGamesFragment : Fragment() {
)
else -> queuedStatusText(state)
}
headerAdapter.update(
publishHeaderState(
CachedGamesHeaderAdapter.HeaderState(
smartCacheEnabled = smartCacheEnabled,
showSmartCache = showSmartCache,
Expand All @@ -166,6 +182,16 @@ class CachedGamesFragment : Fragment() {
queueDialog?.dismiss()
queueDialog = null
gamesAdapter = null
headerAdapter = null
}

private fun publishHeaderState(base: CachedGamesHeaderAdapter.HeaderState) {
headerState = base.copy(searchOpen = searchOpen, searchQuery = searchQuery)
headerAdapter?.update(headerState)
}

private fun submitGames() {
gamesAdapter?.submitList(buildGroupedList(currentGames, collapsedConsoleIds, searchQuery))
}

private fun updateQueueDialog(estimate: QueueEstimate?) {
Expand Down Expand Up @@ -289,6 +315,17 @@ class CachedGamesFragment : Fragment() {
}

private fun buildGroupedList(
games: List<CachedGame>,
collapsedConsoleIds: Set<Int>,
query: String = ""
): List<CachedGameListItem> {
val searching = query.isNotBlank()
val visibleGames = if (searching) games.filter { it.title.contains(query.trim(), ignoreCase = true) } else games
val collapsed = if (searching) emptySet() else collapsedConsoleIds
return groupByConsole(visibleGames, collapsed)
}

private fun groupByConsole(
games: List<CachedGame>,
collapsedConsoleIds: Set<Int>
): List<CachedGameListItem> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ package com.raofflineproxy.ui

import android.view.LayoutInflater
import android.view.View
import android.text.Editable
import android.text.TextWatcher
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
Expand All @@ -11,7 +16,9 @@ import com.raofflineproxy.R
class CachedGamesHeaderAdapter(
private val onSmartCache: () -> Unit,
private val onScan: () -> Unit,
private val onClear: () -> Unit
private val onClear: () -> Unit,
private val onSearchChanged: (String) -> Unit,
private val onSearchToggled: (Boolean) -> Unit
) : RecyclerView.Adapter<CachedGamesHeaderAdapter.ViewHolder>() {

private var state: HeaderState = HeaderState()
Expand All @@ -22,11 +29,15 @@ class CachedGamesHeaderAdapter(
val scanEnabled: Boolean = false,
val clearEnabled: Boolean = true,
val showNoCachedGames: Boolean = false,
val statusText: String? = null
val statusText: String? = null,
val searchOpen: Boolean = false,
val searchQuery: String = ""
)

inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val btnSmartCache: MaterialButton = view.findViewById(R.id.btn_smart_cache)
val btnSearch: MaterialButton = view.findViewById(R.id.btn_search)
val etSearch: EditText = view.findViewById(R.id.et_search)
val btnScan: MaterialButton = view.findViewById(R.id.btn_scan_roms)
val btnClear: MaterialButton = view.findViewById(R.id.btn_clear_cache)
val tvScanHint: TextView = view.findViewById(R.id.tv_scan_hint)
Expand All @@ -36,9 +47,42 @@ class CachedGamesHeaderAdapter(
btnSmartCache.setOnClickListener { onSmartCache() }
btnScan.setOnClickListener { onScan() }
btnClear.setOnClickListener { onClear() }
btnSearch.setOnClickListener { onSearchToggled(!state.searchOpen) }
etSearch.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) {
val query = s?.toString().orEmpty()
if (query != state.searchQuery) onSearchChanged(query)
}
})
etSearch.setOnEditorActionListener { v, actionId, _ ->
if (actionId != EditorInfo.IME_ACTION_SEARCH) return@setOnEditorActionListener false
hideKeyboard(v)
true
}
}

private fun hideKeyboard(target: View) {
val imm = target.context.getSystemService(InputMethodManager::class.java)
imm?.hideSoftInputFromWindow(target.windowToken, 0)
}

private fun bindSearch(s: HeaderState) {
val wasOpen = etSearch.visibility == View.VISIBLE
etSearch.visibility = if (s.searchOpen) View.VISIBLE else View.GONE
btnSearch.setIconResource(if (s.searchOpen) R.drawable.ic_close else R.drawable.ic_search)
if (etSearch.text.toString() != s.searchQuery) etSearch.setText(s.searchQuery)
if (s.searchOpen && !wasOpen) {
etSearch.requestFocus()
etSearch.context.getSystemService(InputMethodManager::class.java)
?.showSoftInput(etSearch, InputMethodManager.SHOW_IMPLICIT)
}
if (!s.searchOpen && wasOpen) hideKeyboard(etSearch)
}

fun bind(s: HeaderState) {
bindSearch(s)
btnSmartCache.visibility = if (s.showSmartCache) View.VISIBLE else View.GONE
btnSmartCache.isEnabled = s.smartCacheEnabled
btnSmartCache.alpha = if (s.smartCacheEnabled) 1f else 0.38f
Expand All @@ -50,7 +94,8 @@ class CachedGamesHeaderAdapter(
btnClear.alpha = if (s.clearEnabled) 1f else 0.38f

tvScanHint.text = s.statusText
tvScanHint.visibility = if (s.statusText.isNullOrEmpty()) View.GONE else View.VISIBLE
tvScanHint.visibility =
if (s.statusText.isNullOrEmpty() || s.searchOpen) View.GONE else View.VISIBLE

tvNoCachedGames.visibility = if (s.showNoCachedGames) View.VISIBLE else View.GONE
}
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_close.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnSurface">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_search.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnSurface">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>
25 changes: 25 additions & 0 deletions app/src/main/res/layout/item_cached_games_header.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,33 @@
android:textSize="12sp"
android:visibility="gone" />

<EditText
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:hint="@string/cached_games_search_hint"
android:imeOptions="actionSearch"
android:importantForAutofill="no"
android:inputType="text"
android:maxLines="1"
android:textSize="14sp"
android:visibility="gone" />

<com.google.android.material.button.MaterialButton
android:id="@+id/btn_search"
style="@style/Widget.RAOfflineProxy.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/cached_games_search"
android:tooltipText="@string/cached_games_search"
app:icon="@drawable/ic_search"
app:iconTint="?attr/colorOnSurface" />

<com.google.android.material.button.MaterialButton
android:id="@+id/btn_smart_cache"
android:layout_marginStart="8dp"
style="@style/Widget.RAOfflineProxy.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
<string name="queue_eta_minutes">%1$d minutes</string>
<string name="queue_eta_hours">%1$d h %2$d min</string>
<string name="cached_games_smart_cache">Start Smart Cache</string>
<string name="cached_games_search">Search games</string>
<string name="cached_games_search_hint">Search games</string>
<string name="cached_games_scan_folder">Scan ROM folder</string>
<string name="cached_games_clear_all">Clear all cache</string>
<string name="cached_games_empty">No games cached yet. Add a ROM or scan your ROM folder while online.</string>
Expand Down
Loading