diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 75e10a54..844597fa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,7 +38,12 @@ android:name="android.support.PARENT_ACTIVITY" android:value="emu.skyline.MainActivity" /> - + + + + @@ -47,7 +52,8 @@ android:name="emu.skyline.EmulationActivity" android:configChanges="orientation|screenSize" android:launchMode="singleInstance" - android:screenOrientation="landscape"> + android:screenOrientation="landscape" + tools:ignore="LockedOrientationActivity"> diff --git a/app/src/main/java/emu/skyline/loader/BaseLoader.kt b/app/src/main/java/emu/skyline/loader/BaseLoader.kt index bbae7f94..63367c91 100644 --- a/app/src/main/java/emu/skyline/loader/BaseLoader.kt +++ b/app/src/main/java/emu/skyline/loader/BaseLoader.kt @@ -22,7 +22,9 @@ import java.util.* * An enumeration of all supported ROM formats */ enum class RomFormat { - NRO, XCI, NSP + NRO, + XCI, + NSP, } /** @@ -80,7 +82,7 @@ class AppEntry : Serializable { val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.moveToFirst() cursor.getString(nameIndex) - }!! + }!!.dropLast(format.name.length + 1) this.format = format this.uri = uri } @@ -125,12 +127,12 @@ class AppEntry : Serializable { */ internal abstract class BaseLoader(val context: Context, val format: RomFormat) { /** - * This returns an AppEntry object for the supplied document + * This is used to get the [AppEntry] for the specified [file] at the supplied [uri] */ abstract fun getAppEntry(file: RandomAccessDocument, uri: Uri): AppEntry /** - * This returns if the supplied document is a valid ROM or not + * This returns if the supplied [file] is a valid ROM or not */ abstract fun verifyFile(file: RandomAccessDocument): Boolean } diff --git a/app/src/main/java/emu/skyline/loader/NroLoader.kt b/app/src/main/java/emu/skyline/loader/NroLoader.kt index 828c2d07..56d4a5df 100644 --- a/app/src/main/java/emu/skyline/loader/NroLoader.kt +++ b/app/src/main/java/emu/skyline/loader/NroLoader.kt @@ -15,6 +15,9 @@ import java.io.IOException * This loader is used to load in NRO (Nintendo Relocatable Object) files (https://switchbrew.org/wiki/NRO) */ internal class NroLoader(context: Context) : BaseLoader(context, RomFormat.NRO) { + /** + * This is used to get the [AppEntry] for the specified NRO + */ override fun getAppEntry(file: RandomAccessDocument, uri: Uri): AppEntry { return try { file.seek(0x18) // Skip to NroHeader.size @@ -54,6 +57,9 @@ internal class NroLoader(context: Context) : BaseLoader(context, RomFormat.NRO) } } + /** + * This verifies if [file] is a valid NRO file + */ override fun verifyFile(file: RandomAccessDocument): Boolean { try { file.seek(0x10) // Skip to NroHeader.magic diff --git a/app/src/main/java/emu/skyline/utility/FolderActivity.kt b/app/src/main/java/emu/skyline/preference/FolderActivity.kt similarity index 74% rename from app/src/main/java/emu/skyline/utility/FolderActivity.kt rename to app/src/main/java/emu/skyline/preference/FolderActivity.kt index 112a46c0..04d0ba92 100644 --- a/app/src/main/java/emu/skyline/utility/FolderActivity.kt +++ b/app/src/main/java/emu/skyline/preference/FolderActivity.kt @@ -3,7 +3,7 @@ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) */ -package emu.skyline.utility +package emu.skyline.preference import android.app.Activity import android.content.Intent @@ -11,26 +11,39 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.preference.PreferenceManager +/** + * This activity is used to select a new search location and set preferences to reflect that + */ class FolderActivity : AppCompatActivity() { + /** + * This launches the [Intent.ACTION_OPEN_DOCUMENT_TREE] intent on creation + */ override fun onCreate(state: Bundle?) { super.onCreate(state) + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) this.startActivityForResult(intent, 1) } + /** + * This changes the search location preference if the [Intent.ACTION_OPEN_DOCUMENT_TREE] has returned and [finish]es the activity + */ public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) + if (resultCode == Activity.RESULT_OK) { if (requestCode == 1) { val uri = data!!.data!! + contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + PreferenceManager.getDefaultSharedPreferences(this).edit() .putString("search_location", uri.toString()) .putBoolean("refresh_required", true) .apply() - finish() } - } else - finish() + } + + finish() } } diff --git a/app/src/main/java/emu/skyline/preference/FolderPreference.kt b/app/src/main/java/emu/skyline/preference/FolderPreference.kt new file mode 100644 index 00000000..d970bf95 --- /dev/null +++ b/app/src/main/java/emu/skyline/preference/FolderPreference.kt @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: LGPL-3.0-or-later + * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.preference + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.util.AttributeSet +import androidx.preference.Preference +import androidx.preference.R + +/** + * This preference shows the decoded URI of it's preference and launches [FolderActivity] + */ +class FolderPreference : Preference { + /** + * The directory the preference is currently set to + */ + private var mDirectory: String? = null + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + summaryProvider = SimpleSummaryProvider() + } + + constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, R.attr.preferenceStyle) + + constructor(context: Context?) : this(context, null) + + /** + * This launches [FolderActivity] on click to change the directory + */ + override fun onClick() { + val intent = Intent(context, FolderActivity::class.java) + (context as Activity).startActivityForResult(intent, 0) + } + + /** + * This sets the initial value of [mDirectory] + */ + override fun onSetInitialValue(defaultValue: Any?) { + mDirectory = getPersistedString(defaultValue as String?) + } + + /** + * This [Preference.SummaryProvider] is used to set the summary for URI values + */ + private class SimpleSummaryProvider : SummaryProvider { + /** + * This returns the decoded URI of the directory as the summary + */ + override fun provideSummary(preference: FolderPreference): CharSequence { + return Uri.decode(preference.mDirectory) ?: "" + } + } +} diff --git a/app/src/main/java/emu/skyline/preference/LicenseDialog.kt b/app/src/main/java/emu/skyline/preference/LicenseDialog.kt new file mode 100644 index 00000000..55904bf3 --- /dev/null +++ b/app/src/main/java/emu/skyline/preference/LicenseDialog.kt @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: LGPL-3.0-or-later + * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.preference + +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import androidx.fragment.app.DialogFragment +import emu.skyline.R +import kotlinx.android.synthetic.main.license_dialog.* + +/** + * This dialog is used to display the contents of a license for a particular project + */ +class LicenseDialog : DialogFragment() { + /** + * This inflates the layout of the dialog and sets the minimum width/height to 90% of the screen size + */ + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val layout = layoutInflater.inflate(R.layout.license_dialog, container) + + val displayRectangle = Rect() + val window: Window = activity!!.window + window.decorView.getWindowVisibleDisplayFrame(displayRectangle) + + layout.minimumWidth = ((displayRectangle.width() * 0.9f).toInt()) + layout.minimumHeight = ((displayRectangle.height() * 0.9f).toInt()) + + return layout + } + + /** + * This sets the [license_url] and [license_content] based on arguments passed + */ + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + license_url.text = arguments?.getString("libraryUrl")!! + license_content.text = context?.getString(arguments?.getInt("libraryLicense")!!)!! + } +} diff --git a/app/src/main/java/emu/skyline/utility/LicensePreference.kt b/app/src/main/java/emu/skyline/preference/LicensePreference.kt similarity index 66% rename from app/src/main/java/emu/skyline/utility/LicensePreference.kt rename to app/src/main/java/emu/skyline/preference/LicensePreference.kt index 0a997ea4..0fb8b297 100644 --- a/app/src/main/java/emu/skyline/utility/LicensePreference.kt +++ b/app/src/main/java/emu/skyline/preference/LicensePreference.kt @@ -3,30 +3,49 @@ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) */ -package emu.skyline.utility +package emu.skyline.preference import android.content.Context import android.os.Bundle import android.util.AttributeSet -import android.util.Log import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.DialogFragment.STYLE_NORMAL import androidx.fragment.app.FragmentManager import androidx.preference.Preference import emu.skyline.R -import emu.skyline.SettingsActivity +/** + * This preference is used to show licenses and the source of a library + */ class LicensePreference : Preference { + /** + * The [FragmentManager] is used to show the [LicenseDialog] fragment + */ private val fragmentManager: FragmentManager + + /** + * The tag used by this preference when launching a corresponding fragment + */ private val mDialogFragmentTag = "LicensePreference" + + /** + * The URL of the library + */ private var libraryUrl: String? = null + + /** + * The contents of the license of this library + */ private var libraryLicense: Int? = null + /** + * The constructor assigns the [fragmentManager] from the activity and finds [libraryUrl] and [libraryLicense] in the attributes + */ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { fragmentManager = (context as AppCompatActivity).supportFragmentManager for (i in 0 until attrs!!.attributeCount) { val attr = attrs.getAttributeName(i) + if (attr.equals("libraryUrl", ignoreCase = true)) libraryUrl = attrs.getAttributeValue(i) else if (attr.equals("libraryLicense", ignoreCase = true)) @@ -34,25 +53,26 @@ class LicensePreference : Preference { } } - constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, R.style.LicenseDialogTheme) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 0) constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, R.attr.dialogPreferenceStyle) constructor(context: Context?) : this(context, null) + /** + * The [LicenseDialog] fragment is shown using [fragmentManager] on click with [libraryUrl] and [libraryLicense] passed as arguments + */ override fun onClick() { if (fragmentManager.findFragmentByTag(mDialogFragmentTag) != null) return - val fragment = LicenseDialog() + val dialog = LicenseDialog() val bundle = Bundle(2) bundle.putString("libraryUrl", libraryUrl!!) bundle.putInt("libraryLicense", libraryLicense!!) - fragment.arguments = bundle + dialog.arguments = bundle - fragment.setStyle(STYLE_NORMAL, R.style.LicenseDialogTheme) - - fragment.show(fragmentManager, mDialogFragmentTag) + dialog.show(fragmentManager, mDialogFragmentTag) } } diff --git a/app/src/main/java/emu/skyline/utility/ThemePreference.kt b/app/src/main/java/emu/skyline/preference/ThemePreference.kt similarity index 82% rename from app/src/main/java/emu/skyline/utility/ThemePreference.kt rename to app/src/main/java/emu/skyline/preference/ThemePreference.kt index c62f14a7..7750d46f 100644 --- a/app/src/main/java/emu/skyline/utility/ThemePreference.kt +++ b/app/src/main/java/emu/skyline/preference/ThemePreference.kt @@ -3,13 +3,16 @@ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) */ -package emu.skyline.utility +package emu.skyline.preference import android.content.Context import android.util.AttributeSet import androidx.appcompat.app.AppCompatDelegate import androidx.preference.ListPreference +/** + * This preference is used to set the theme to Light/Dark mode + */ class ThemePreference : ListPreference { constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) @@ -17,6 +20,9 @@ class ThemePreference : ListPreference { constructor(context: Context?) : super(context) + /** + * This changes [AppCompatDelegate.sDefaultNightMode] based on what the user's selection is + */ override fun callChangeListener(newValue: Any?): Boolean { AppCompatDelegate.setDefaultNightMode(when ((newValue as String).toInt()) { 0 -> AppCompatDelegate.MODE_NIGHT_NO @@ -24,6 +30,7 @@ class ThemePreference : ListPreference { 2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED }) + return super.callChangeListener(newValue) } } diff --git a/app/src/main/java/emu/skyline/utility/FolderPreference.kt b/app/src/main/java/emu/skyline/utility/FolderPreference.kt deleted file mode 100644 index 300aac35..00000000 --- a/app/src/main/java/emu/skyline/utility/FolderPreference.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-3.0-or-later - * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) - */ - -package emu.skyline.utility - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.res.TypedArray -import android.net.Uri -import android.os.Parcel -import android.os.Parcelable -import android.text.TextUtils -import android.util.AttributeSet -import androidx.preference.Preference - -class FolderPreference : Preference { - private var mDirectory: String? = null - - constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - summaryProvider = SimpleSummaryProvider.instance - } - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { - summaryProvider = SimpleSummaryProvider.instance - } - - constructor(context: Context?) : super(context) { - summaryProvider = SimpleSummaryProvider.instance - } - - override fun onClick() { - val intent = Intent(context, FolderActivity::class.java) - (context as Activity).startActivityForResult(intent, 0) - } - - var directory: String? - get() = mDirectory - set(directory) { - val changed = !TextUtils.equals(mDirectory, directory) - if (changed) { - mDirectory = directory - persistString(directory) - if (changed) { - notifyChanged() - } - } - } - - override fun onGetDefaultValue(a: TypedArray, index: Int): Any { - return a.getString(index)!! - } - - override fun onSetInitialValue(defaultValue: Any?) { - directory = getPersistedString(defaultValue as String?) - } - - override fun onSaveInstanceState(): Parcelable { - val superState = super.onSaveInstanceState() - if (isPersistent) { - return superState - } - val myState = SavedState(superState) - myState.mDirectory = directory - return myState - } - - override fun onRestoreInstanceState(state: Parcelable?) { - if (state == null || state.javaClass != SavedState::class.java) { - super.onRestoreInstanceState(state) - return - } - val myState = state as SavedState - super.onRestoreInstanceState(myState.superState) - directory = myState.mDirectory - } - - internal class SavedState : BaseSavedState { - var mDirectory: String? = null - - constructor(source: Parcel) : super(source) { - mDirectory = source.readString() - } - - constructor(superState: Parcelable?) : super(superState) - - override fun writeToParcel(dest: Parcel, flags: Int) { - super.writeToParcel(dest, flags) - dest.writeString(mDirectory) - } - - override fun describeContents(): Int { - return 0 - } - } - - class SimpleSummaryProvider private constructor() : SummaryProvider { - override fun provideSummary(preference: FolderPreference): CharSequence { - return Uri.decode(preference.directory!!) - } - - companion object { - private var sSimpleSummaryProvider: SimpleSummaryProvider? = null - val instance: SimpleSummaryProvider? - get() { - if (sSimpleSummaryProvider == null) { - sSimpleSummaryProvider = SimpleSummaryProvider() - } - return sSimpleSummaryProvider - } - } - } -} diff --git a/app/src/main/java/emu/skyline/utility/LicenseDialog.kt b/app/src/main/java/emu/skyline/utility/LicenseDialog.kt deleted file mode 100644 index 32f09ed3..00000000 --- a/app/src/main/java/emu/skyline/utility/LicenseDialog.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-3.0-or-later - * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) - */ - -package emu.skyline.utility - -import android.os.Bundle -import android.text.method.ScrollingMovementMethod -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment -import emu.skyline.R -import kotlinx.android.synthetic.main.license_dialog.* - -class LicenseDialog : DialogFragment() { - private var libraryUrl: String = "" - private var libraryLicense: String = "" - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - libraryUrl = arguments?.getString("libraryUrl")!! - libraryLicense = context?.getString(arguments?.getInt("libraryLicense")!!)!! - - return requireActivity().layoutInflater.inflate(R.layout.license_dialog, container) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - license_url.text = libraryUrl - license_content.text = libraryLicense - } -} diff --git a/app/src/main/res/layout/license_dialog.xml b/app/src/main/res/layout/license_dialog.xml index 36f066cc..42cd0a37 100644 --- a/app/src/main/res/layout/license_dialog.xml +++ b/app/src/main/res/layout/license_dialog.xml @@ -1,35 +1,32 @@ - + android:layout_height="wrap_content"> - + android:orientation="vertical" + android:paddingTop="10dp"> - + + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" /> - - - + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 74b6539f..4a5d488f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ The size of the buffer used to store audio samples for ROMs using audren. A lower value will result in less latency but potentially increased audio stutter depending on the performance of the device Licenses + The license of Skyline (LGPLv3 or later) {fmt} We use libfmt for formatting strings (Custom License) oboe @@ -55,5 +56,6 @@ We use Android Material Components to have a consistent material design UI (Apache License 2.0) Kotlin Standard Library We use Kotlin Standard Library for accessing convenience functions in Kotlin (Apache License 2.0) - The license of Skyline (LGPLv3 or later) + Material Design Icons + We use Material Design Icons to have consistent iconography throughout the application diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 79a96a6a..20de3331 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -19,11 +19,11 @@ - - - - - - - - - - - +