mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-07-20 11:45:47 +00:00
[android] improve driver fetcher (#251)
- Fix the app compat crash - Fix kimchi sorting - Improve performance in ui thread Signed-off-by: Aleksandr Popovich <alekpopo@pm.me> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/251 Co-authored-by: Aleksandr Popovich <alekpopo@pm.me> Co-committed-by: Aleksandr Popovich <alekpopo@pm.me>
This commit is contained in:
parent
aeb2aec13b
commit
b60d0aabf0
3 changed files with 118 additions and 65 deletions
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.fetcher
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
|
@ -17,7 +20,9 @@ import androidx.transition.TransitionManager
|
|||
import androidx.transition.TransitionSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
|
||||
class DriverGroupAdapter(
|
||||
|
@ -25,43 +30,61 @@ class DriverGroupAdapter(
|
|||
private val driverViewModel: DriverViewModel
|
||||
) : RecyclerView.Adapter<DriverGroupAdapter.DriverGroupViewHolder>() {
|
||||
private var driverGroups: List<DriverGroup> = emptyList()
|
||||
private val adapterJobs = mutableMapOf<Int, Job>()
|
||||
|
||||
inner class DriverGroupViewHolder(
|
||||
private val binding: ItemDriverGroupBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(group: DriverGroup) {
|
||||
binding.textGroupName.text = group.name
|
||||
|
||||
if (binding.recyclerReleases.layoutManager == null) {
|
||||
binding.recyclerReleases.layoutManager = LinearLayoutManager(activity)
|
||||
binding.recyclerReleases.addItemDecoration(
|
||||
SpacingItemDecoration(
|
||||
(activity.resources.displayMetrics.density * 8).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val onClick = {
|
||||
adapterJobs[bindingAdapterPosition]?.cancel()
|
||||
|
||||
TransitionManager.beginDelayedTransition(
|
||||
binding.root,
|
||||
TransitionSet().addTransition(Fade()).addTransition(ChangeBounds())
|
||||
.setDuration(200)
|
||||
)
|
||||
|
||||
val isVisible = binding.recyclerReleases.isVisible
|
||||
|
||||
if (!isVisible && binding.recyclerReleases.adapter == null) {
|
||||
val job = CoroutineScope(Dispatchers.Main).launch {
|
||||
// It prevents blocking the ui thread.
|
||||
var adapter: ReleaseAdapter?
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
adapter = ReleaseAdapter(group.releases, activity, driverViewModel)
|
||||
}
|
||||
|
||||
binding.recyclerReleases.adapter = adapter
|
||||
}
|
||||
|
||||
adapterJobs[bindingAdapterPosition] = job
|
||||
}
|
||||
|
||||
binding.recyclerReleases.visibility = if (isVisible) View.GONE else View.VISIBLE
|
||||
binding.imageDropdownArrow.rotation = if (isVisible) 0f else 180f
|
||||
|
||||
if (!isVisible && binding.recyclerReleases.adapter == null) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
binding.recyclerReleases.layoutManager =
|
||||
LinearLayoutManager(binding.root.context)
|
||||
binding.recyclerReleases.adapter =
|
||||
ReleaseAdapter(group.releases, activity, driverViewModel)
|
||||
|
||||
binding.recyclerReleases.addItemDecoration(
|
||||
SpacingItemDecoration(
|
||||
(activity.resources.displayMetrics.density * 8).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.textGroupName.text = group.name
|
||||
binding.textGroupName.setOnClickListener { onClick() }
|
||||
|
||||
binding.imageDropdownArrow.setOnClickListener { onClick() }
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
adapterJobs[bindingAdapterPosition]?.cancel()
|
||||
adapterJobs.remove(bindingAdapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverGroupViewHolder {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
|
@ -28,10 +30,12 @@ import org.yuzu.yuzu_emu.databinding.FragmentDriverFetcherBinding
|
|||
import org.yuzu.yuzu_emu.features.fetcher.DriverGroupAdapter
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import kotlin.getValue
|
||||
|
||||
class DriverFetcherFragment : Fragment() {
|
||||
|
@ -49,17 +53,22 @@ class DriverFetcherFragment : Fragment() {
|
|||
private val recommendedDriver: String
|
||||
get() = driverMap.firstOrNull { adrenoModel in it.first }?.second ?: "Unsupported"
|
||||
|
||||
enum class SortMode {
|
||||
Default, PublishTime,
|
||||
}
|
||||
|
||||
private data class DriverRepo(
|
||||
val name: String = "",
|
||||
val path: String = "",
|
||||
val sort: Int = 0,
|
||||
val useTagName: Boolean = false
|
||||
val useTagName: Boolean = false,
|
||||
val sortMode: SortMode = SortMode.Default,
|
||||
)
|
||||
|
||||
private val repoList: List<DriverRepo> = listOf(
|
||||
DriverRepo("Mr. Purple Turnip", "MrPurple666/purple-turnip", 0),
|
||||
DriverRepo("GameHub Adreno 8xx", "crueter/GameHub-8Elite-Drivers", 1),
|
||||
DriverRepo("KIMCHI Turnip", "K11MCH1/AdrenoToolsDrivers", 2, true),
|
||||
DriverRepo("KIMCHI Turnip", "K11MCH1/AdrenoToolsDrivers", 2, true, SortMode.PublishTime),
|
||||
DriverRepo("Weab-Chan Freedreno", "Weab-chan/freedreno_turnip-CI", 3),
|
||||
)
|
||||
|
||||
|
@ -78,7 +87,7 @@ class DriverFetcherFragment : Fragment() {
|
|||
private lateinit var driverGroupAdapter: DriverGroupAdapter
|
||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||
|
||||
fun parseAdrenoModel(): Int {
|
||||
private fun parseAdrenoModel(): Int {
|
||||
if (gpuModel == null) {
|
||||
return 0
|
||||
}
|
||||
|
@ -115,9 +124,8 @@ class DriverFetcherFragment : Fragment() {
|
|||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentDriverFetcherBinding.inflate(inflater)
|
||||
binding.badgeRecommendedDriver.text = recommendedDriver
|
||||
binding.badgeGpuModel.text = gpuModel
|
||||
|
@ -150,11 +158,12 @@ class DriverFetcherFragment : Fragment() {
|
|||
val name = driver.name
|
||||
val path = driver.path
|
||||
val useTagName = driver.useTagName
|
||||
val sortMode = driver.sortMode
|
||||
val sort = driver.sort
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val request = Request.Builder()
|
||||
.url("https://api.github.com/repos/$path/releases")
|
||||
.build()
|
||||
val request =
|
||||
Request.Builder().url("https://api.github.com/repos/$path/releases").build()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
var releases: ArrayList<Release>
|
||||
|
@ -165,28 +174,25 @@ class DriverFetcherFragment : Fragment() {
|
|||
}
|
||||
|
||||
val body = response.body?.string() ?: return@withContext
|
||||
releases = Release.fromJsonArray(body, useTagName)
|
||||
releases = Release.fromJsonArray(body, useTagName, sortMode)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
MaterialAlertDialogBuilder(requireActivity().applicationContext)
|
||||
.setTitle(getString(R.string.error_during_fetch))
|
||||
MaterialAlertDialogBuilder(requireActivity()).setTitle(getString(R.string.error_during_fetch))
|
||||
.setMessage("${getString(R.string.failed_to_fetch)} ${name}:\n${e.message}")
|
||||
.setPositiveButton(getString(R.string.ok)) { dialog, _ -> dialog.cancel() }
|
||||
.show()
|
||||
|
||||
releases = ArrayList<Release>()
|
||||
releases = ArrayList()
|
||||
}
|
||||
}
|
||||
|
||||
val driver = DriverGroup(
|
||||
name,
|
||||
releases,
|
||||
sort
|
||||
val group = DriverGroup(
|
||||
name, releases, sort
|
||||
)
|
||||
|
||||
synchronized(driverGroups) {
|
||||
driverGroups.add(driver)
|
||||
driverGroups.add(group)
|
||||
driverGroups.sortBy {
|
||||
it.sort
|
||||
}
|
||||
|
@ -204,39 +210,41 @@ class DriverFetcherFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
|
||||
binding.toolbarDrivers.updateMargins(left = leftInsets, right = rightInsets)
|
||||
binding.listDrivers.updateMargins(left = leftInsets, right = rightInsets)
|
||||
binding.toolbarDrivers.updateMargins(left = leftInsets, right = rightInsets)
|
||||
binding.listDrivers.updateMargins(left = leftInsets, right = rightInsets)
|
||||
|
||||
binding.listDrivers.updatePadding(
|
||||
bottom = barInsets.bottom +
|
||||
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
|
||||
)
|
||||
binding.listDrivers.updatePadding(
|
||||
bottom = barInsets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
|
||||
)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
windowInsets
|
||||
}
|
||||
|
||||
data class Artifact(val url: URL, val name: String)
|
||||
|
||||
data class Release(
|
||||
var tagName: String = "",
|
||||
var titleName: String = "",
|
||||
var title: String = "",
|
||||
var body: String = "",
|
||||
var artifacts: List<Artifact> = ArrayList<Artifact>(),
|
||||
var artifacts: List<Artifact> = ArrayList(),
|
||||
var prerelease: Boolean = false,
|
||||
var latest: Boolean = false
|
||||
var latest: Boolean = false,
|
||||
var publishTime: LocalDateTime = LocalDateTime.now(),
|
||||
) {
|
||||
companion object {
|
||||
fun fromJsonArray(jsonString: String, useTagName: Boolean): ArrayList<Release> {
|
||||
fun fromJsonArray(
|
||||
jsonString: String, useTagName: Boolean, sortMode: SortMode
|
||||
): ArrayList<Release> {
|
||||
val mapper = jacksonObjectMapper()
|
||||
|
||||
try {
|
||||
|
@ -256,39 +264,55 @@ class DriverFetcherFragment : Fragment() {
|
|||
}
|
||||
|
||||
releases.add(release)
|
||||
|
||||
println(release.publishTime)
|
||||
}
|
||||
}
|
||||
|
||||
when (sortMode) {
|
||||
SortMode.PublishTime -> releases.sortByDescending {
|
||||
it.publishTime
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
return releases
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return ArrayList<Release>()
|
||||
return ArrayList()
|
||||
}
|
||||
}
|
||||
|
||||
fun fromJson(node: JsonNode, useTagName: Boolean): Release {
|
||||
private fun fromJson(node: JsonNode, useTagName: Boolean): Release {
|
||||
try {
|
||||
val tagName = node.get("tag_name").toString().removeSurrounding("\"")
|
||||
val body = node.get("body").toString().removeSurrounding("\"")
|
||||
val prerelease = node.get("prerelease").toString().toBoolean()
|
||||
val title = if (useTagName) tagName else node.get("name").toString().removeSurrounding("\"")
|
||||
val titleName = node.get("name").toString().removeSurrounding("\"")
|
||||
|
||||
val published = node.get("published_at").toString().removeSurrounding("\"")
|
||||
val instantTime: Instant? = Instant.parse(published)
|
||||
val localTime = instantTime?.atZone(ZoneId.systemDefault())?.toLocalDateTime() ?: LocalDateTime.now()
|
||||
|
||||
val title = if (useTagName) tagName else titleName
|
||||
|
||||
val assets = node.get("assets")
|
||||
val artifacts = ArrayList<Artifact>()
|
||||
if (assets?.isArray == true) {
|
||||
assets.forEach { node ->
|
||||
val urlStr =
|
||||
node.get("browser_download_url").toString().removeSurrounding("\"")
|
||||
assets.forEach { subNode ->
|
||||
val urlStr = subNode.get("browser_download_url").toString()
|
||||
.removeSurrounding("\"")
|
||||
|
||||
val url = URL(urlStr)
|
||||
val name = node.get("name").toString().removeSurrounding("\"")
|
||||
val name = subNode.get("name").toString().removeSurrounding("\"")
|
||||
|
||||
val artifact = Artifact(url, name)
|
||||
artifacts.add(artifact)
|
||||
}
|
||||
}
|
||||
|
||||
return Release(tagName, title, body, artifacts, prerelease)
|
||||
return Release(tagName, titleName, title, body, artifacts, prerelease, false, localTime)
|
||||
} catch (e: Exception) {
|
||||
// TODO: handle malformed input.
|
||||
e.printStackTrace()
|
||||
|
|
|
@ -335,7 +335,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
homeViewModel.setCheckKeys(true)
|
||||
homeViewModel.setCheckFirmware(true)
|
||||
|
||||
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
||||
if (!firstTimeSetup) {
|
||||
homeViewModel.setCheckFirmware(true)
|
||||
}
|
||||
|
||||
gamesViewModel.reloadGames(true)
|
||||
return true
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue