android: Migrate settings to navigation component

Consolidates all of the settings components to the fragment and activity with no interfaces and only the settings fragment presenter. This also includes new material animations and new viewmodel usage to prevent the fragment and activity directly interacting with one another.
This commit is contained in:
Charles Lombardo 2023-08-22 19:46:07 -04:00
parent f5e6b12c74
commit 95a939a49f
28 changed files with 372 additions and 568 deletions

View file

@ -46,7 +46,7 @@ class YuzuApplication : Application() {
super.onCreate() super.onCreate()
application = this application = this
documentsTree = DocumentsTree() documentsTree = DocumentsTree()
DirectoryInitialization.start(applicationContext) DirectoryInitialization.start()
GpuDriverHelper.initializeDriverParameters(applicationContext) GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo() NativeLibrary.logDeviceInfo()

View file

@ -85,9 +85,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val navHostFragment = val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
val navController = navHostFragment.navController navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
navController
.setGraph(R.navigation.emulation_navigation, intent.extras)
isActivityRecreated = savedInstanceState != null isActivityRecreated = savedInstanceState != null

View file

@ -3,10 +3,7 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast import android.widget.Toast
@ -16,20 +13,26 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
class SettingsActivity : AppCompatActivity(), SettingsActivityView { class SettingsActivity : AppCompatActivity() {
private val presenter = SettingsActivityPresenter(this)
private lateinit var binding: ActivitySettingsBinding private lateinit var binding: ActivitySettingsBinding
private val args by navArgs<SettingsActivityArgs>()
private val settingsViewModel: SettingsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
@ -38,16 +41,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
binding = ActivitySettingsBinding.inflate(layoutInflater) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
settingsViewModel.game = args.game
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
val launcher = intent if (savedInstanceState != null) {
val gameID = launcher.getStringExtra(ARG_GAME_ID) settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
val menuTag = launcher.getStringExtra(ARG_MENU_TAG) }
presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
// Show "Back" button in the action bar for navigation
setSupportActionBar(binding.toolbarSettings)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
if (InsetsHelper.getSystemGestureType(applicationContext) != if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION InsetsHelper.GESTURE_NAVIGATION
@ -63,6 +67,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
) )
} }
settingsViewModel.shouldRecreate.observe(this) {
if (it) {
settingsViewModel.setShouldRecreate(false)
recreate()
}
}
settingsViewModel.shouldNavigateBack.observe(this) {
if (it) {
settingsViewModel.setShouldNavigateBack(false)
navigateBack()
}
}
settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
if (it) {
settingsViewModel.setShouldShowResetSettingsDialog(false)
ResetSettingsDialogFragment().show(
supportFragmentManager,
ResetSettingsDialogFragment.TAG
)
}
}
onBackPressedDispatcher.addCallback( onBackPressedDispatcher.addCallback(
this, this,
object : OnBackPressedCallback(true) { object : OnBackPressedCallback(true) {
@ -73,34 +99,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
setInsets() setInsets()
} }
override fun onSupportNavigateUp(): Boolean { fun navigateBack() {
navigateBack() val navHostFragment =
return true supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
} if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
navHostFragment.navController.popBackStack()
private fun navigateBack() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack()
} else { } else {
finish() finish()
} }
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_settings, menu)
return true
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted. // Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
presenter.saveState(outState) outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
presenter.onStart() // TODO: Load custom settings contextually
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
} }
/** /**
@ -110,65 +130,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
*/ */
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
presenter.onStop(isFinishing) if (isFinishing && settingsViewModel.shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
Settings.saveSettings()
NativeLibrary.reloadSettings()
}
} }
override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) { override fun onDestroy() {
if (!addToStack && settingsFragment != null) { settingsViewModel.clear()
return super.onDestroy()
}
val transaction = supportFragmentManager.beginTransaction()
if (addToStack) {
if (areSystemAnimationsEnabled()) {
transaction.setCustomAnimations(
R.anim.anim_settings_fragment_in,
R.anim.anim_settings_fragment_out,
0,
R.anim.anim_pop_settings_fragment_out
)
}
transaction.addToBackStack(null)
}
transaction.replace(
R.id.frame_content,
SettingsFragment.newInstance(menuTag, gameId),
FRAGMENT_TAG
)
transaction.commit()
}
private fun areSystemAnimationsEnabled(): Boolean {
val duration = android.provider.Settings.Global.getFloat(
contentResolver,
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
1f
)
val transition = android.provider.Settings.Global.getFloat(
contentResolver,
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
1f
)
return duration != 0f && transition != 0f
}
override fun onSettingsFileLoaded() {
val fragment: SettingsFragmentView? = settingsFragment
fragment?.loadSettingsList()
}
override fun onSettingsFileNotFound() {
val fragment: SettingsFragmentView? = settingsFragment
fragment?.loadSettingsList()
}
override fun onSettingChanged() {
presenter.onSettingChanged()
} }
fun onSettingsReset() { fun onSettingsReset() {
// Prevents saving to a non-existent settings file // Prevents saving to a non-existent settings file
presenter.onSettingsReset() settingsViewModel.shouldSave = false
// Delete settings file because the user may have changed values that do not exist in the UI // Delete settings file because the user may have changed values that do not exist in the UI
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
@ -185,47 +161,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
finish() finish()
} }
fun setToolbarTitle(title: String) {
binding.toolbarSettingsLayout.title = title
}
private val settingsFragment: SettingsFragment?
get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
private fun setInsets() { private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener( ViewCompat.setOnApplyWindowInsetsListener(
binding.frameContent binding.navigationBarShade
) { view: View, windowInsets: WindowInsetsCompat -> ) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.updatePadding(
left = barInsets.left + cutoutInsets.left,
right = barInsets.right + cutoutInsets.right
)
val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams val mlpShade = view.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
binding.appbarSettings.layoutParams = mlpAppBar
val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
mlpShade.height = barInsets.bottom mlpShade.height = barInsets.bottom
binding.navigationBarShade.layoutParams = mlpShade view.layoutParams = mlpShade
windowInsets windowInsets
} }
} }
companion object { companion object {
private const val ARG_MENU_TAG = "menu_tag" private const val KEY_SHOULD_SAVE = "should_save"
private const val ARG_GAME_ID = "game_id"
private const val FRAGMENT_TAG = "settings"
fun launch(context: Context, menuTag: String?, gameId: String?) {
val settings = Intent(context, SettingsActivity::class.java)
settings.putExtra(ARG_MENU_TAG, menuTag)
settings.putExtra(ARG_GAME_ID, gameId)
context.startActivity(settings)
}
} }
} }

View file

@ -1,81 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.os.Bundle
import java.io.File
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
private var shouldSave = false
private lateinit var menuTag: String
private lateinit var gameId: String
fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
this.menuTag = menuTag
this.gameId = gameId
if (savedInstanceState != null) {
shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
}
}
fun onStart() {
prepareDirectoriesIfNeeded()
}
private fun loadSettingsUI() {
// TODO: Load custom settings contextually
activityView.showSettingsFragment(menuTag, false, gameId)
activityView.onSettingsFileLoaded()
}
private fun prepareDirectoriesIfNeeded() {
val configFile =
File(
"${DirectoryInitialization.userDirectory}/config/" +
"${SettingsFile.FILE_NAME_CONFIG}.ini"
)
if (!configFile.exists()) {
Log.error(
"${DirectoryInitialization.userDirectory}/config/" +
"${SettingsFile.FILE_NAME_CONFIG}.ini"
)
Log.error("yuzu config file could not be found!")
}
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start(activityView as Context)
}
loadSettingsUI()
}
fun onStop(finishing: Boolean) {
if (finishing && shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
Settings.saveSettings()
NativeLibrary.reloadSettings()
}
}
fun onSettingChanged() {
shouldSave = true
}
fun onSettingsReset() {
shouldSave = false
}
fun saveState(outState: Bundle) {
outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
}
companion object {
private const val KEY_SHOULD_SAVE = "should_save"
}
}

View file

@ -1,38 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui
/**
* Abstraction for the Activity that manages SettingsFragments.
*/
interface SettingsActivityView {
/**
* Show a new SettingsFragment.
*
* @param menuTag Identifier for the settings group that should be displayed.
* @param addToStack Whether or not this fragment should replace a previous one.
*/
fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
/**
* Called when a load operation completes.
*/
fun onSettingsFileLoaded()
/**
* Called when a load operation fails.
*/
fun onSettingsFileNotFound()
/**
* End the activity.
*/
fun finish()
/**
* Called by a containing Fragment to tell the Activity that a setting was changed;
* unless this has been called, the Activity will not save to disk.
*/
fun onSettingChanged()
}

View file

@ -12,7 +12,8 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -20,6 +21,7 @@ import com.google.android.material.slider.Slider
import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat import com.google.android.material.timepicker.TimeFormat
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.SettingsNavigationDirections
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
@ -30,18 +32,22 @@ import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsAdapter( class SettingsAdapter(
private val fragmentView: SettingsFragmentView, private val fragment: SettingsFragment,
private val context: Context private val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener { ) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
private var settings: ArrayList<SettingsItem>? = null private var settings = ArrayList<SettingsItem>()
private var clickedItem: SettingsItem? = null private var clickedItem: SettingsItem? = null
private var clickedPosition: Int private var clickedPosition: Int
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private var sliderProgress = 0 private var sliderProgress = 0
private var textSliderValue: TextView? = null private var textSliderValue: TextView? = null
private val settingsViewModel: SettingsViewModel
get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
private var defaultCancelListener = private var defaultCancelListener =
DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() } DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
@ -91,30 +97,22 @@ class SettingsAdapter(
holder.bind(getItem(position)) holder.bind(getItem(position))
} }
private fun getItem(position: Int): SettingsItem { private fun getItem(position: Int): SettingsItem = settings[position]
return settings!![position]
}
override fun getItemCount(): Int { override fun getItemCount(): Int = settings.size
return if (settings != null) {
settings!!.size
} else {
0
}
}
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return getItem(position).type return getItem(position).type
} }
fun setSettingsList(settings: ArrayList<SettingsItem>?) { fun setSettingsList(settings: ArrayList<SettingsItem>) {
this.settings = settings this.settings = settings
notifyDataSetChanged() notifyDataSetChanged()
} }
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
item.checked = checked item.checked = checked
fragmentView.onSettingChanged() settingsViewModel.shouldSave = true
} }
private fun onSingleChoiceClick(item: SingleChoiceSetting) { private fun onSingleChoiceClick(item: SingleChoiceSetting) {
@ -155,7 +153,7 @@ class SettingsAdapter(
calendar.timeZone = TimeZone.getTimeZone("UTC") calendar.timeZone = TimeZone.getTimeZone("UTC")
var timeFormat: Int = TimeFormat.CLOCK_12H var timeFormat: Int = TimeFormat.CLOCK_12H
if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) { if (DateFormat.is24HourFormat(context)) {
timeFormat = TimeFormat.CLOCK_24H timeFormat = TimeFormat.CLOCK_24H
} }
@ -172,7 +170,7 @@ class SettingsAdapter(
datePicker.addOnPositiveButtonClickListener { datePicker.addOnPositiveButtonClickListener {
timePicker.show( timePicker.show(
(fragmentView.activityView as AppCompatActivity).supportFragmentManager, fragment.childFragmentManager,
"TimePicker" "TimePicker"
) )
} }
@ -181,14 +179,14 @@ class SettingsAdapter(
epochTime += timePicker.hour.toLong() * 60 * 60 epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60 epochTime += timePicker.minute.toLong() * 60
if (item.value != epochTime) { if (item.value != epochTime) {
fragmentView.onSettingChanged() settingsViewModel.shouldSave = true
notifyItemChanged(clickedPosition) notifyItemChanged(clickedPosition)
item.value = epochTime item.value = epochTime
} }
clickedItem = null clickedItem = null
} }
datePicker.show( datePicker.show(
(fragmentView.activityView as AppCompatActivity).supportFragmentManager, fragment.childFragmentManager,
"DatePicker" "DatePicker"
) )
} }
@ -231,7 +229,8 @@ class SettingsAdapter(
} }
fun onSubmenuClick(item: SubmenuSetting) { fun onSubmenuClick(item: SubmenuSetting) {
fragmentView.loadSubMenu(item.menuKey) val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
fragment.view?.findNavController()?.navigate(action)
} }
override fun onClick(dialog: DialogInterface, which: Int) { override fun onClick(dialog: DialogInterface, which: Int) {
@ -240,7 +239,7 @@ class SettingsAdapter(
val scSetting = clickedItem as SingleChoiceSetting val scSetting = clickedItem as SingleChoiceSetting
val value = getValueForSingleChoiceSelection(scSetting, which) val value = getValueForSingleChoiceSelection(scSetting, which)
if (scSetting.selectedValue != value) { if (scSetting.selectedValue != value) {
fragmentView.onSettingChanged() settingsViewModel.shouldSave = true
} }
// Get the backing Setting, which may be null (if for example it was missing from the file) // Get the backing Setting, which may be null (if for example it was missing from the file)
@ -251,7 +250,7 @@ class SettingsAdapter(
is StringSingleChoiceSetting -> { is StringSingleChoiceSetting -> {
val scSetting = clickedItem as StringSingleChoiceSetting val scSetting = clickedItem as StringSingleChoiceSetting
val value = scSetting.getValueAt(which) val value = scSetting.getValueAt(which)
if (scSetting.selectedValue != value) fragmentView.onSettingChanged() if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
scSetting.selectedValue = value!! scSetting.selectedValue = value!!
closeDialog() closeDialog()
} }
@ -259,7 +258,7 @@ class SettingsAdapter(
is SliderSetting -> { is SliderSetting -> {
val sliderSetting = clickedItem as SliderSetting val sliderSetting = clickedItem as SliderSetting
if (sliderSetting.selectedValue != sliderProgress) { if (sliderSetting.selectedValue != sliderProgress) {
fragmentView.onSettingChanged() settingsViewModel.shouldSave = true
} }
when (sliderSetting.setting) { when (sliderSetting.setting) {
is ByteSetting -> { is ByteSetting -> {
@ -294,7 +293,7 @@ class SettingsAdapter(
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
setting.reset() setting.reset()
notifyItemChanged(position) notifyItemChanged(position)
fragmentView.onSettingChanged() settingsViewModel.shouldSave = true
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show() .show()

View file

@ -3,39 +3,41 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsFragment : Fragment(), SettingsFragmentView { class SettingsFragment : Fragment() {
override var activityView: SettingsActivityView? = null private lateinit var presenter: SettingsFragmentPresenter
private val fragmentPresenter = SettingsFragmentPresenter(this)
private var settingsAdapter: SettingsAdapter? = null private var settingsAdapter: SettingsAdapter? = null
private var _binding: FragmentSettingsBinding? = null private var _binding: FragmentSettingsBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
override fun onAttach(context: Context) { private val args by navArgs<SettingsFragmentArgs>()
super.onAttach(context)
activityView = requireActivity() as SettingsActivityView private val settingsViewModel: SettingsViewModel by activityViewModels()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
val gameId = requireArguments().getString(ARGUMENT_GAME_ID) returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
fragmentPresenter.onCreate(menuTag!!, gameId!!) reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
} }
override fun onCreateView( override fun onCreateView(
@ -48,7 +50,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
settingsAdapter = SettingsAdapter(this, requireActivity()) settingsAdapter = SettingsAdapter(this, requireContext())
presenter = SettingsFragmentPresenter(
settingsViewModel,
settingsAdapter!!,
args.menuTag,
args.game?.gameId ?: ""
)
val dividerDecoration = MaterialDividerItemDecoration( val dividerDecoration = MaterialDividerItemDecoration(
requireContext(), requireContext(),
LinearLayoutManager.VERTICAL LinearLayoutManager.VERTICAL
@ -56,63 +65,52 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
dividerDecoration.isLastItemDecorated = false dividerDecoration.isLastItemDecorated = false
binding.listSettings.apply { binding.listSettings.apply {
adapter = settingsAdapter adapter = settingsAdapter
layoutManager = LinearLayoutManager(activity) layoutManager = LinearLayoutManager(requireContext())
addItemDecoration(dividerDecoration) addItemDecoration(dividerDecoration)
} }
fragmentPresenter.onViewCreated()
binding.toolbarSettings.setNavigationOnClickListener {
settingsViewModel.setShouldNavigateBack(true)
}
settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
}
presenter.onViewCreated()
setInsets() setInsets()
} }
override fun onDetach() { override fun onDetach() {
super.onDetach() super.onDetach()
activityView = null settingsAdapter?.closeDialog()
if (settingsAdapter != null) {
settingsAdapter!!.closeDialog()
}
}
override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
settingsAdapter!!.setSettingsList(settingsList)
}
override fun loadSettingsList() {
fragmentPresenter.loadSettingsList()
}
override fun loadSubMenu(menuKey: String) {
activityView!!.showSettingsFragment(
menuKey,
true,
requireArguments().getString(ARGUMENT_GAME_ID)!!
)
}
override fun onSettingChanged() {
activityView!!.onSettingChanged()
} }
private fun setInsets() { private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener( ViewCompat.setOnApplyWindowInsetsListener(
binding.listSettings binding.root
) { view: View, windowInsets: WindowInsetsCompat -> ) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(bottom = insets.bottom) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
mlpSettingsList.leftMargin = sideMargin + leftInsets
mlpSettingsList.rightMargin = sideMargin + rightInsets
binding.listSettings.layoutParams = mlpSettingsList
binding.listSettings.updatePadding(
bottom = barInsets.bottom
)
val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.appbarSettings.layoutParams = mlpAppBar
windowInsets windowInsets
} }
} }
companion object {
private const val ARGUMENT_MENU_TAG = "menu_tag"
private const val ARGUMENT_GAME_ID = "game_id"
fun newInstance(menuTag: String?, gameId: String?): Fragment {
val fragment = SettingsFragment()
val arguments = Bundle()
arguments.putString(ARGUMENT_MENU_TAG, menuTag)
arguments.putString(ARGUMENT_GAME_ID, gameId)
fragment.arguments = arguments
return fragment
}
}
} }

View file

@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.text.TextUtils import android.text.TextUtils
@ -20,36 +21,36 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.ThemeHelper
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { class SettingsFragmentPresenter(
private var menuTag: String? = null private val settingsViewModel: SettingsViewModel,
private lateinit var gameId: String private val adapter: SettingsAdapter,
private var settingsList: ArrayList<SettingsItem>? = null private var menuTag: String,
private var gameId: String
) {
private var settingsList = ArrayList<SettingsItem>()
private val settingsActivity get() = fragmentView.activityView as SettingsActivity private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
private lateinit var preferences: SharedPreferences private val context: Context get() = YuzuApplication.appContext
fun onCreate(menuTag: String, gameId: String) {
this.gameId = gameId
this.menuTag = menuTag
}
fun onViewCreated() { fun onViewCreated() {
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
loadSettingsList() loadSettingsList()
} }
fun loadSettingsList() { private fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) { if (!TextUtils.isEmpty(gameId)) {
settingsActivity.setToolbarTitle("Game Settings: $gameId") settingsViewModel.setToolbarTitle(
context.getString(
R.string.advanced_settings_game,
gameId
)
)
} }
val sl = ArrayList<SettingsItem>() val sl = ArrayList<SettingsItem>()
if (menuTag == null) {
return
}
when (menuTag) { when (menuTag) {
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
Settings.SECTION_GENERAL -> addGeneralSettings(sl) Settings.SECTION_GENERAL -> addGeneralSettings(sl)
@ -69,11 +70,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
} }
settingsList = sl settingsList = sl
fragmentView.showSettingsList(settingsList!!) adapter.setSettingsList(settingsList)
} }
private fun addConfigSettings(sl: ArrayList<SettingsItem>) { private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings)) settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
sl.apply { sl.apply {
add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
@ -82,17 +83,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
add( add(
RunnableSetting(R.string.reset_to_default, 0, false) { RunnableSetting(R.string.reset_to_default, 0, false) {
ResetSettingsDialogFragment().show( settingsViewModel.setShouldShowResetSettingsDialog(true)
settingsActivity.supportFragmentManager,
ResetSettingsDialogFragment.TAG
)
} }
) )
} }
} }
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general)) settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
sl.apply { sl.apply {
add( add(
SwitchSetting( SwitchSetting(
@ -131,7 +129,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
private fun addSystemSettings(sl: ArrayList<SettingsItem>) { private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system)) settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
sl.apply { sl.apply {
add( add(
SwitchSetting( SwitchSetting(
@ -170,7 +168,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
sl.apply { sl.apply {
add( add(
SingleChoiceSetting( SingleChoiceSetting(
@ -267,7 +265,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
private fun addAudioSettings(sl: ArrayList<SettingsItem>) { private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
sl.apply { sl.apply {
add( add(
SingleChoiceSetting( SingleChoiceSetting(
@ -292,7 +290,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
private fun addThemeSettings(sl: ArrayList<SettingsItem>) { private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
sl.apply { sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting { val theme: AbstractIntSetting = object : AbstractIntSetting {
override val int: Int override val int: Int
@ -302,7 +300,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
preferences.edit() preferences.edit()
.putInt(Settings.PREF_THEME, value) .putInt(Settings.PREF_THEME, value)
.apply() .apply()
settingsActivity.recreate() settingsViewModel.setShouldRecreate(true)
} }
override val key: String? = null override val key: String? = null
@ -346,7 +344,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
preferences.edit() preferences.edit()
.putInt(Settings.PREF_THEME_MODE, value) .putInt(Settings.PREF_THEME_MODE, value)
.apply() .apply()
ThemeHelper.setThemeMode(settingsActivity) settingsViewModel.setShouldRecreate(true)
} }
override val key: String? = null override val key: String? = null
@ -357,6 +355,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
preferences.edit() preferences.edit()
.putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
.apply() .apply()
settingsViewModel.setShouldRecreate(true)
} }
} }
@ -378,7 +377,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
preferences.edit() preferences.edit()
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
.apply() .apply()
settingsActivity.recreate() settingsViewModel.setShouldRecreate(true)
} }
override val key: String? = null override val key: String? = null
@ -389,6 +388,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
preferences.edit() preferences.edit()
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
.apply() .apply()
settingsViewModel.setShouldRecreate(true)
} }
} }
@ -403,7 +403,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
private fun addDebugSettings(sl: ArrayList<SettingsItem>) { private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
sl.apply { sl.apply {
add(HeaderSetting(R.string.gpu)) add(HeaderSetting(R.string.gpu))
add( add(

View file

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
/**
* Abstraction for a screen showing a list of settings. Instances of
* this type of view will each display a layer of the setting hierarchy.
*/
interface SettingsFragmentView {
/**
* Pass an ArrayList to the View so that it can be displayed on screen.
*
* @param settingsList The result of converting the HashMap to an ArrayList
*/
fun showSettingsList(settingsList: ArrayList<SettingsItem>)
/**
* Instructs the Fragment to load the settings screen.
*/
fun loadSettingsList()
/**
* @return The Fragment's containing activity.
*/
val activityView: SettingsActivityView?
/**
* Tell the Fragment to tell the containing Activity to show a new
* Fragment containing a submenu of settings.
*
* @param menuKey Identifier for the settings group that should be shown.
*/
fun loadSubMenu(menuKey: String)
/**
* Have the fragment tell the containing Activity that a setting was modified.
*/
fun onSettingChanged()
}

View file

@ -28,6 +28,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.window.layout.FoldingFeature import androidx.window.layout.FoldingFeature
@ -37,6 +38,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
@ -45,7 +47,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.overlay.InputOverlay import org.yuzu.yuzu_emu.overlay.InputOverlay
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
@ -139,7 +140,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
R.id.menu_settings -> { R.id.menu_settings -> {
SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
SettingsFile.FILE_NAME_CONFIG
)
binding.root.findNavController().navigate(action)
true true
} }
@ -211,7 +216,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (!DirectoryInitialization.areDirectoriesReady) { if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start(requireContext()) DirectoryInitialization.start()
} }
updateScreenLayout() updateScreenLayout()

View file

@ -25,17 +25,18 @@ import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() {
R.string.advanced_settings, R.string.advanced_settings,
R.string.settings_description, R.string.settings_description,
R.drawable.ic_settings, R.drawable.ic_settings,
{ SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
SettingsFile.FILE_NAME_CONFIG
)
binding.root.findNavController().navigate(action)
}
) )
) )
add( add(
@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() {
R.string.preferences_theme, R.string.preferences_theme,
R.string.theme_and_color_description, R.string.theme_and_color_description,
R.drawable.ic_palette, R.drawable.ic_palette,
{ SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") } {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
Settings.SECTION_THEME
)
binding.root.findNavController().navigate(action)
}
) )
) )
add( add(

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class SettingsViewModel : ViewModel() {
var game: Game? = null
var shouldSave = false
private val _toolbarTitle = MutableLiveData("")
val toolbarTitle: LiveData<String> get() = _toolbarTitle
private val _shouldRecreate = MutableLiveData(false)
val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
private val _shouldNavigateBack = MutableLiveData(false)
val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
private val _shouldShowResetSettingsDialog = MutableLiveData(false)
val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
fun setToolbarTitle(value: String) {
_toolbarTitle.value = value
}
fun setShouldRecreate(value: Boolean) {
_shouldRecreate.value = value
}
fun setShouldNavigateBack(value: Boolean) {
_shouldNavigateBack.value = value
}
fun setShouldShowResetSettingsDialog(value: Boolean) {
_shouldShowResetSettingsDialog.value = value
}
fun clear() {
game = null
shouldSave = false
}
}

View file

@ -33,13 +33,13 @@ import java.io.IOException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
@ -105,11 +105,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
when (it.itemId) { when (it.itemId) {
R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
R.id.searchFragment -> gamesViewModel.setSearchFocused(true) R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
R.id.homeSettingsFragment -> SettingsActivity.launch( R.id.homeSettingsFragment -> {
this, val action = HomeNavigationDirections.actionGlobalSettingsActivity(
SettingsFile.FILE_NAME_CONFIG, null,
"" SettingsFile.FILE_NAME_CONFIG
) )
navHostFragment.navController.navigate(action)
}
} }
} }

View file

@ -3,18 +3,18 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.content.Context
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
object DirectoryInitialization { object DirectoryInitialization {
private var userPath: String? = null private var userPath: String? = null
var areDirectoriesReady: Boolean = false var areDirectoriesReady: Boolean = false
fun start(context: Context) { fun start() {
if (!areDirectoriesReady) { if (!areDirectoriesReady) {
initializeInternalStorage(context) initializeInternalStorage()
NativeLibrary.initializeEmulation() NativeLibrary.initializeEmulation()
areDirectoriesReady = true areDirectoriesReady = true
} }
@ -26,9 +26,9 @@ object DirectoryInitialization {
return userPath return userPath
} }
private fun initializeInternalStorage(context: Context) { private fun initializeInternalStorage() {
try { try {
userPath = context.getExternalFilesDir(null)!!.canonicalPath userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
NativeLibrary.setAppDirectory(userPath!!) NativeLibrary.setAppDirectory(userPath!!)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="125"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1"
android:toAlpha="0" />
<translate
android:duration="125"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="0"
android:toXDelta="-75" />
</set>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="0"
android:toAlpha="1" />
<translate
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="-200"
android:toXDelta="0" />
</set>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="125"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1"
android:toAlpha="0" />
<translate
android:duration="125"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="0"
android:toXDelta="75" />
</set>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="0"
android:toAlpha="1" />
<translate
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="200"
android:toXDelta="0" />
</set>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1"
android:toAlpha="0" />
</set>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="translationX"
android:valueType="floatType"
android:valueFrom="-1280dp"
android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="300"/>
<objectAnimator
android:propertyName="alpha"
android:valueType="floatType"
android:valueFrom="0"
android:valueTo="1"
android:interpolator="@android:interpolator/accelerate_quad"
android:duration="300"/>
</set>

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- This animation is used ONLY when a submenu is replaced. -->
<objectAnimator
android:propertyName="translationX"
android:valueType="floatType"
android:valueFrom="0"
android:valueTo="-1280dp"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="200"/>
<objectAnimator
android:propertyName="alpha"
android:valueType="floatType"
android:valueFrom="1"
android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="200"/>
</set>

View file

@ -1,42 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/coordinator_main"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"> android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout <androidx.fragment.app.FragmentContainerView
android:id="@+id/appbar_settings" android:id="@+id/fragment_container"
android:layout_width="match_parent" android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_height="wrap_content" android:layout_width="0dp"
android:fitsSystemWindows="true" android:layout_height="0dp"
app:elevation="0dp"> app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
<com.google.android.material.appbar.CollapsingToolbarLayout app:layout_constraintLeft_toLeftOf="parent"
style="?attr/collapsingToolbarLayoutMediumStyle" app:layout_constraintRight_toRightOf="parent"
android:id="@+id/toolbar_settings_layout" app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent" tools:layout="@layout/fragment_settings" />
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_settings"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/frame_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="12dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<View <View
android:id="@+id/navigation_bar_shade" android:id="@+id/navigation_bar_shade"
@ -45,6 +27,8 @@
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:clickable="false" android:clickable="false"
android:focusable="false" android:focusable="false"
android:layout_gravity="bottom|center_horizontal" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,14 +1,41 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_settings_layout"
style="?attr/collapsingToolbarLayoutMediumStyle"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_settings"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_back" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_settings" android:id="@+id/list_settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface" android:clipToPadding="false"
android:clipToPadding="false" /> app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</FrameLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu />

View file

@ -15,4 +15,21 @@
app:argType="org.yuzu.yuzu_emu.model.Game" /> app:argType="org.yuzu.yuzu_emu.model.Game" />
</fragment> </fragment>
<activity
android:id="@+id/settingsActivity"
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
android:label="SettingsActivity">
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true" />
<argument
android:name="menuTag"
app:argType="string" />
</activity>
<action
android:id="@+id/action_global_settingsActivity"
app:destination="@id/settingsActivity" />
</navigation> </navigation>

View file

@ -70,4 +70,21 @@
app:destination="@id/emulationActivity" app:destination="@id/emulationActivity"
app:launchSingleTop="true" /> app:launchSingleTop="true" />
<activity
android:id="@+id/settingsActivity"
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
android:label="SettingsActivity">
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true" />
<argument
android:name="menuTag"
app:argType="string" />
</activity>
<action
android:id="@+id/action_global_settingsActivity"
app:destination="@id/settingsActivity" />
</navigation> </navigation>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/settings_navigation"
app:startDestination="@id/settingsFragment">
<fragment
android:id="@+id/settingsFragment"
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
android:label="SettingsFragment">
<argument
android:name="menuTag"
app:argType="string" />
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true" />
</fragment>
<action
android:id="@+id/action_global_settingsFragment"
app:destination="@id/settingsFragment" />
</navigation>

View file

@ -74,6 +74,7 @@
<string name="install_gpu_driver">Install GPU driver</string> <string name="install_gpu_driver">Install GPU driver</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="advanced_settings">Advanced settings</string> <string name="advanced_settings">Advanced settings</string>
<string name="advanced_settings_game">Advanced settings: %1$s</string>
<string name="settings_description">Configure emulator settings</string> <string name="settings_description">Configure emulator settings</string>
<string name="search_recently_played">Recently played</string> <string name="search_recently_played">Recently played</string>
<string name="search_recently_added">Recently added</string> <string name="search_recently_added">Recently added</string>