Просмотр исходного кода

android: Persist settings across configuration changes

Mostly things get refactored here to remove previous assumptions made about how the activity/fragment lifecycles would operate. The important change for persistence is removing the assumption that the user will be at the first settings fragment on recreation when deciding whether or not to reload settings. Now we check a flag in Settings to know if we loaded the settings within this lifecycle.
Charles Lombardo 3 лет назад
Родитель
Сommit
295ffd4d47

+ 3 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt

@@ -13,6 +13,8 @@ import java.util.*
 class Settings {
 class Settings {
     private var gameId: String? = null
     private var gameId: String? = null
 
 
+    var isLoaded = false
+
     /**
     /**
      * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
      * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
      * when getting a key not already in the map
      * when getting a key not already in the map
@@ -43,6 +45,7 @@ class Settings {
         if (!TextUtils.isEmpty(gameId)) {
         if (!TextUtils.isEmpty(gameId)) {
             loadCustomGameSettings(gameId!!, view)
             loadCustomGameSettings(gameId!!, view)
         }
         }
+        isLoaded = true
     }
     }
 
 
     private fun loadYuzuSettings(view: SettingsActivityView) {
     private fun loadYuzuSettings(view: SettingsActivityView) {

+ 1 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt

@@ -3,5 +3,5 @@ package org.yuzu.yuzu_emu.features.settings.model
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModel
 
 
 class SettingsViewModel : ViewModel() {
 class SettingsViewModel : ViewModel() {
-    var settings = Settings()
+    val settings = Settings()
 }
 }

+ 4 - 8
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt

@@ -35,11 +35,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
 
 
     private val settingsViewModel: SettingsViewModel by viewModels()
     private val settingsViewModel: SettingsViewModel by viewModels()
 
 
-    override var settings: Settings
-        get() = settingsViewModel.settings
-        set(settings) {
-            settingsViewModel.settings = settings
-        }
+    override val settings: Settings get() = settingsViewModel.settings
 
 
     override fun onCreate(savedInstanceState: Bundle?) {
     override fun onCreate(savedInstanceState: Bundle?) {
         ThemeHelper.setTheme(this)
         ThemeHelper.setTheme(this)
@@ -179,14 +175,14 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         ).show()
         ).show()
     }
     }
 
 
-    override fun onSettingsFileLoaded(settings: Settings) {
+    override fun onSettingsFileLoaded() {
         val fragment: SettingsFragmentView? = settingsFragment
         val fragment: SettingsFragmentView? = settingsFragment
-        fragment?.onSettingsFileLoaded(settings)
+        fragment?.loadSettingsList()
     }
     }
 
 
     override fun onSettingsFileNotFound() {
     override fun onSettingsFileNotFound() {
         val fragment: SettingsFragmentView? = settingsFragment
         val fragment: SettingsFragmentView? = settingsFragment
-        fragment?.loadDefaultSettings()
+        fragment?.loadSettingsList()
     }
     }
 
 
     override fun showToastMessage(message: String, is_long: Boolean) {
     override fun showToastMessage(message: String, is_long: Boolean) {

+ 2 - 2
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt

@@ -36,7 +36,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
     }
     }
 
 
     private fun loadSettingsUI() {
     private fun loadSettingsUI() {
-        if (settings.isEmpty) {
+        if (!settings.isLoaded) {
             if (!TextUtils.isEmpty(gameId)) {
             if (!TextUtils.isEmpty(gameId)) {
                 settings.loadSettings(gameId, activityView)
                 settings.loadSettings(gameId, activityView)
             } else {
             } else {
@@ -44,7 +44,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
             }
             }
         }
         }
         activityView.showSettingsFragment(menuTag, false, gameId)
         activityView.showSettingsFragment(menuTag, false, gameId)
-        activityView.onSettingsFileLoaded(settings)
+        activityView.onSettingsFileLoaded()
     }
     }
 
 
     private fun prepareDirectoriesIfNeeded() {
     private fun prepareDirectoriesIfNeeded() {

+ 5 - 7
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt

@@ -24,19 +24,17 @@ interface SettingsActivityView {
      * loaded from disk, so that each Fragment doesn't need to perform its own
      * loaded from disk, so that each Fragment doesn't need to perform its own
      * read operation.
      * read operation.
      *
      *
-     * @return A possibly null HashMap of Settings.
+     * @return A HashMap of Settings.
      */
      */
-    var settings: Settings
+    val settings: Settings
 
 
     /**
     /**
-     * Called when an asynchronous load operation completes.
-     *
-     * @param settings The (possibly null) result of the ini load operation.
+     * Called when a load operation completes.
      */
      */
-    fun onSettingsFileLoaded(settings: Settings)
+    fun onSettingsFileLoaded()
 
 
     /**
     /**
-     * Called when an asynchronous load operation fails.
+     * Called when a load operation fails.
      */
      */
     fun onSettingsFileNotFound()
     fun onSettingsFileNotFound()
 
 

+ 5 - 4
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt

@@ -12,6 +12,7 @@ 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.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
@@ -93,7 +94,7 @@ class SettingsAdapter(
         return getItem(position).type
         return getItem(position).type
     }
     }
 
 
-    fun setSettings(settings: ArrayList<SettingsItem>?) {
+    fun setSettingsList(settings: ArrayList<SettingsItem>?) {
         this.settings = settings
         this.settings = settings
         notifyDataSetChanged()
         notifyDataSetChanged()
     }
     }
@@ -144,7 +145,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.fragmentActivity)) {
+        if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
             timeFormat = TimeFormat.CLOCK_24H
             timeFormat = TimeFormat.CLOCK_24H
         }
         }
 
 
@@ -161,7 +162,7 @@ class SettingsAdapter(
 
 
         datePicker.addOnPositiveButtonClickListener {
         datePicker.addOnPositiveButtonClickListener {
             timePicker.show(
             timePicker.show(
-                fragmentView.fragmentActivity.supportFragmentManager,
+                (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
                 "TimePicker"
                 "TimePicker"
             )
             )
         }
         }
@@ -177,7 +178,7 @@ class SettingsAdapter(
             item.setSelectedValue(rtcString)
             item.setSelectedValue(rtcString)
             clickedItem = null
             clickedItem = null
         }
         }
-        datePicker.show(fragmentView.fragmentActivity.supportFragmentManager, "DatePicker")
+        datePicker.show((fragmentView.activityView as AppCompatActivity).supportFragmentManager, "DatePicker")
     }
     }
 
 
     fun onSliderClick(item: SliderSetting, position: Int) {
     fun onSliderClick(item: SliderSetting, position: Int) {

+ 9 - 17
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt

@@ -12,7 +12,6 @@ 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.FragmentActivity
 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 org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
@@ -21,10 +20,9 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 
 
 class SettingsFragment : Fragment(), SettingsFragmentView {
 class SettingsFragment : Fragment(), SettingsFragmentView {
-    override lateinit var fragmentActivity: FragmentActivity
+    override var activityView: SettingsActivityView? = null
 
 
-    private val presenter = SettingsFragmentPresenter(this)
-    private var activityView: SettingsActivityView? = null
+    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
@@ -32,15 +30,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
 
 
     override fun onAttach(context: Context) {
     override fun onAttach(context: Context) {
         super.onAttach(context)
         super.onAttach(context)
-        activityView = context as SettingsActivityView
-        fragmentActivity = requireActivity()
+        activityView = requireActivity() as SettingsActivityView
     }
     }
 
 
     override fun onCreate(savedInstanceState: Bundle?) {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         super.onCreate(savedInstanceState)
         val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
         val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
         val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
         val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
-        presenter.onCreate(menuTag!!, gameId!!)
+        fragmentPresenter.onCreate(menuTag!!, gameId!!)
     }
     }
 
 
     override fun onCreateView(
     override fun onCreateView(
@@ -61,8 +58,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
             layoutManager = LinearLayoutManager(activity)
             layoutManager = LinearLayoutManager(activity)
             addItemDecoration(dividerDecoration)
             addItemDecoration(dividerDecoration)
         }
         }
-        val activity = activity as SettingsActivityView?
-        presenter.onViewCreated(activity!!.settings)
+        fragmentPresenter.onViewCreated()
 
 
         setInsets()
         setInsets()
     }
     }
@@ -75,16 +71,12 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
         }
         }
     }
     }
 
 
-    override fun onSettingsFileLoaded(settings: Settings) {
-        presenter.setSettings(settings)
-    }
-
     override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
     override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
-        settingsAdapter!!.setSettings(settingsList)
+        settingsAdapter!!.setSettingsList(settingsList)
     }
     }
 
 
-    override fun loadDefaultSettings() {
-        presenter.loadDefaultSettings()
+    override fun loadSettingsList() {
+        fragmentPresenter.loadSettingsList()
     }
     }
 
 
     override fun loadSubMenu(menuKey: String) {
     override fun loadSubMenu(menuKey: String) {
@@ -100,7 +92,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
     }
     }
 
 
     override fun putSetting(setting: Setting) {
     override fun putSetting(setting: Setting) {
-        presenter.putSetting(setting)
+        fragmentPresenter.putSetting(setting)
     }
     }
 
 
     override fun onSettingChanged() {
     override fun onSettingChanged() {

+ 19 - 40
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt

@@ -4,58 +4,37 @@
 package org.yuzu.yuzu_emu.features.settings.ui
 package org.yuzu.yuzu_emu.features.settings.ui
 
 
 import android.text.TextUtils
 import android.text.TextUtils
+import androidx.appcompat.app.AppCompatActivity
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.features.settings.model.Setting
 import org.yuzu.yuzu_emu.features.settings.model.Setting
 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.model.StringSetting
 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
 
 
 class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
 class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
     private var menuTag: String? = null
     private var menuTag: String? = null
     private lateinit var gameId: String
     private lateinit var gameId: String
-    private var settings: Settings? = null
     private var settingsList: ArrayList<SettingsItem>? = null
     private var settingsList: ArrayList<SettingsItem>? = null
 
 
+    private val settingsActivity get() = fragmentView.activityView as AppCompatActivity
+    private val settings get() = fragmentView.activityView!!.settings
+
     fun onCreate(menuTag: String, gameId: String) {
     fun onCreate(menuTag: String, gameId: String) {
         this.gameId = gameId
         this.gameId = gameId
         this.menuTag = menuTag
         this.menuTag = menuTag
     }
     }
 
 
-    fun onViewCreated(settings: Settings) {
-        setSettings(settings)
-    }
-
-    fun putSetting(setting: Setting) {
-        settings!!.getSection(setting.section)!!.putSetting(setting)
-    }
-
-    private fun asStringSetting(setting: Setting?): StringSetting? {
-        if (setting == null) {
-            return null
-        }
-        val stringSetting = StringSetting(setting.key, setting.section, setting.valueAsString)
-        putSetting(stringSetting)
-        return stringSetting
-    }
-
-    fun loadDefaultSettings() {
+    fun onViewCreated() {
         loadSettingsList()
         loadSettingsList()
     }
     }
 
 
-    fun setSettings(settings: Settings) {
-        if (settingsList == null) {
-            this.settings = settings
-            loadSettingsList()
-        } else {
-            fragmentView.fragmentActivity.setTitle(R.string.preferences_settings)
-            fragmentView.showSettingsList(settingsList!!)
-        }
+    fun putSetting(setting: Setting) {
+        settings.getSection(setting.section)!!.putSetting(setting)
     }
     }
 
 
-    private fun loadSettingsList() {
+    fun loadSettingsList() {
         if (!TextUtils.isEmpty(gameId)) {
         if (!TextUtils.isEmpty(gameId)) {
-            fragmentView.fragmentActivity.title = "Game Settings: $gameId"
+            settingsActivity.title = "Game Settings: $gameId"
         }
         }
         val sl = ArrayList<SettingsItem>()
         val sl = ArrayList<SettingsItem>()
         if (menuTag == null) {
         if (menuTag == null) {
@@ -77,7 +56,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
     }
 
 
     private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
     private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
-        fragmentView.fragmentActivity.setTitle(R.string.preferences_settings)
+        settingsActivity.setTitle(R.string.preferences_settings)
         sl.apply {
         sl.apply {
             add(
             add(
                 SubmenuSetting(
                 SubmenuSetting(
@@ -119,12 +98,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
     }
 
 
     private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
     private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
-        fragmentView.fragmentActivity.setTitle(R.string.preferences_general)
-        val rendererSection = settings!!.getSection(Settings.SECTION_RENDERER)
+        settingsActivity.setTitle(R.string.preferences_general)
+        val rendererSection = settings.getSection(Settings.SECTION_RENDERER)
         val frameLimitEnable =
         val frameLimitEnable =
             rendererSection!!.getSetting(SettingsFile.KEY_RENDERER_USE_SPEED_LIMIT)
             rendererSection!!.getSetting(SettingsFile.KEY_RENDERER_USE_SPEED_LIMIT)
         val frameLimitValue = rendererSection.getSetting(SettingsFile.KEY_RENDERER_SPEED_LIMIT)
         val frameLimitValue = rendererSection.getSetting(SettingsFile.KEY_RENDERER_SPEED_LIMIT)
-        val cpuSection = settings!!.getSection(Settings.SECTION_CPU)
+        val cpuSection = settings.getSection(Settings.SECTION_CPU)
         val cpuAccuracy = cpuSection!!.getSetting(SettingsFile.KEY_CPU_ACCURACY)
         val cpuAccuracy = cpuSection!!.getSetting(SettingsFile.KEY_CPU_ACCURACY)
         sl.apply {
         sl.apply {
             add(
             add(
@@ -166,8 +145,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
     }
 
 
     private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
     private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
-        fragmentView.fragmentActivity.setTitle(R.string.preferences_system)
-        val systemSection = settings!!.getSection(Settings.SECTION_SYSTEM)
+        settingsActivity.setTitle(R.string.preferences_system)
+        val systemSection = settings.getSection(Settings.SECTION_SYSTEM)
         val dockedMode = systemSection!!.getSetting(SettingsFile.KEY_USE_DOCKED_MODE)
         val dockedMode = systemSection!!.getSetting(SettingsFile.KEY_USE_DOCKED_MODE)
         val region = systemSection.getSetting(SettingsFile.KEY_REGION_INDEX)
         val region = systemSection.getSetting(SettingsFile.KEY_REGION_INDEX)
         val language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE_INDEX)
         val language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE_INDEX)
@@ -210,8 +189,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
     }
 
 
     private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
     private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
-        fragmentView.fragmentActivity.setTitle(R.string.preferences_graphics)
-        val rendererSection = settings!!.getSection(Settings.SECTION_RENDERER)
+        settingsActivity.setTitle(R.string.preferences_graphics)
+        val rendererSection = settings.getSection(Settings.SECTION_RENDERER)
         val rendererBackend = rendererSection!!.getSetting(SettingsFile.KEY_RENDERER_BACKEND)
         val rendererBackend = rendererSection!!.getSetting(SettingsFile.KEY_RENDERER_BACKEND)
         val rendererAccuracy = rendererSection.getSetting(SettingsFile.KEY_RENDERER_ACCURACY)
         val rendererAccuracy = rendererSection.getSetting(SettingsFile.KEY_RENDERER_ACCURACY)
         val rendererResolution = rendererSection.getSetting(SettingsFile.KEY_RENDERER_RESOLUTION)
         val rendererResolution = rendererSection.getSetting(SettingsFile.KEY_RENDERER_RESOLUTION)
@@ -305,8 +284,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
     }
 
 
     private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
     private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
-        fragmentView.fragmentActivity.setTitle(R.string.preferences_audio)
-        val audioSection = settings!!.getSection(Settings.SECTION_AUDIO)
+        settingsActivity.setTitle(R.string.preferences_audio)
+        val audioSection = settings.getSection(Settings.SECTION_AUDIO)
         val audioVolume = audioSection!!.getSetting(SettingsFile.KEY_AUDIO_VOLUME)
         val audioVolume = audioSection!!.getSetting(SettingsFile.KEY_AUDIO_VOLUME)
         sl.add(
         sl.add(
             SliderSetting(
             SliderSetting(

+ 3 - 14
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt

@@ -3,9 +3,7 @@
 
 
 package org.yuzu.yuzu_emu.features.settings.ui
 package org.yuzu.yuzu_emu.features.settings.ui
 
 
-import androidx.fragment.app.FragmentActivity
 import org.yuzu.yuzu_emu.features.settings.model.Setting
 import org.yuzu.yuzu_emu.features.settings.model.Setting
-import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 
 
 /**
 /**
@@ -13,14 +11,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
  * this type of view will each display a layer of the setting hierarchy.
  * this type of view will each display a layer of the setting hierarchy.
  */
  */
 interface SettingsFragmentView {
 interface SettingsFragmentView {
-    /**
-     * Called by the containing Activity to notify the Fragment that an
-     * asynchronous load operation completed.
-     *
-     * @param settings The (possibly null) result of the ini load operation.
-     */
-    fun onSettingsFileLoaded(settings: Settings)
-
     /**
     /**
      * Pass an ArrayList to the View so that it can be displayed on screen.
      * Pass an ArrayList to the View so that it can be displayed on screen.
      *
      *
@@ -29,15 +19,14 @@ interface SettingsFragmentView {
     fun showSettingsList(settingsList: ArrayList<SettingsItem>)
     fun showSettingsList(settingsList: ArrayList<SettingsItem>)
 
 
     /**
     /**
-     * Called by the containing Activity when an asynchronous load operation fails.
-     * Instructs the Fragment to load the settings screen with defaults selected.
+     * Instructs the Fragment to load the settings screen.
      */
      */
-    fun loadDefaultSettings()
+    fun loadSettingsList()
 
 
     /**
     /**
      * @return The Fragment's containing activity.
      * @return The Fragment's containing activity.
      */
      */
-    val fragmentActivity: FragmentActivity
+    val activityView: SettingsActivityView?
 
 
     /**
     /**
      * Tell the Fragment to tell the containing Activity to show a new
      * Tell the Fragment to tell the containing Activity to show a new