mirror of
https://github.com/rafaelvcaetano/melonDS-android.git
synced 2024-11-23 05:39:41 +00:00
Convert cheats screen logic from Rx to Coroutines
This commit is contained in:
parent
30d016d759
commit
70b70861f1
@ -20,5 +20,5 @@ interface CheatDao {
|
||||
fun getEnabledRomCheats(gameCode: String, gameChecksum: String): Single<List<CheatEntity>>
|
||||
|
||||
@Update(entity = CheatEntity::class)
|
||||
fun updateCheatsStatus(cheats: List<CheatStatusUpdate>)
|
||||
suspend fun updateCheatsStatus(cheats: List<CheatStatusUpdate>)
|
||||
}
|
@ -4,15 +4,21 @@ import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.reactivex.Maybe
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import me.magnum.melonds.database.entities.CheatFolderWithCheats
|
||||
import me.magnum.melonds.database.entities.GameEntity
|
||||
import me.magnum.melonds.database.entities.GameWithCheatCategories
|
||||
|
||||
@Dao
|
||||
interface GameDao {
|
||||
@Transaction
|
||||
@Query("SELECT * FROM game")
|
||||
fun getGames(): Flow<List<GameEntity>>
|
||||
|
||||
@Query("SELECT * FROM game WHERE game_code = :gameCode AND (game_checksum IS NULL OR game_checksum = :gameChecksum)")
|
||||
fun findGameWithCheats(gameCode: String, gameChecksum: String): Maybe<List<GameWithCheatCategories>>
|
||||
suspend fun findGames(gameCode: String, gameChecksum: String): List<GameEntity>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM cheat_folder WHERE game_id = :gameId")
|
||||
suspend fun getGameCheats(gameId: Long): List<CheatFolderWithCheats>
|
||||
|
||||
@Insert
|
||||
fun insertGame(game: GameEntity): Long
|
||||
|
@ -1,16 +1,22 @@
|
||||
package me.magnum.melonds.domain.repositories
|
||||
|
||||
import android.net.Uri
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import me.magnum.melonds.domain.model.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import me.magnum.melonds.domain.model.Cheat
|
||||
import me.magnum.melonds.domain.model.CheatDatabase
|
||||
import me.magnum.melonds.domain.model.CheatFolder
|
||||
import me.magnum.melonds.domain.model.CheatImportProgress
|
||||
import me.magnum.melonds.domain.model.Game
|
||||
import me.magnum.melonds.domain.model.RomInfo
|
||||
|
||||
interface CheatsRepository {
|
||||
fun getAllRomCheats(romInfo: RomInfo): Maybe<List<Game>>
|
||||
suspend fun observeGames(): Flow<List<Game>>
|
||||
suspend fun findGamesForRom(romInfo: RomInfo): List<Game>
|
||||
suspend fun getAllGameCheats(game: Game): List<CheatFolder>
|
||||
fun getRomEnabledCheats(romInfo: RomInfo): Single<List<Cheat>>
|
||||
fun updateCheatsStatus(cheats: List<Cheat>): Completable
|
||||
suspend fun updateCheatsStatus(cheats: List<Cheat>)
|
||||
fun deleteCheatDatabaseIfExists(databaseName: String)
|
||||
fun addCheatDatabase(databaseName: String): CheatDatabase
|
||||
fun addGameCheats(databaseId: Long, game: Game)
|
||||
|
@ -3,16 +3,29 @@ package me.magnum.melonds.impl
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.work.*
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Maybe
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import me.magnum.melonds.common.workers.CheatImportWorker
|
||||
import me.magnum.melonds.database.MelonDatabase
|
||||
import me.magnum.melonds.database.entities.*
|
||||
import me.magnum.melonds.domain.model.*
|
||||
import me.magnum.melonds.database.entities.CheatDatabaseEntity
|
||||
import me.magnum.melonds.database.entities.CheatEntity
|
||||
import me.magnum.melonds.database.entities.CheatFolderEntity
|
||||
import me.magnum.melonds.database.entities.CheatStatusUpdate
|
||||
import me.magnum.melonds.database.entities.GameEntity
|
||||
import me.magnum.melonds.domain.model.Cheat
|
||||
import me.magnum.melonds.domain.model.CheatDatabase
|
||||
import me.magnum.melonds.domain.model.CheatFolder
|
||||
import me.magnum.melonds.domain.model.CheatImportProgress
|
||||
import me.magnum.melonds.domain.model.Game
|
||||
import me.magnum.melonds.domain.model.RomInfo
|
||||
import me.magnum.melonds.domain.repositories.CheatsRepository
|
||||
|
||||
class RoomCheatsRepository(private val context: Context, private val database: MelonDatabase) : CheatsRepository {
|
||||
@ -20,32 +33,50 @@ class RoomCheatsRepository(private val context: Context, private val database: M
|
||||
private const val IMPORT_WORKER_NAME = "cheat_import_worker"
|
||||
}
|
||||
|
||||
override fun getAllRomCheats(romInfo: RomInfo): Maybe<List<Game>> {
|
||||
return database.gameDao().findGameWithCheats(romInfo.gameCode, romInfo.headerChecksumString()).map {
|
||||
override suspend fun observeGames(): Flow<List<Game>> {
|
||||
return database.gameDao().getGames().map {
|
||||
it.map { game ->
|
||||
Game(
|
||||
game.game.id,
|
||||
game.game.name,
|
||||
game.game.gameCode,
|
||||
game.game.gameChecksum,
|
||||
game.cheatFolders.map { category ->
|
||||
CheatFolder(
|
||||
category.cheatFolder.id,
|
||||
category.cheatFolder.name,
|
||||
category.cheats.map { cheat ->
|
||||
Cheat(
|
||||
cheat.id,
|
||||
cheat.name,
|
||||
cheat.description,
|
||||
cheat.code,
|
||||
cheat.enabled
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
game.id,
|
||||
game.name,
|
||||
game.gameCode,
|
||||
game.gameChecksum,
|
||||
emptyList(),
|
||||
)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findGamesForRom(romInfo: RomInfo): List<Game> {
|
||||
return database.gameDao().findGames(romInfo.gameCode, romInfo.headerChecksumString()).map {
|
||||
Game(
|
||||
it.id,
|
||||
it.name,
|
||||
it.gameCode,
|
||||
it.gameChecksum,
|
||||
emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAllGameCheats(game: Game): List<CheatFolder> {
|
||||
val gameId = game.id ?: return emptyList()
|
||||
|
||||
return database.gameDao().getGameCheats(gameId).map {
|
||||
CheatFolder(
|
||||
it.cheatFolder.id,
|
||||
it.cheatFolder.name,
|
||||
it.cheats.map { cheat ->
|
||||
Cheat(
|
||||
cheat.id,
|
||||
cheat.name,
|
||||
cheat.description,
|
||||
cheat.code,
|
||||
cheat.enabled
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRomEnabledCheats(romInfo: RomInfo): Single<List<Cheat>> {
|
||||
@ -62,15 +93,12 @@ class RoomCheatsRepository(private val context: Context, private val database: M
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
override fun updateCheatsStatus(cheats: List<Cheat>): Completable {
|
||||
override suspend fun updateCheatsStatus(cheats: List<Cheat>) {
|
||||
val cheatEntities = cheats.map {
|
||||
CheatStatusUpdate(it.id!!, it.enabled)
|
||||
}
|
||||
|
||||
return Completable.create {
|
||||
database.cheatDao().updateCheatsStatus(cheatEntities)
|
||||
it.onComplete()
|
||||
}.subscribeOn(Schedulers.io())
|
||||
database.cheatDao().updateCheatsStatus(cheatEntities)
|
||||
}
|
||||
|
||||
override fun deleteCheatDatabaseIfExists(databaseName: String) {
|
||||
|
@ -7,9 +7,13 @@ import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.magnum.melonds.R
|
||||
import me.magnum.melonds.databinding.ActivityCheatsBinding
|
||||
|
||||
@ -38,26 +42,33 @@ class CheatsActivity : AppCompatActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
onBackPressedDispatcher.addCallback(backHandler)
|
||||
|
||||
viewModel.getRomCheats().observe(this) {
|
||||
binding.progressBarCheats.isGone = true
|
||||
if (savedInstanceState == null) {
|
||||
openCheatsFragment()
|
||||
}
|
||||
|
||||
if (it.isEmpty()) {
|
||||
binding.textCheatsNotFound.isVisible = true
|
||||
} else if (savedInstanceState == null) {
|
||||
openCheatsFragment()
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.openEnabledCheatsEvent.collectLatest {
|
||||
openEnabledCheatsFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.openEnabledCheatsEvent.observe(this) {
|
||||
openEnabledCheatsFragment()
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.committingCheatsChangesState.collectLatest {
|
||||
binding.viewBlock.isGone = !it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.committingCheatsChangesStatus().observe(this) {
|
||||
binding.viewBlock.isGone = !it
|
||||
}
|
||||
|
||||
viewModel.onCheatChangesCommitted().observe(this) {
|
||||
finish()
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.cheatChangesCommittedEvent.collectLatest {
|
||||
if (!it) {
|
||||
Toast.makeText(this@CheatsActivity, R.string.failed_save_cheat_changes, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,14 +110,10 @@ class CheatsActivity : AppCompatActivity() {
|
||||
|
||||
private fun commitCheatChangesAndFinish() {
|
||||
// If changes are already being committed, do nothing
|
||||
if (viewModel.committingCheatsChangesStatus().value == true) {
|
||||
if (viewModel.committingCheatsChangesState.value) {
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.commitCheatChanges().observe(this) {
|
||||
if (!it) {
|
||||
Toast.makeText(this, R.string.failed_save_cheat_changes, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
viewModel.commitCheatChanges()
|
||||
}
|
||||
}
|
@ -10,7 +10,12 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.fragment.app.replace
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.magnum.melonds.R
|
||||
import me.magnum.melonds.databinding.FragmentCheatsBinding
|
||||
import me.magnum.melonds.extensions.viewBinding
|
||||
@ -50,21 +55,34 @@ class CheatsFragment : Fragment(R.layout.fragment_cheats) {
|
||||
|
||||
requireActivity().addMenuProvider(cheatsMenuProvider, viewLifecycleOwner)
|
||||
|
||||
if (isLaunchingForFirstTime) {
|
||||
val hasMultipleGames = (viewModel.getRomCheats().value?.size ?: 0) > 1
|
||||
if (hasMultipleGames) {
|
||||
openSubScreenFragment<GamesSubScreenFragment>(isRootFragment = true)
|
||||
} else {
|
||||
openSubScreenFragment<FoldersSubScreenFragment>(isRootFragment = true)
|
||||
if (isLaunchingForFirstTime || !viewModel.initialContentReady.value) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.initialContentReady.collectLatest { ready ->
|
||||
if (ready) {
|
||||
val hasSelectedGame = viewModel.selectedGame.value != null
|
||||
if (hasSelectedGame) {
|
||||
openSubScreenFragment<FoldersSubScreenFragment>(isRootFragment = true)
|
||||
} else {
|
||||
openSubScreenFragment<GamesSubScreenFragment>(isRootFragment = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.openFoldersEvent.observe(viewLifecycleOwner) {
|
||||
openSubScreenFragment<FoldersSubScreenFragment>()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.openFoldersEvent.collectLatest {
|
||||
openSubScreenFragment<FoldersSubScreenFragment>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.openCheatsEvent.observe(viewLifecycleOwner) {
|
||||
openSubScreenFragment<FolderCheatsScreenFragment>()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.openCheatsEvent.collectLatest {
|
||||
openSubScreenFragment<FolderCheatsScreenFragment>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isLaunchingForFirstTime = false
|
||||
|
@ -1,19 +1,24 @@
|
||||
package me.magnum.melonds.ui.cheats
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import me.magnum.melonds.common.suspendRunCatching
|
||||
import me.magnum.melonds.domain.model.Cheat
|
||||
import me.magnum.melonds.domain.model.CheatFolder
|
||||
import me.magnum.melonds.domain.model.CheatInFolder
|
||||
import me.magnum.melonds.domain.model.Game
|
||||
import me.magnum.melonds.domain.repositories.CheatsRepository
|
||||
import me.magnum.melonds.extensions.addTo
|
||||
import me.magnum.melonds.parcelables.RomInfoParcelable
|
||||
import me.magnum.melonds.utils.SingleLiveEvent
|
||||
import me.magnum.melonds.ui.cheats.model.CheatsScreenUiState
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@ -22,63 +27,91 @@ class CheatsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle
|
||||
) : ViewModel() {
|
||||
|
||||
private val selectedGame = MutableLiveData<Game>()
|
||||
private val selectedFolder = MutableLiveData<CheatFolder?>()
|
||||
private var allRomCheatsLiveData = MutableLiveData<List<Game>>()
|
||||
private val committingCheatsChangesStatusLiveData = MutableLiveData(false)
|
||||
private val cheatChangesCommittedLiveEvent = SingleLiveEvent<Unit>()
|
||||
private val modifiedCheatSet = mutableListOf<Cheat>()
|
||||
|
||||
private val _openFoldersEvent = SingleLiveEvent<Unit>()
|
||||
val openFoldersEvent: LiveData<Unit> = _openFoldersEvent
|
||||
private val _initialContentReady = MutableStateFlow(false)
|
||||
val initialContentReady = _initialContentReady.asStateFlow()
|
||||
|
||||
private val _openCheatsEvent = SingleLiveEvent<Unit>()
|
||||
val openCheatsEvent: LiveData<Unit> = _openCheatsEvent
|
||||
private val _games = MutableStateFlow<CheatsScreenUiState<List<Game>>>(CheatsScreenUiState.Loading())
|
||||
val games = _games.asStateFlow()
|
||||
|
||||
private val _openEnabledCheatsEvent = SingleLiveEvent<Unit>()
|
||||
val openEnabledCheatsEvent: LiveData<Unit> = _openEnabledCheatsEvent
|
||||
private val _selectedGame = MutableStateFlow<Game?>(null)
|
||||
val selectedGame = _selectedGame.asStateFlow()
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
private val _selectedGameCheats = MutableStateFlow<CheatsScreenUiState<List<CheatFolder>>>(CheatsScreenUiState.Loading())
|
||||
val selectedGameCheats: StateFlow<CheatsScreenUiState<List<CheatFolder>>> get() {
|
||||
_selectedGameCheats.tryEmit(CheatsScreenUiState.Loading())
|
||||
loadCheatsForSelectedGame()
|
||||
return _selectedGameCheats.asStateFlow()
|
||||
}
|
||||
|
||||
private val _selectedCheatFolder = MutableStateFlow<CheatFolder?>(null)
|
||||
val selectedCheatFolder = _selectedCheatFolder.asStateFlow()
|
||||
|
||||
private val _openFoldersEvent = Channel<Unit>(Channel.CONFLATED)
|
||||
val openFoldersEvent = _openFoldersEvent.receiveAsFlow()
|
||||
|
||||
private val _openCheatsEvent = Channel<Unit>(Channel.CONFLATED)
|
||||
val openCheatsEvent = _openCheatsEvent.receiveAsFlow()
|
||||
|
||||
private val _openEnabledCheatsEvent = Channel<Unit>(Channel.CONFLATED)
|
||||
val openEnabledCheatsEvent = _openEnabledCheatsEvent.receiveAsFlow()
|
||||
|
||||
private val _committingCheatsChangesState = MutableStateFlow(false)
|
||||
val committingCheatsChangesState = _committingCheatsChangesState.asStateFlow()
|
||||
|
||||
private val _cheatChangesCommittedEvent = Channel<Boolean>(Channel.CONFLATED)
|
||||
val cheatChangesCommittedEvent = _cheatChangesCommittedEvent.receiveAsFlow()
|
||||
|
||||
init {
|
||||
val romInfo = savedStateHandle.get<RomInfoParcelable>(CheatsActivity.KEY_ROM_INFO) ?: error("No ROM info provided")
|
||||
val romInfo = savedStateHandle.get<RomInfoParcelable>(CheatsActivity.KEY_ROM_INFO)
|
||||
|
||||
cheatsRepository.getAllRomCheats(romInfo.toRomInfo()).subscribe {
|
||||
allRomCheatsLiveData.postValue(it)
|
||||
if (it.size == 1) {
|
||||
selectedGame.postValue(it.first())
|
||||
viewModelScope.launch {
|
||||
if (romInfo != null) {
|
||||
val games = cheatsRepository.findGamesForRom(romInfo.toRomInfo())
|
||||
_games.emit(CheatsScreenUiState.Ready(games))
|
||||
if (games.size == 1) {
|
||||
_selectedGame.emit(games.first())
|
||||
}
|
||||
_initialContentReady.emit(true)
|
||||
} else {
|
||||
cheatsRepository.observeGames().collectLatest {
|
||||
_games.emit(CheatsScreenUiState.Ready(it))
|
||||
_initialContentReady.emit(true)
|
||||
}
|
||||
|
||||
/*cheatsRepository.getAllRomCheats(romInfo.toRomInfo()).subscribe {
|
||||
allRomCheatsLiveData.postValue(it)
|
||||
if (it.size == 1) {
|
||||
_selectedGame.tryEmit(it.first())
|
||||
}
|
||||
_initialContentReady.tryEmit(true)
|
||||
}.addTo(disposables)*/
|
||||
}
|
||||
}.addTo(disposables)
|
||||
}
|
||||
}
|
||||
|
||||
fun getRomCheats(): LiveData<List<Game>> {
|
||||
return allRomCheatsLiveData
|
||||
}
|
||||
private fun loadCheatsForSelectedGame() {
|
||||
val selectedGame = _selectedGame.value ?: return
|
||||
|
||||
fun getGames(): List<Game> {
|
||||
return allRomCheatsLiveData.value ?: emptyList()
|
||||
}
|
||||
|
||||
fun getSelectedGame(): LiveData<Game> {
|
||||
return selectedGame
|
||||
viewModelScope.launch {
|
||||
val cheatFolders = cheatsRepository.getAllGameCheats(selectedGame)
|
||||
_selectedGameCheats.emit(CheatsScreenUiState.Ready(cheatFolders))
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedGame(game: Game) {
|
||||
selectedGame.value = game
|
||||
_openFoldersEvent.postValue(Unit)
|
||||
_selectedGame.tryEmit(game)
|
||||
_openFoldersEvent.trySend(Unit)
|
||||
}
|
||||
|
||||
fun getSelectedFolder(): LiveData<CheatFolder?> {
|
||||
return selectedFolder
|
||||
}
|
||||
|
||||
fun setSelectedFolder(folder: CheatFolder?) {
|
||||
selectedFolder.value = folder
|
||||
_openCheatsEvent.postValue(Unit)
|
||||
fun setSelectedFolder(folder: CheatFolder) {
|
||||
_selectedCheatFolder.value = folder
|
||||
_openCheatsEvent.trySend(Unit)
|
||||
}
|
||||
|
||||
fun getSelectedFolderCheats(): List<Cheat> {
|
||||
val cheats = selectedFolder.value?.cheats?.toMutableList() ?: mutableListOf()
|
||||
val cheats = _selectedCheatFolder.value?.cheats?.toMutableList() ?: mutableListOf()
|
||||
|
||||
modifiedCheatSet.forEach { cheat ->
|
||||
val originalCheatIndex = cheats.indexOfFirst { it.id == cheat.id }
|
||||
@ -113,40 +146,24 @@ class CheatsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun openEnabledCheats() {
|
||||
_openEnabledCheatsEvent.postValue(Unit)
|
||||
_openEnabledCheatsEvent.trySend(Unit)
|
||||
}
|
||||
|
||||
fun committingCheatsChangesStatus(): LiveData<Boolean> {
|
||||
return committingCheatsChangesStatusLiveData
|
||||
}
|
||||
|
||||
fun onCheatChangesCommitted(): LiveData<Unit> {
|
||||
return cheatChangesCommittedLiveEvent
|
||||
}
|
||||
|
||||
fun commitCheatChanges(): LiveData<Boolean> {
|
||||
val liveData = MutableLiveData<Boolean>()
|
||||
|
||||
fun commitCheatChanges() {
|
||||
if (modifiedCheatSet.isEmpty()) {
|
||||
cheatChangesCommittedLiveEvent.postValue(Unit)
|
||||
return liveData
|
||||
_cheatChangesCommittedEvent.trySend(true)
|
||||
return
|
||||
}
|
||||
|
||||
committingCheatsChangesStatusLiveData.value = true
|
||||
cheatsRepository.updateCheatsStatus(modifiedCheatSet).doAfterTerminate {
|
||||
committingCheatsChangesStatusLiveData.postValue(false)
|
||||
cheatChangesCommittedLiveEvent.postValue(Unit)
|
||||
}.subscribe({
|
||||
liveData.postValue(true)
|
||||
}, {
|
||||
liveData.postValue(false)
|
||||
}).addTo(disposables)
|
||||
|
||||
return liveData
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
disposables.dispose()
|
||||
_committingCheatsChangesState.value = true
|
||||
viewModelScope.launch {
|
||||
suspendRunCatching {
|
||||
cheatsRepository.updateCheatsStatus(modifiedCheatSet)
|
||||
}.fold(
|
||||
onSuccess = { _cheatChangesCommittedEvent.trySend(true) },
|
||||
onFailure = { _cheatChangesCommittedEvent.trySend(false) },
|
||||
)
|
||||
_committingCheatsChangesState.value = false
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import me.magnum.melonds.extensions.setViewEnabledRecursive
|
||||
class FolderCheatsScreenFragment : SubScreenFragment() {
|
||||
|
||||
override fun getScreenName(): String? {
|
||||
return viewModel.getSelectedFolder().value?.name
|
||||
return viewModel.selectedCheatFolder.value?.name
|
||||
}
|
||||
|
||||
override fun getSubScreenAdapter(): RecyclerView.Adapter<*> {
|
||||
|
@ -2,28 +2,46 @@ package me.magnum.melonds.ui.cheats
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.magnum.melonds.R
|
||||
import me.magnum.melonds.databinding.ItemCheatsFolderBinding
|
||||
import me.magnum.melonds.domain.model.CheatFolder
|
||||
import me.magnum.melonds.ui.cheats.model.CheatsScreenUiState
|
||||
import me.magnum.melonds.utils.SimpleDiffCallback
|
||||
|
||||
class FoldersSubScreenFragment : SubScreenFragment() {
|
||||
|
||||
override fun getSubScreenAdapter(): RecyclerView.Adapter<*> {
|
||||
return FoldersAdapter(viewModel.getSelectedGame().value?.cheats ?: emptyList()) {
|
||||
val adapter = FoldersAdapter {
|
||||
viewModel.setSelectedFolder(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getScreenName(): String? {
|
||||
return if (viewModel.getGames().size == 1) {
|
||||
getString(R.string.cheats)
|
||||
} else {
|
||||
viewModel.getSelectedGame().value?.name
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.selectedGameCheats.collectLatest {
|
||||
updateScreenState(it)
|
||||
when (it) {
|
||||
is CheatsScreenUiState.Loading -> { }
|
||||
is CheatsScreenUiState.Ready<*> -> adapter.updateCheatFolders(it.data as List<CheatFolder>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return adapter
|
||||
}
|
||||
|
||||
private class FoldersAdapter(val folders: List<CheatFolder>, private val onFolderClicked: (CheatFolder) -> Unit) : RecyclerView.Adapter<FoldersAdapter.ViewHolder>() {
|
||||
override fun getScreenName(): String {
|
||||
return viewModel.selectedGame.value?.name ?: getString(R.string.cheats)
|
||||
}
|
||||
|
||||
private class FoldersAdapter(private val onFolderClicked: (CheatFolder) -> Unit) : RecyclerView.Adapter<FoldersAdapter.ViewHolder>() {
|
||||
class ViewHolder(private val binding: ItemCheatsFolderBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
private lateinit var folder: CheatFolder
|
||||
|
||||
@ -38,6 +56,17 @@ class FoldersSubScreenFragment : SubScreenFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val folders = mutableListOf<CheatFolder>()
|
||||
|
||||
fun updateCheatFolders(newCheatFolders: List<CheatFolder>) {
|
||||
val diffResult = DiffUtil.calculateDiff(FoldersDiffCallback(folders, newCheatFolders))
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
folders.apply {
|
||||
clear()
|
||||
addAll(newCheatFolders)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = ItemCheatsFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding).apply {
|
||||
@ -54,5 +83,11 @@ class FoldersSubScreenFragment : SubScreenFragment() {
|
||||
override fun getItemCount(): Int {
|
||||
return folders.size
|
||||
}
|
||||
|
||||
class FoldersDiffCallback(oldList: List<CheatFolder>, newList: List<CheatFolder>): SimpleDiffCallback<CheatFolder>(oldList, newList) {
|
||||
override fun areItemsTheSame(old: CheatFolder, new: CheatFolder): Boolean {
|
||||
return old.id == new.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,24 +2,45 @@ package me.magnum.melonds.ui.cheats
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.magnum.melonds.R
|
||||
import me.magnum.melonds.databinding.ItemCheatsGameBinding
|
||||
import me.magnum.melonds.domain.model.Game
|
||||
import me.magnum.melonds.ui.cheats.model.CheatsScreenUiState
|
||||
|
||||
class GamesSubScreenFragment : SubScreenFragment() {
|
||||
|
||||
override fun getSubScreenAdapter(): RecyclerView.Adapter<*> {
|
||||
return GamesAdapter(viewModel.getGames()) {
|
||||
val adapter = GamesAdapter {
|
||||
viewModel.setSelectedGame(it)
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.games.collectLatest {
|
||||
updateScreenState(it)
|
||||
when (it) {
|
||||
is CheatsScreenUiState.Loading -> { }
|
||||
is CheatsScreenUiState.Ready<*> -> adapter.updateGames(it.data as List<Game>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return adapter
|
||||
}
|
||||
|
||||
override fun getScreenName(): String {
|
||||
return getString(R.string.cheats)
|
||||
}
|
||||
|
||||
private class GamesAdapter(val games: List<Game>, private val onGameClicked: (Game) -> Unit) : RecyclerView.Adapter<GamesAdapter.ViewHolder>() {
|
||||
private class GamesAdapter(private val onGameClicked: (Game) -> Unit) : RecyclerView.Adapter<GamesAdapter.ViewHolder>() {
|
||||
class ViewHolder(private val binding: ItemCheatsGameBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
private lateinit var game: Game
|
||||
|
||||
@ -34,6 +55,8 @@ class GamesSubScreenFragment : SubScreenFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private var games = emptyList<Game>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = ItemCheatsGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding).apply {
|
||||
@ -43,6 +66,12 @@ class GamesSubScreenFragment : SubScreenFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateGames(newGames: List<Game>) {
|
||||
val result = DiffUtil.calculateDiff(GamesDillCallback(games, newGames))
|
||||
result.dispatchUpdatesTo(this)
|
||||
games = newGames
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.setGame(games[position])
|
||||
}
|
||||
@ -50,5 +79,19 @@ class GamesSubScreenFragment : SubScreenFragment() {
|
||||
override fun getItemCount(): Int {
|
||||
return games.size
|
||||
}
|
||||
|
||||
private class GamesDillCallback(val oldGames: List<Game>, val newGames: List<Game>) : DiffUtil.Callback() {
|
||||
override fun getOldListSize() = oldGames.size
|
||||
|
||||
override fun getNewListSize() = newGames.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldGames[oldItemPosition].id == newGames[newItemPosition].id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldGames[oldItemPosition] == newGames[newItemPosition]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
@ -12,6 +13,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import me.magnum.melonds.databinding.FragmentCheatsSubscreenBinding
|
||||
import me.magnum.melonds.ui.cheats.model.CheatsScreenUiState
|
||||
|
||||
abstract class SubScreenFragment : Fragment() {
|
||||
protected val viewModel: CheatsViewModel by activityViewModels()
|
||||
@ -31,7 +33,6 @@ abstract class SubScreenFragment : Fragment() {
|
||||
addItemDecoration(DividerItemDecoration(context, listLayoutManager.orientation))
|
||||
adapter = getSubScreenAdapter()
|
||||
}
|
||||
binding.listItems.adapter?.notifyDataSetChanged()
|
||||
|
||||
if (binding.listItems.adapter?.itemCount == 0) {
|
||||
getNoContentText()?.let {
|
||||
@ -45,6 +46,21 @@ abstract class SubScreenFragment : Fragment() {
|
||||
(requireActivity() as AppCompatActivity).supportActionBar?.title = getScreenName()
|
||||
}
|
||||
|
||||
fun updateScreenState(uiState: CheatsScreenUiState<*>) {
|
||||
when (uiState) {
|
||||
is CheatsScreenUiState.Loading -> {
|
||||
binding.progressBar.isVisible = true
|
||||
binding.textNoContent.isGone = true
|
||||
binding.listItems.isVisible = false
|
||||
}
|
||||
is CheatsScreenUiState.Ready -> {
|
||||
binding.progressBar.isGone = true
|
||||
binding.textNoContent.isGone = true
|
||||
binding.listItems.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getSubScreenAdapter(): RecyclerView.Adapter<*>
|
||||
|
||||
abstract fun getScreenName(): String?
|
||||
|
@ -0,0 +1,6 @@
|
||||
package me.magnum.melonds.ui.cheats.model
|
||||
|
||||
sealed class CheatsScreenUiState<T> {
|
||||
class Loading<T> : CheatsScreenUiState<T>()
|
||||
data class Ready<T>(val data: T) : CheatsScreenUiState<T>()
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package me.magnum.melonds.utils
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
||||
abstract class SimpleDiffCallback<T>(private val oldList: List<T>, private val newList: List<T>) : DiffUtil.Callback() {
|
||||
override fun getOldListSize() = oldList.size
|
||||
|
||||
override fun getNewListSize() = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return areItemsTheSame(oldList[oldItemPosition], newList[newItemPosition])
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
||||
|
||||
abstract fun areItemsTheSame(old: T, new: T): Boolean
|
||||
}
|
@ -19,4 +19,12 @@
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_margin="24dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
Loading…
Reference in New Issue
Block a user