Skip to content

Commit

Permalink
Add filter and move search bar down
Browse files Browse the repository at this point in the history
* Fix crashes
  • Loading branch information
Grarak authored and PixelyIon committed Jun 17, 2021
1 parent 4792098 commit 4db7502
Show file tree
Hide file tree
Showing 22 changed files with 520 additions and 434 deletions.
94 changes: 69 additions & 25 deletions app/src/main/java/emu/skyline/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.content.res.use
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.size
import androidx.lifecycle.observe
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.adapter.AppViewItem
Expand All @@ -33,6 +36,7 @@ import emu.skyline.data.AppItem
import emu.skyline.data.DataItem
import emu.skyline.data.HeaderItem
import emu.skyline.databinding.MainActivityBinding
import emu.skyline.loader.AppEntry
import emu.skyline.loader.LoaderResult
import emu.skyline.loader.RomFormat
import emu.skyline.utils.Settings
Expand All @@ -41,6 +45,10 @@ import kotlin.math.ceil

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
companion object {
private val formatOrder = arrayOf(RomFormat.NSP, RomFormat.XCI, RomFormat.NRO, RomFormat.NSO, RomFormat.NCA)
}

private val binding by lazy { MainActivityBinding.inflate(layoutInflater) }

@Inject
Expand All @@ -54,16 +62,32 @@ class MainActivity : AppCompatActivity() {

private val viewModel by viewModels<MainViewModel>()

private var formatFilter : RomFormat? = null
private var appEntries : Map<RomFormat, List<AppEntry>>? = null

private var refreshIconVisible = false
set(visible) {
field = visible
binding.refreshIcon.apply {
if (visible != isVisible) {
binding.refreshIcon.alpha = if (visible) 0f else 1f
animate().alpha(if (visible) 1f else 0f).withStartAction { isVisible = true }.withEndAction { isInvisible = !visible }.apply { duration = 500 }.start()
}
}
}

private fun AppItem.toViewItem() = AppViewItem(layoutType, this, missingIcon, ::selectStartGame, ::selectShowGameDialog)

override fun onCreate(savedInstanceState : Bundle?) {
// Need to create new instance of settings, dependency injection happens
AppCompatDelegate.setDefaultNightMode(when ((Settings(this).appTheme.toInt())) {
0 -> AppCompatDelegate.MODE_NIGHT_NO
1 -> AppCompatDelegate.MODE_NIGHT_YES
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
})
AppCompatDelegate.setDefaultNightMode(
when ((Settings(this).appTheme.toInt())) {
0 -> AppCompatDelegate.MODE_NIGHT_NO
1 -> AppCompatDelegate.MODE_NIGHT_YES
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
}
)
super.onCreate(savedInstanceState)

setContentView(binding.root)
Expand All @@ -79,13 +103,26 @@ class MainActivity : AppCompatActivity() {
setOnRefreshListener { loadRoms(false) }
}

for (format in formatOrder) {
binding.chipGroup.addView(Chip(this, null, R.attr.chipChoiceStyle).apply { text = format.name })
}
binding.chipGroup.setOnCheckedChangeListener { group, checkedId ->
for (i in 0 until group.childCount) {
if (group.getChildAt(i).id == checkedId) {
formatFilter = if (i == 0) null else formatOrder[i - 1]
populateAdapter()
break
}
}
}

viewModel.stateData.observe(owner = this, onChanged = ::handleState)
loadRoms(!settings.refreshRequired)

binding.searchBar.apply {
setLogIconListener { startActivity(Intent(context, LogActivity::class.java)) }
setSettingsIconListener { startActivityForResult(Intent(context, SettingsActivity::class.java), 3) }
setRefreshIconListener { loadRoms(false) }
binding.logIcon.setOnClickListener { startActivity(Intent(context, LogActivity::class.java)) }
binding.settingsIcon.setOnClickListener { startActivityForResult(Intent(context, SettingsActivity::class.java), 3) }
binding.refreshIcon.setOnClickListener { loadRoms(false) }
addTextChangedListener(afterTextChanged = { editable ->
editable?.let { text -> adapter.filter.filter(text.toString()) }
})
Expand All @@ -95,7 +132,7 @@ class MainActivity : AppCompatActivity() {
}
}
window.decorView.findViewById<View>(android.R.id.content).viewTreeObserver.addOnTouchModeChangeListener { isInTouchMode ->
binding.searchBar.refreshIconVisible = !isInTouchMode
refreshIconVisible = !isInTouchMode
}
}

Expand Down Expand Up @@ -184,32 +221,38 @@ class MainActivity : AppCompatActivity() {
binding.appList.layoutManager = CustomLayoutManager(gridSpan)
setAppListDecoration()

if (settings.searchLocation.isEmpty()) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
if (settings.searchLocation.isEmpty()) startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
}, 1)
}

startActivityForResult(intent, 1)
private fun getDataItems() = mutableListOf<DataItem>().apply {
appEntries?.let { entries ->
val formats = formatFilter?.let { arrayOf(it) } ?: formatOrder
for (format in formats) {
entries[format]?.let {
add(HeaderItem(format.name))
it.forEach { entry -> add(AppItem(entry)) }
}
}
}
}

private fun handleState(state : MainState) = when (state) {
MainState.Loading -> {
binding.searchBar.animateRefreshIcon()
binding.refreshIcon.animate().rotationBy(-180f)
binding.swipeRefreshLayout.isRefreshing = true
}

is MainState.Loaded -> {
binding.swipeRefreshLayout.isRefreshing = false

val formatOrder = arrayOf(RomFormat.NSP, RomFormat.NRO, RomFormat.NSO, RomFormat.NCA)
val items = mutableListOf<DataItem>()
for (format in formatOrder) {
state.items[format]?.let {
items.add(HeaderItem(format.name))
it.forEach { entry -> items.add(AppItem(entry)) }
}
}
populateAdapter(items)
appEntries = state.items
populateAdapter()
}

is MainState.Error -> Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${state.ex.localizedMessage}", Snackbar.LENGTH_SHORT).show()
}

Expand All @@ -233,7 +276,8 @@ class MainActivity : AppCompatActivity() {
settings.refreshRequired = false
}

private fun populateAdapter(items : List<DataItem>) {
private fun populateAdapter() {
val items = getDataItems()
adapter.setItems(items.map {
when (it) {
is HeaderItem -> HeaderViewItem(it.title)
Expand Down
24 changes: 15 additions & 9 deletions app/src/main/java/emu/skyline/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import emu.skyline.utils.toFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.io.IOException
import java.util.*
import javax.inject.Inject
import kotlin.collections.HashMap

sealed class MainState {
object Loading : MainState()
Expand Down Expand Up @@ -50,7 +51,7 @@ class MainViewModel @Inject constructor(private val romProvider : RomProvider) :
val romsFile = File(context.filesDir.canonicalPath + "/roms.bin")

viewModelScope.launch(Dispatchers.IO) {
if (loadFromFile) {
if (loadFromFile && romsFile.exists()) {
try {
state = MainState.Loaded(fromFile(romsFile))
return@launch
Expand All @@ -59,14 +60,19 @@ class MainViewModel @Inject constructor(private val romProvider : RomProvider) :
}
}

val romElements = romProvider.loadRoms(searchLocation)
try {
romElements.toFile(romsFile)
} catch (e : IOException) {
Log.w(TAG, "Ran into exception while saving: ${e.message}")
state = if (searchLocation.toString().isEmpty()) {
@Suppress("ReplaceWithEnumMap")
MainState.Loaded(HashMap())
} else {
try {
val romElements = romProvider.loadRoms(searchLocation)
romElements.toFile(romsFile)
MainState.Loaded(romElements)
} catch (e : Exception) {
Log.w(TAG, "Ran into exception while saving: ${e.message}")
MainState.Error(e)
}
}

state = MainState.Loaded(romElements)
}
}
}
3 changes: 1 addition & 2 deletions app/src/main/java/emu/skyline/adapter/GenericAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

package emu.skyline.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
Expand Down Expand Up @@ -101,7 +100,7 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), F
val avgScore = topResults.sumByDouble { it.score } / topResults.size

for (result in topResults)
if (result.score > avgScore) filterData.add(result.item)
if (result.score >= avgScore) filterData.add(result.item)

results.values = filterData
results.count = filterData.size
Expand Down
35 changes: 0 additions & 35 deletions app/src/main/java/emu/skyline/views/SearchBarView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ import android.content.Context
import android.text.Editable
import android.text.TextWatcher
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.inputmethod.InputMethodManager
import androidx.core.view.MarginLayoutParamsCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.google.android.material.card.MaterialCardView
import emu.skyline.databinding.ViewSearchBarBinding
import kotlin.math.roundToInt

class SearchBarView @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = com.google.android.material.R.attr.materialCardViewStyle) : MaterialCardView(context, attrs, defStyleAttr) {
private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)
Expand All @@ -21,32 +16,6 @@ class SearchBarView @JvmOverloads constructor(context : Context, attrs : Attribu
useCompatPadding = true
}

override fun onAttachedToWindow() {
super.onAttachedToWindow()

val margin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, context.resources.displayMetrics).roundToInt()
MarginLayoutParamsCompat.setMarginStart(layoutParams as MarginLayoutParams?, margin)
MarginLayoutParamsCompat.setMarginEnd(layoutParams as MarginLayoutParams?, margin)

radius = margin / 2f
cardElevation = radius / 2f
}

fun setRefreshIconListener(listener : OnClickListener) = binding.refreshIcon.setOnClickListener(listener)
fun setLogIconListener(listener : OnClickListener) = binding.logIcon.setOnClickListener(listener)
fun setSettingsIconListener(listener : OnClickListener) = binding.settingsIcon.setOnClickListener(listener)

var refreshIconVisible = false
set(visible) {
field = visible
binding.refreshIcon.apply {
if (visible != isVisible) {
binding.refreshIcon.alpha = if (visible) 0f else 1f
animate().alpha(if (visible) 1f else 0f).withStartAction { isVisible = true }.withEndAction { isInvisible = !visible }.apply { duration = 500 }.start()
}
}
}

var text : CharSequence
get() = binding.searchField.text
set(value) = binding.searchField.setText(value)
Expand All @@ -65,10 +34,6 @@ class SearchBarView @JvmOverloads constructor(context : Context, attrs : Attribu
}
}

fun animateRefreshIcon() {
binding.refreshIcon.animate().rotationBy(-180f)
}

fun addTextChangedListener(
beforeTextChanged : (
text : CharSequence?,
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/app_dialog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
android:layout_height="150dp"
android:contentDescription="@string/icon"
android:focusable="false"
app:shapeAppearanceOverlay="@style/roundedAppImage"
app:shapeAppearanceOverlay="@style/RoundedAppImage"
tools:src="@drawable/default_icon" />

<LinearLayout
Expand Down
Loading

0 comments on commit 4db7502

Please sign in to comment.