mirror of
https://github.com/philj56/gbcc-android.git
synced 2025-02-18 12:30:43 +00:00
Split most classes into separate files.
Non-activity class files are placed into packages named after the associated activity.
This commit is contained in:
parent
0fcbc8521a
commit
64b731af6b
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@ -1,9 +1,5 @@
|
||||
-keep class com.philj56.gbcc.SettingsFragment {
|
||||
*;
|
||||
}
|
||||
|
||||
-keep class com.philj56.gbcc.RomConfigFragment {
|
||||
*;
|
||||
<init>();
|
||||
}
|
||||
|
||||
#-dontobfuscate
|
||||
|
@ -11,18 +11,14 @@
|
||||
package com.philj56.gbcc
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.os.*
|
||||
import android.util.AttributeSet
|
||||
import android.view.*
|
||||
import android.view.View.OnLongClickListener
|
||||
import android.view.View.OnTouchListener
|
||||
import android.widget.FrameLayout
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.ViewCompat
|
||||
@ -32,10 +28,8 @@ import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.philj56.gbcc.databinding.ActivityArrangeBinding
|
||||
import kotlin.math.log
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class ArrangeActivity : BaseActivity() {
|
||||
private val scaleFactorRange : Float = 2f
|
||||
|
||||
@ -311,135 +305,3 @@ class ArrangeActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
class ResizableImage : AppCompatImageView {
|
||||
private var floating: Boolean = false
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
private fun addMotionListener() {
|
||||
setOnTouchListener(OnTouchListener { view, motionEvent ->
|
||||
|
||||
if (!floating) {
|
||||
return@OnTouchListener view.performClick()
|
||||
}
|
||||
|
||||
when (motionEvent.actionMasked) {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
floating = false
|
||||
}
|
||||
}
|
||||
|
||||
view.x = motionEvent.rawX - view.width / 2
|
||||
view.y = motionEvent.rawY - view.height / 2
|
||||
|
||||
return@OnTouchListener true
|
||||
})
|
||||
}
|
||||
|
||||
private fun addLongClickListener() {
|
||||
setOnLongClickListener(OnLongClickListener {
|
||||
floating = true
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
return@OnLongClickListener false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ResizableLayout : FrameLayout {
|
||||
private var floating: Boolean = false
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
private fun addMotionListener() {
|
||||
setOnTouchListener(OnTouchListener { view, motionEvent ->
|
||||
|
||||
if (!floating) {
|
||||
return@OnTouchListener view.performClick()
|
||||
}
|
||||
|
||||
when (motionEvent.actionMasked) {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
floating = false
|
||||
}
|
||||
}
|
||||
|
||||
view.x = motionEvent.rawX - view.width / 2
|
||||
view.y = motionEvent.rawY - view.height / 2
|
||||
|
||||
return@OnTouchListener true
|
||||
})
|
||||
}
|
||||
|
||||
private fun addLongClickListener() {
|
||||
setOnLongClickListener(OnLongClickListener {
|
||||
floating = true
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
return@OnLongClickListener false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenPlaceholder : AppCompatImageView {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
setMeasuredDimension(160, 144)
|
||||
layoutParams = ViewGroup.LayoutParams(160, 144)
|
||||
}
|
||||
|
||||
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
||||
setMeasuredDimension(160, 144)
|
||||
layoutParams = ViewGroup.LayoutParams(160, 144)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
|
||||
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
|
||||
|
||||
var width = 0
|
||||
var height = 0
|
||||
val scaleX = widthSize / 160
|
||||
val scaleY = heightSize / 144
|
||||
|
||||
when(widthMode) {
|
||||
MeasureSpec.EXACTLY -> width = widthSize
|
||||
MeasureSpec.AT_MOST -> Unit
|
||||
MeasureSpec.UNSPECIFIED -> width = 160
|
||||
}
|
||||
|
||||
when(heightMode) {
|
||||
MeasureSpec.EXACTLY -> height = heightSize
|
||||
MeasureSpec.AT_MOST -> Unit
|
||||
MeasureSpec.UNSPECIFIED -> height = 144
|
||||
}
|
||||
|
||||
if (width == 0 && height == 0) {
|
||||
val scale = min(scaleX, scaleY)
|
||||
width = 160 * scale
|
||||
height = 144 * scale
|
||||
} else if (width == 0) {
|
||||
width = (height * 160) / 144
|
||||
} else if (height == 0) {
|
||||
height = (width * 144) / 160
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height)
|
||||
}
|
||||
}
|
@ -10,26 +10,13 @@
|
||||
|
||||
package com.philj56.gbcc
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.InputFilter
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philj56.gbcc.cheat.Cheat
|
||||
import com.philj56.gbcc.cheat.CheatAdapter
|
||||
import com.philj56.gbcc.cheat.CheatDialogFragment
|
||||
import com.philj56.gbcc.databinding.ActivityCheatListBinding
|
||||
import java.io.File
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
data class Cheat(var description: String, var code: String, var active: Boolean)
|
||||
|
||||
class CheatActivity : BaseActivity() {
|
||||
private lateinit var configFile: File
|
||||
@ -133,103 +120,3 @@ class CheatActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
class CheatAdapter(
|
||||
context: Context,
|
||||
resource: Int,
|
||||
textViewResourceId: Int,
|
||||
private val objects: List<Cheat>
|
||||
) : ArrayAdapter<Cheat>(context, resource, textViewResourceId, objects) {
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = super.getView(position, convertView, parent)
|
||||
val textDescription = view.findViewById<TextView>(R.id.cheatDescription)
|
||||
val textCode = view.findViewById<TextView>(R.id.cheatCode)
|
||||
val switchActive = view.findViewById<SwitchCompat>(R.id.cheatActive)
|
||||
val (description, code, active) = objects[position]
|
||||
|
||||
textDescription.text = description
|
||||
textCode.text = formatCode(code)
|
||||
switchActive.isChecked = active
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun formatCode(code: String): String {
|
||||
if (code.length == 9) {
|
||||
return code.substring(0, 3) + "-" + code.substring(3, 6) + "-" + code.substring(6, 9)
|
||||
}
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
class CheatDialogFragment(private val index: Int) : DialogFragment() {
|
||||
private var descriptionFilled = false
|
||||
private var codeFilled = false
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val activity = requireActivity() as CheatActivity
|
||||
val view = activity.layoutInflater.inflate(R.layout.dialog_edit_cheat, null, false)
|
||||
val descriptionInput = view.findViewById<EditText>(R.id.cheatDescriptionInput)
|
||||
val codeInput = view.findViewById<EditText>(R.id.cheatCodeInput)
|
||||
val cheatExists = (index >= 0)
|
||||
|
||||
val title: Int
|
||||
if (!cheatExists) {
|
||||
title = R.string.cheat_add_description
|
||||
} else {
|
||||
title = R.string.cheat_edit_description
|
||||
val cheat = activity.getCheat(index)
|
||||
descriptionInput.setText(cheat.description)
|
||||
codeInput.setText(cheat.code)
|
||||
descriptionFilled = true
|
||||
codeFilled = true
|
||||
}
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(title)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
activity.addCheat(
|
||||
Cheat(
|
||||
descriptionInput.text.toString().trim(),
|
||||
codeInput.text.toString().uppercase(),
|
||||
true),
|
||||
index
|
||||
)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setView(view)
|
||||
|
||||
if (cheatExists) {
|
||||
builder.setNeutralButton(R.string.delete) { _, _ -> activity.removeCheat(index) }
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = !cheatExists
|
||||
}
|
||||
|
||||
descriptionInput.filters = arrayOf(InputFilter { source, start, end, _, _, _ ->
|
||||
if (end <= start) {
|
||||
// Deletion, always accept
|
||||
return@InputFilter null
|
||||
}
|
||||
return@InputFilter source.subSequence(start, end).filterNot { it == '#' || it == ';' }
|
||||
})
|
||||
|
||||
descriptionInput.addTextChangedListener { editor ->
|
||||
descriptionFilled = editor?.isNotBlank() ?: false
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||
descriptionFilled && codeFilled
|
||||
}
|
||||
|
||||
codeInput.addTextChangedListener { editor ->
|
||||
codeFilled = editor?.length == 8 || editor?.length == 9
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||
descriptionFilled && codeFilled
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
@ -10,59 +10,47 @@
|
||||
|
||||
package com.philj56.gbcc
|
||||
|
||||
import android.animation.Animator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.*
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.transition.*
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philj56.gbcc.animations.CircularReveal
|
||||
import com.philj56.gbcc.databinding.ActivityMainBinding
|
||||
import com.philj56.gbcc.fileList.FileAdapter
|
||||
import com.philj56.gbcc.fileList.PathAdapter
|
||||
import com.philj56.gbcc.databinding.DialogDirectoryActionsBinding
|
||||
import com.philj56.gbcc.databinding.DialogRomActionsBinding
|
||||
import com.philj56.gbcc.main.*
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipException
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.math.max
|
||||
|
||||
private const val BACK_DELAY: Int = 2000
|
||||
private const val SAVE_DIR: String = "saves"
|
||||
private const val IMPORTED_SAVE_SUBDIR: String = "imported"
|
||||
const val IMPORTED_SAVE_SUBDIR: String = "imported"
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
|
||||
enum class SelectionMode {
|
||||
private enum class SelectionMode {
|
||||
NORMAL, MOVE, DELETE, SELECT
|
||||
}
|
||||
|
||||
@ -373,54 +361,56 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun showDirectoryActionsDialog(file: File) {
|
||||
val binding = DialogDirectoryActionsBinding.inflate(layoutInflater)
|
||||
val dialog = MaterialAlertDialogBuilder(this)
|
||||
.setView(R.layout.dialog_directory_actions)
|
||||
.setView(binding.root)
|
||||
.setOnCancelListener { clearSelection() }
|
||||
.show()
|
||||
|
||||
dialog.findViewById<Button>(R.id.buttonSelectItems)?.setOnClickListener {
|
||||
binding.buttonSelectItems.setOnClickListener {
|
||||
beginSelection()
|
||||
dialog.dismiss()
|
||||
}
|
||||
dialog.findViewById<Button>(R.id.buttonRenameDirectory)?.setOnClickListener {
|
||||
binding.buttonRenameDirectory.setOnClickListener {
|
||||
showRenameDialog(file)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRomActionsDialog(file: File) {
|
||||
val binding = DialogRomActionsBinding.inflate(layoutInflater)
|
||||
val dialog = MaterialAlertDialogBuilder(this)
|
||||
.setView(R.layout.dialog_rom_actions)
|
||||
.setView(binding.root)
|
||||
.setOnCancelListener { clearSelection() }
|
||||
.show()
|
||||
|
||||
dialog.findViewById<Button>(R.id.buttonMultiplayer)?.setOnClickListener {
|
||||
binding.buttonMultiplayer.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setView(R.layout.dialog_multiplayer_searching)
|
||||
.setOnDismissListener { clearSelection() }
|
||||
.show()
|
||||
dialog.dismiss()
|
||||
}
|
||||
dialog.findViewById<Button>(R.id.buttonSelectItems)?.setOnClickListener {
|
||||
binding.buttonSelectItems.setOnClickListener {
|
||||
beginSelection()
|
||||
dialog.dismiss()
|
||||
}
|
||||
dialog.findViewById<Button>(R.id.buttonRenameRom)?.setOnClickListener {
|
||||
binding.buttonRenameRom.setOnClickListener {
|
||||
showRenameDialog(file)
|
||||
dialog.dismiss()
|
||||
}
|
||||
dialog.findViewById<Button>(R.id.buttonDeleteSave)?.setOnClickListener {
|
||||
binding.buttonDeleteSave.setOnClickListener {
|
||||
showSaveDeleteDialog()
|
||||
dialog.dismiss()
|
||||
}
|
||||
dialog.findViewById<Button>(R.id.buttonEditCheats)?.setOnClickListener {
|
||||
binding.buttonEditCheats.setOnClickListener {
|
||||
val intent = Intent(this, CheatActivity::class.java).apply {
|
||||
putExtra("file", file.name)
|
||||
}
|
||||
startActivity(intent)
|
||||
dialog.dismiss()
|
||||
}
|
||||
dialog.findViewById<Button>(R.id.buttonEditConfig)?.setOnClickListener {
|
||||
binding.buttonEditConfig.setOnClickListener {
|
||||
val intent = Intent(this, RomConfigActivity::class.java).apply {
|
||||
putExtra("file", file.name)
|
||||
}
|
||||
@ -745,182 +735,3 @@ class MainActivity : BaseActivity() {
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ImportOverwriteAdapter(
|
||||
context: Context,
|
||||
resource: Int,
|
||||
textViewResourceId: Int,
|
||||
private val objects: List<File>
|
||||
) : ArrayAdapter<File>(context, resource, textViewResourceId, objects) {
|
||||
|
||||
val selected = HashSet<File>()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view: View = super.getView(position, convertView, parent)
|
||||
val textView: CheckedTextView = view.findViewById(R.id.importOverwriteText)
|
||||
val file: File = objects[position]
|
||||
|
||||
textView.isChecked = file in selected
|
||||
|
||||
textView.text = file.nameWithoutExtension
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
class EditTextDialogFragment(private val title: Int, private val initialText: String, private val onConfirm: (String) -> Unit) : DialogFragment() {
|
||||
private var onDismissListener: (() -> Unit)? = null
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let {
|
||||
val textView = it.layoutInflater.inflate(R.layout.dialog_create_folder, null, false)
|
||||
val input = textView?.findViewById<EditText>(R.id.createFolderInput)
|
||||
input?.setText(initialText)
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(it)
|
||||
builder.setTitle(title)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
onConfirm(input?.text.toString())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setView(textView)
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
}
|
||||
|
||||
input?.addTextChangedListener { editor ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||
editor?.isNotBlank() ?: false
|
||||
}
|
||||
input?.setOnFocusChangeListener { v, hasFocus ->
|
||||
v.postDelayed(50) {
|
||||
if (hasFocus) {
|
||||
val imm =
|
||||
context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(v, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
input?.requestFocus()
|
||||
|
||||
return dialog
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
onDismissListener?.invoke()
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
fun setOnDismissListener(listener: () -> Unit) {
|
||||
onDismissListener = listener
|
||||
}
|
||||
}
|
||||
|
||||
class ImportOverwriteDialogFragment(private val files: ArrayList<File>) : DialogFragment() {
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let {
|
||||
val adapter = ImportOverwriteAdapter(requireContext(), R.layout.entry_import_overwrite, R.id.importOverwriteText, files)
|
||||
adapter.selected.addAll(files)
|
||||
val view = it.layoutInflater.inflate(R.layout.dialog_import_overwrite, null, false)
|
||||
val listView = view.findViewById<ListView>(R.id.listView)
|
||||
listView.adapter = adapter
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
val file = listView.adapter.getItem(position) as File
|
||||
val item = listView.getChildAt(position - listView.firstVisiblePosition)
|
||||
if (file in adapter.selected) {
|
||||
adapter.selected.remove(file)
|
||||
} else {
|
||||
adapter.selected.add(file)
|
||||
}
|
||||
item.findViewById<CheckedTextView>(R.id.importOverwriteText).isChecked = file in adapter.selected
|
||||
}
|
||||
|
||||
val saveDir = requireContext().filesDir.resolve("saves")
|
||||
fun deleteFiles() {
|
||||
saveDir.resolve(IMPORTED_SAVE_SUBDIR).deleteRecursively()
|
||||
}
|
||||
val builder = MaterialAlertDialogBuilder(it)
|
||||
builder.setTitle(resources.getString(R.string.overwrite_confirmation))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
Thread {
|
||||
adapter.selected.forEach { file ->
|
||||
val dest = saveDir.resolve(file.name)
|
||||
dest.delete()
|
||||
file.renameTo(dest)
|
||||
}
|
||||
activity?.runOnUiThread {
|
||||
Toast.makeText(
|
||||
context,
|
||||
resources.getQuantityString(
|
||||
R.plurals.message_imported,
|
||||
adapter.selected.size,
|
||||
adapter.selected.size
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
deleteFiles()
|
||||
}.start()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> deleteFiles() }
|
||||
.setView(view)
|
||||
.setOnDismissListener { deleteFiles() }
|
||||
return builder.create()
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
}
|
||||
|
||||
class CreateSaveExportZip : ActivityResultContracts.CreateDocument() {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
override fun createIntent(context: Context, input: String): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
intent.apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
}
|
||||
val date = SimpleDateFormat("yyyyMMdd").format(Date())
|
||||
intent.putExtra(Intent.EXTRA_TITLE, "gbcc_saves_$date.zip")
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
||||
class CircularReveal : Visibility() {
|
||||
override fun onAppear(
|
||||
sceneRoot: ViewGroup,
|
||||
view: View,
|
||||
startValues: TransitionValues,
|
||||
endValues: TransitionValues
|
||||
): Animator {
|
||||
val animator = ViewAnimationUtils.createCircularReveal(
|
||||
view,
|
||||
view.width / 2,
|
||||
view.height / 2,
|
||||
0.0f,
|
||||
max(view.width, view.height).toFloat()
|
||||
)
|
||||
view.alpha = 0.0f
|
||||
animator.addListener(
|
||||
onStart = { view.alpha = 1.0f }
|
||||
)
|
||||
return animator
|
||||
}
|
||||
|
||||
override fun onDisappear(
|
||||
sceneRoot: ViewGroup,
|
||||
view: View,
|
||||
startValues: TransitionValues,
|
||||
endValues: TransitionValues
|
||||
): Animator {
|
||||
return ViewAnimationUtils.createCircularReveal(
|
||||
view,
|
||||
view.width / 2,
|
||||
view.height / 2,
|
||||
max(view.width, view.height).toFloat(),
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,17 @@
|
||||
package com.philj56.gbcc
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.widget.Button
|
||||
import android.widget.RadioGroup
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton
|
||||
import com.philj56.gbcc.databinding.ActivityRemapControllerBinding
|
||||
import com.philj56.gbcc.remap.RemapButtonDialogFragment
|
||||
import kotlin.math.abs
|
||||
|
||||
class RemapActivity : BaseActivity() {
|
||||
@ -194,46 +191,3 @@ class RemapActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
class RemapButtonDialogFragment(private val button: Button, private val key: String, private val analogue: Boolean) :
|
||||
DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val mapping = prefs.getString(key, "unmapped")
|
||||
val buttonNames: Array<String>
|
||||
val buttonValues: Array<String>
|
||||
if (analogue) {
|
||||
buttonNames = resources.getStringArray(R.array.button_map_analogue_names_array)
|
||||
buttonValues = resources.getStringArray(R.array.button_map_analogue_values_array)
|
||||
} else {
|
||||
buttonNames = resources.getStringArray(R.array.button_map_names_array)
|
||||
buttonValues = resources.getStringArray(R.array.button_map_values_array)
|
||||
}
|
||||
|
||||
val layout = requireActivity().layoutInflater.inflate(R.layout.dialog_remap_button, null, false)
|
||||
val radioGroup = layout.findViewById<RadioGroup>(R.id.remapDialogRadioGroup)
|
||||
buttonNames.forEachIndexed { index, name ->
|
||||
val radio = MaterialRadioButton(radioGroup.context)
|
||||
radio.text = name
|
||||
radio.id = index
|
||||
radioGroup.addView(radio)
|
||||
|
||||
if (mapping == buttonValues[index]) {
|
||||
radioGroup.check(index)
|
||||
}
|
||||
}
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(requireActivity())
|
||||
builder.setTitle(R.string.select_mapping)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
prefs.edit {
|
||||
putString(key, buttonValues[radioGroup.checkedRadioButtonId])
|
||||
apply()
|
||||
}
|
||||
button.text = buttonNames[radioGroup.checkedRadioButtonId]
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setView(layout)
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,6 @@
|
||||
package com.philj56.gbcc
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.*
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philj56.gbcc.databinding.ActivityRomConfigBinding
|
||||
import java.io.File
|
||||
|
||||
@ -41,36 +39,3 @@ class RomConfigActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
class RomConfigFragment : PreferenceFragmentCompat() {
|
||||
private lateinit var dataStore: IniDataStore
|
||||
private var save = true
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
dataStore = IniDataStore((requireActivity() as RomConfigActivity).configFile)
|
||||
preferenceManager.preferenceDataStore = dataStore
|
||||
setPreferencesFromResource(R.xml.rom_config, rootKey)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (save) {
|
||||
dataStore.saveFile()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
if (preference.key == "delete") {
|
||||
val context = requireContext()
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_config_confirmation)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
save = false
|
||||
(activity as RomConfigActivity).clearConfig()
|
||||
}.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
return true
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,21 +10,17 @@
|
||||
|
||||
package com.philj56.gbcc
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.add
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.preference.*
|
||||
import com.philj56.gbcc.databinding.ActivitySettingsBinding
|
||||
import com.philj56.gbcc.preference.MaterialTurboPreferenceDialogFragmentCompat
|
||||
import com.philj56.gbcc.preference.MaterialListPreferenceDialogFragmentCompat
|
||||
import com.philj56.gbcc.preference.SliderPreference
|
||||
import com.philj56.gbcc.materialPreferences.MaterialListPreferenceDialogFragmentCompat
|
||||
import com.philj56.gbcc.materialPreferences.MaterialTurboPreferenceDialogFragmentCompat
|
||||
import com.philj56.gbcc.settings.SummaryListPreference
|
||||
import com.philj56.gbcc.settings.TurboPreference
|
||||
|
||||
abstract class BaseSettingsActivity : BaseActivity() {
|
||||
abstract val preferenceResource : Int
|
||||
@ -120,72 +116,3 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
class SummaryListPreference(context: Context, attrs: AttributeSet) :
|
||||
ListPreference(context, attrs) {
|
||||
init {
|
||||
summaryProvider = SimpleSummaryProvider.getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
class TurboPreference(context: Context, attrs: AttributeSet) :
|
||||
EditTextPreference(context, attrs) {
|
||||
init {
|
||||
summaryProvider = TurboSummaryProvider
|
||||
|
||||
// This is currently unneeded, as it's hardcoded into
|
||||
// MaterialTurboPreferenceDialogFragmentCompat.
|
||||
// If that ever gets changed back to a less hacky solution, we'll want this again.
|
||||
// setOnBindEditTextListener { editText ->
|
||||
// editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||
// editText.selectAll()
|
||||
// }
|
||||
}
|
||||
|
||||
override fun setText(text: String?) {
|
||||
if (text?.toFloatOrNull() == null) {
|
||||
super.setText("0")
|
||||
} else {
|
||||
super.setText(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TurboSummaryProvider : Preference.SummaryProvider<EditTextPreference> {
|
||||
override fun provideSummary(preference: EditTextPreference): CharSequence {
|
||||
val text = preference.text?.ifEmpty {
|
||||
"0"
|
||||
}
|
||||
return when (text?.toFloat()) {
|
||||
0F -> "0 (Unlimited)"
|
||||
else -> "$text×"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnitSeekbarPreference(context: Context, attrs: AttributeSet) :
|
||||
SliderPreference(context, attrs) {
|
||||
|
||||
lateinit var textView: TextView
|
||||
val watcher = UnitSeekbarPreferenceWatcher()
|
||||
|
||||
override fun onBindViewHolder(view: PreferenceViewHolder) {
|
||||
textView = view.findViewById(R.id.seekbar_value) as TextView
|
||||
textView.removeTextChangedListener(watcher)
|
||||
textView.addTextChangedListener(watcher)
|
||||
super.onBindViewHolder(view)
|
||||
}
|
||||
|
||||
inner class UnitSeekbarPreferenceWatcher : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
textView.removeTextChangedListener(watcher)
|
||||
s?.insert(s.length, context.resources.getString(R.string.settings_bluetooth_latency_units))
|
||||
textView.addTextChangedListener(watcher)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
package com.philj56.gbcc.animations
|
||||
|
||||
import android.animation.Animator
|
||||
import android.view.View
|
||||
import android.view.ViewAnimationUtils
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.transition.TransitionValues
|
||||
import androidx.transition.Visibility
|
||||
import kotlin.math.max
|
||||
|
||||
class CircularReveal : Visibility() {
|
||||
override fun onAppear(
|
||||
sceneRoot: ViewGroup,
|
||||
view: View,
|
||||
startValues: TransitionValues,
|
||||
endValues: TransitionValues
|
||||
): Animator {
|
||||
val animator = ViewAnimationUtils.createCircularReveal(
|
||||
view,
|
||||
view.width / 2,
|
||||
view.height / 2,
|
||||
0.0f,
|
||||
max(view.width, view.height).toFloat()
|
||||
)
|
||||
view.alpha = 0.0f
|
||||
animator.addListener(
|
||||
onStart = { view.alpha = 1.0f }
|
||||
)
|
||||
return animator
|
||||
}
|
||||
|
||||
override fun onDisappear(
|
||||
sceneRoot: ViewGroup,
|
||||
view: View,
|
||||
startValues: TransitionValues,
|
||||
endValues: TransitionValues
|
||||
): Animator {
|
||||
return ViewAnimationUtils.createCircularReveal(
|
||||
view,
|
||||
view.width / 2,
|
||||
view.height / 2,
|
||||
max(view.width, view.height).toFloat(),
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
}
|
50
app/src/main/java/com/philj56/gbcc/arrange/ResizableImage.kt
Normal file
50
app/src/main/java/com/philj56/gbcc/arrange/ResizableImage.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package com.philj56.gbcc.arrange
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
|
||||
class ResizableImage : AppCompatImageView {
|
||||
private var floating: Boolean = false
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
private fun addMotionListener() {
|
||||
setOnTouchListener(OnTouchListener { view, motionEvent ->
|
||||
|
||||
if (!floating) {
|
||||
return@OnTouchListener view.performClick()
|
||||
}
|
||||
|
||||
when (motionEvent.actionMasked) {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
floating = false
|
||||
}
|
||||
}
|
||||
|
||||
view.x = motionEvent.rawX - view.width / 2
|
||||
view.y = motionEvent.rawY - view.height / 2
|
||||
|
||||
return@OnTouchListener true
|
||||
})
|
||||
}
|
||||
|
||||
private fun addLongClickListener() {
|
||||
setOnLongClickListener(OnLongClickListener {
|
||||
floating = true
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
return@OnLongClickListener false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package com.philj56.gbcc.arrange
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.widget.FrameLayout
|
||||
|
||||
class ResizableLayout : FrameLayout {
|
||||
private var floating: Boolean = false
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
||||
addMotionListener()
|
||||
addLongClickListener()
|
||||
}
|
||||
|
||||
private fun addMotionListener() {
|
||||
setOnTouchListener(OnTouchListener { view, motionEvent ->
|
||||
|
||||
if (!floating) {
|
||||
return@OnTouchListener view.performClick()
|
||||
}
|
||||
|
||||
when (motionEvent.actionMasked) {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
floating = false
|
||||
}
|
||||
}
|
||||
|
||||
view.x = motionEvent.rawX - view.width / 2
|
||||
view.y = motionEvent.rawY - view.height / 2
|
||||
|
||||
return@OnTouchListener true
|
||||
})
|
||||
}
|
||||
|
||||
private fun addLongClickListener() {
|
||||
setOnLongClickListener(OnLongClickListener {
|
||||
floating = true
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
return@OnLongClickListener false
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.philj56.gbcc.arrange
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import kotlin.math.min
|
||||
|
||||
class ScreenPlaceholder : AppCompatImageView {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
setMeasuredDimension(160, 144)
|
||||
layoutParams = ViewGroup.LayoutParams(160, 144)
|
||||
}
|
||||
|
||||
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
||||
setMeasuredDimension(160, 144)
|
||||
layoutParams = ViewGroup.LayoutParams(160, 144)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
|
||||
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
|
||||
|
||||
var width = 0
|
||||
var height = 0
|
||||
val scaleX = widthSize / 160
|
||||
val scaleY = heightSize / 144
|
||||
|
||||
when(widthMode) {
|
||||
MeasureSpec.EXACTLY -> width = widthSize
|
||||
MeasureSpec.AT_MOST -> Unit
|
||||
MeasureSpec.UNSPECIFIED -> width = 160
|
||||
}
|
||||
|
||||
when(heightMode) {
|
||||
MeasureSpec.EXACTLY -> height = heightSize
|
||||
MeasureSpec.AT_MOST -> Unit
|
||||
MeasureSpec.UNSPECIFIED -> height = 144
|
||||
}
|
||||
|
||||
if (width == 0 && height == 0) {
|
||||
val scale = min(scaleX, scaleY)
|
||||
width = 160 * scale
|
||||
height = 144 * scale
|
||||
} else if (width == 0) {
|
||||
width = (height * 160) / 144
|
||||
} else if (height == 0) {
|
||||
height = (width * 144) / 160
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height)
|
||||
}
|
||||
}
|
3
app/src/main/java/com/philj56/gbcc/cheat/Cheat.kt
Normal file
3
app/src/main/java/com/philj56/gbcc/cheat/Cheat.kt
Normal file
@ -0,0 +1,3 @@
|
||||
package com.philj56.gbcc.cheat
|
||||
|
||||
data class Cheat(var description: String, var code: String, var active: Boolean)
|
38
app/src/main/java/com/philj56/gbcc/cheat/CheatAdapter.kt
Normal file
38
app/src/main/java/com/philj56/gbcc/cheat/CheatAdapter.kt
Normal file
@ -0,0 +1,38 @@
|
||||
package com.philj56.gbcc.cheat
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import com.philj56.gbcc.R
|
||||
|
||||
class CheatAdapter(
|
||||
context: Context,
|
||||
resource: Int,
|
||||
textViewResourceId: Int,
|
||||
private val objects: List<Cheat>
|
||||
) : ArrayAdapter<Cheat>(context, resource, textViewResourceId, objects) {
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = super.getView(position, convertView, parent)
|
||||
val textDescription = view.findViewById<TextView>(R.id.cheatDescription)
|
||||
val textCode = view.findViewById<TextView>(R.id.cheatCode)
|
||||
val switchActive = view.findViewById<SwitchCompat>(R.id.cheatActive)
|
||||
val (description, code, active) = objects[position]
|
||||
|
||||
textDescription.text = description
|
||||
textCode.text = formatCode(code)
|
||||
switchActive.isChecked = active
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun formatCode(code: String): String {
|
||||
if (code.length == 9) {
|
||||
return code.substring(0, 3) + "-" + code.substring(3, 6) + "-" + code.substring(6, 9)
|
||||
}
|
||||
return code
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.philj56.gbcc.cheat
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.text.InputFilter
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philj56.gbcc.CheatActivity
|
||||
import com.philj56.gbcc.R
|
||||
import com.philj56.gbcc.databinding.DialogEditCheatBinding
|
||||
|
||||
class CheatDialogFragment(private val index: Int) : DialogFragment() {
|
||||
private var descriptionFilled = false
|
||||
private var codeFilled = false
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val activity = requireActivity() as CheatActivity
|
||||
val view = activity.layoutInflater.inflate(R.layout.dialog_edit_cheat, null, false)
|
||||
val binding = DialogEditCheatBinding.bind(view)
|
||||
val cheatExists = (index >= 0)
|
||||
|
||||
val title: Int
|
||||
if (!cheatExists) {
|
||||
title = R.string.cheat_add_description
|
||||
} else {
|
||||
title = R.string.cheat_edit_description
|
||||
val cheat = activity.getCheat(index)
|
||||
binding.cheatDescriptionInput.setText(cheat.description)
|
||||
binding.cheatCodeInput.setText(cheat.code)
|
||||
descriptionFilled = true
|
||||
codeFilled = true
|
||||
}
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(title)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
activity.addCheat(
|
||||
Cheat(
|
||||
binding.cheatDescriptionInput.text.toString().trim(),
|
||||
binding.cheatCodeInput.text.toString().uppercase(),
|
||||
true
|
||||
),
|
||||
index
|
||||
)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setView(view)
|
||||
|
||||
if (cheatExists) {
|
||||
builder.setNeutralButton(R.string.delete) { _, _ -> activity.removeCheat(index) }
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
}
|
||||
|
||||
binding.cheatDescriptionInput.filters = arrayOf(InputFilter { source, start, end, _, _, _ ->
|
||||
if (end <= start) {
|
||||
// Deletion, always accept
|
||||
return@InputFilter null
|
||||
}
|
||||
return@InputFilter source.subSequence(start, end).filterNot { it == '#' || it == ';' }
|
||||
})
|
||||
|
||||
binding.cheatDescriptionInput.addTextChangedListener { editor ->
|
||||
descriptionFilled = editor?.isNotBlank() ?: false
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||
descriptionFilled && codeFilled
|
||||
}
|
||||
|
||||
binding.cheatCodeInput.addTextChangedListener { editor ->
|
||||
codeFilled = editor?.length == 8 || editor?.length == 9
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||
descriptionFilled && codeFilled
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.philj56.gbcc.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class CreateSaveExportZip : ActivityResultContracts.CreateDocument() {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
override fun createIntent(context: Context, input: String): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
intent.apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
}
|
||||
val date = SimpleDateFormat("yyyyMMdd").format(Date())
|
||||
intent.putExtra(Intent.EXTRA_TITLE, "gbcc_saves_$date.zip")
|
||||
return intent
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package com.philj56.gbcc.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philj56.gbcc.R
|
||||
|
||||
class EditTextDialogFragment(
|
||||
private val title: Int,
|
||||
private val initialText: String,
|
||||
private val onConfirm: (String) -> Unit
|
||||
) : DialogFragment() {
|
||||
private var onDismissListener: (() -> Unit)? = null
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let {
|
||||
val textView = it.layoutInflater.inflate(R.layout.dialog_create_folder, null, false)
|
||||
val input = textView?.findViewById<EditText>(R.id.createFolderInput)
|
||||
input?.setText(initialText)
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(it)
|
||||
builder.setTitle(title)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
onConfirm(input?.text.toString())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setView(textView)
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
}
|
||||
|
||||
input?.addTextChangedListener { editor ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||
editor?.isNotBlank() ?: false
|
||||
}
|
||||
input?.setOnFocusChangeListener { v, hasFocus ->
|
||||
v.postDelayed(50) {
|
||||
if (hasFocus) {
|
||||
val imm =
|
||||
context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(v, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
input?.requestFocus()
|
||||
|
||||
return dialog
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
onDismissListener?.invoke()
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
fun setOnDismissListener(listener: () -> Unit) {
|
||||
onDismissListener = listener
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.philj56.gbcc.fileList
|
||||
package com.philj56.gbcc.main
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -11,7 +11,10 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.philj56.gbcc.R
|
||||
import java.io.File
|
||||
|
||||
class FileAdapter(private val onClick: (File, View) -> Unit, private val onLongClick: (File, View) -> Unit) :
|
||||
class FileAdapter(
|
||||
private val onClick: (File, View) -> Unit,
|
||||
private val onLongClick: (File, View) -> Unit
|
||||
) :
|
||||
ListAdapter<File, FileAdapter.FileViewHolder>(FileDiffCallback) {
|
||||
|
||||
val selected = HashSet<File>()
|
@ -0,0 +1,31 @@
|
||||
package com.philj56.gbcc.main
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.CheckedTextView
|
||||
import com.philj56.gbcc.R
|
||||
import java.io.File
|
||||
import java.util.HashSet
|
||||
|
||||
class ImportOverwriteAdapter(
|
||||
context: Context,
|
||||
resource: Int,
|
||||
textViewResourceId: Int,
|
||||
private val objects: List<File>
|
||||
) : ArrayAdapter<File>(context, resource, textViewResourceId, objects) {
|
||||
|
||||
val selected = HashSet<File>()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view: View = super.getView(position, convertView, parent)
|
||||
val textView: CheckedTextView = view.findViewById(R.id.importOverwriteText)
|
||||
val file: File = objects[position]
|
||||
|
||||
textView.isChecked = file in selected
|
||||
|
||||
textView.text = file.nameWithoutExtension
|
||||
return view
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.philj56.gbcc.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.widget.CheckedTextView
|
||||
import android.widget.ListView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philj56.gbcc.IMPORTED_SAVE_SUBDIR
|
||||
import com.philj56.gbcc.R
|
||||
import java.io.File
|
||||
|
||||
class ImportOverwriteDialogFragment(private val files: ArrayList<File>) : DialogFragment() {
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let {
|
||||
val adapter = ImportOverwriteAdapter(
|
||||
requireContext(),
|
||||
R.layout.entry_import_overwrite,
|
||||
R.id.importOverwriteText,
|
||||
files
|
||||
)
|
||||
adapter.selected.addAll(files)
|
||||
val view = it.layoutInflater.inflate(R.layout.dialog_import_overwrite, null, false)
|
||||
val listView = view.findViewById<ListView>(R.id.listView)
|
||||
listView.adapter = adapter
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
val file = listView.adapter.getItem(position) as File
|
||||
val item = listView.getChildAt(position - listView.firstVisiblePosition)
|
||||
if (file in adapter.selected) {
|
||||
adapter.selected.remove(file)
|
||||
} else {
|
||||
adapter.selected.add(file)
|
||||
}
|
||||
item.findViewById<CheckedTextView>(R.id.importOverwriteText).isChecked = file in adapter.selected
|
||||
}
|
||||
|
||||
val saveDir = requireContext().filesDir.resolve("saves")
|
||||
fun deleteFiles() {
|
||||
saveDir.resolve(IMPORTED_SAVE_SUBDIR).deleteRecursively()
|
||||
}
|
||||
val builder = MaterialAlertDialogBuilder(it)
|
||||
builder.setTitle(resources.getString(R.string.overwrite_confirmation))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
Thread {
|
||||
adapter.selected.forEach { file ->
|
||||
val dest = saveDir.resolve(file.name)
|
||||
dest.delete()
|
||||
file.renameTo(dest)
|
||||
}
|
||||
activity?.runOnUiThread {
|
||||
Toast.makeText(
|
||||
context,
|
||||
resources.getQuantityString(
|
||||
R.plurals.message_imported,
|
||||
adapter.selected.size,
|
||||
adapter.selected.size
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
deleteFiles()
|
||||
}.start()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> deleteFiles() }
|
||||
.setView(view)
|
||||
.setOnDismissListener { deleteFiles() }
|
||||
return builder.create()
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.philj56.gbcc.fileList
|
||||
package com.philj56.gbcc.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
@ -19,7 +19,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.philj56.gbcc.preference;
|
||||
package com.philj56.gbcc.materialPreferences;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
@ -20,7 +20,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.philj56.gbcc.preference;
|
||||
package com.philj56.gbcc.materialPreferences;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import android.app.Dialog;
|
@ -20,7 +20,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.philj56.gbcc.preference;
|
||||
package com.philj56.gbcc.materialPreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
@ -57,7 +57,7 @@ import com.philj56.gbcc.R;
|
||||
* max})
|
||||
* can be set directly on the preference widget layout.
|
||||
*/
|
||||
public class SliderPreference extends Preference {
|
||||
public class MaterialSeekbarPreference extends Preference {
|
||||
private static final String TAG = "SliderPreference";
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
int mSeekBarValue;
|
||||
@ -140,7 +140,7 @@ public class SliderPreference extends Preference {
|
||||
return mSeekBar.onKeyDown(keyCode, event);
|
||||
}
|
||||
};
|
||||
public SliderPreference(
|
||||
public MaterialSeekbarPreference(
|
||||
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
TypedArray a = context.obtainStyledAttributes(
|
||||
@ -156,13 +156,13 @@ public class SliderPreference extends Preference {
|
||||
mUpdatesContinuously = false; //a.getBoolean(R.styleable.SeekBarPreference_updatesContinuously, false);
|
||||
a.recycle();
|
||||
}
|
||||
public SliderPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public MaterialSeekbarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
public SliderPreference(Context context, AttributeSet attrs) {
|
||||
public MaterialSeekbarPreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.seekBarPreferenceStyle);
|
||||
}
|
||||
public SliderPreference(Context context) {
|
||||
public MaterialSeekbarPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@Override
|
@ -21,7 +21,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.philj56.gbcc.preference;
|
||||
package com.philj56.gbcc.materialPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
@ -18,7 +18,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.philj56.gbcc.preference;
|
||||
package com.philj56.gbcc.materialPreferences;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
@ -0,0 +1,56 @@
|
||||
package com.philj56.gbcc.remap
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.RadioGroup
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton
|
||||
import com.philj56.gbcc.R
|
||||
|
||||
class RemapButtonDialogFragment(private val button: Button, private val key: String, private val analogue: Boolean) :
|
||||
DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val mapping = prefs.getString(key, "unmapped")
|
||||
val buttonNames: Array<String>
|
||||
val buttonValues: Array<String>
|
||||
if (analogue) {
|
||||
buttonNames = resources.getStringArray(R.array.button_map_analogue_names_array)
|
||||
buttonValues = resources.getStringArray(R.array.button_map_analogue_values_array)
|
||||
} else {
|
||||
buttonNames = resources.getStringArray(R.array.button_map_names_array)
|
||||
buttonValues = resources.getStringArray(R.array.button_map_values_array)
|
||||
}
|
||||
|
||||
val layout = requireActivity().layoutInflater.inflate(R.layout.dialog_remap_button, null, false)
|
||||
val radioGroup = layout.findViewById<RadioGroup>(R.id.remapDialogRadioGroup)
|
||||
buttonNames.forEachIndexed { index, name ->
|
||||
val radio = MaterialRadioButton(radioGroup.context)
|
||||
radio.text = name
|
||||
radio.id = index
|
||||
radioGroup.addView(radio)
|
||||
|
||||
if (mapping == buttonValues[index]) {
|
||||
radioGroup.check(index)
|
||||
}
|
||||
}
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(requireActivity())
|
||||
builder.setTitle(R.string.select_mapping)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
prefs.edit {
|
||||
putString(key, buttonValues[radioGroup.checkedRadioButtonId])
|
||||
apply()
|
||||
}
|
||||
button.text = buttonNames[radioGroup.checkedRadioButtonId]
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setView(layout)
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.philj56.gbcc
|
||||
package com.philj56.gbcc.romConfig
|
||||
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import java.io.File
|
@ -0,0 +1,41 @@
|
||||
package com.philj56.gbcc.romConfig
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philj56.gbcc.R
|
||||
import com.philj56.gbcc.RomConfigActivity
|
||||
|
||||
class RomConfigFragment : PreferenceFragmentCompat() {
|
||||
private lateinit var dataStore: IniDataStore
|
||||
private var save = true
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
dataStore = IniDataStore((requireActivity() as RomConfigActivity).configFile)
|
||||
preferenceManager.preferenceDataStore = dataStore
|
||||
setPreferencesFromResource(R.xml.rom_config, rootKey)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (save) {
|
||||
dataStore.saveFile()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
if (preference.key == "delete") {
|
||||
val context = requireContext()
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_config_confirmation)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
save = false
|
||||
(activity as RomConfigActivity).clearConfig()
|
||||
}.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
return true
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.philj56.gbcc.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.ListPreference
|
||||
|
||||
class SummaryListPreference(context: Context, attrs: AttributeSet) :
|
||||
ListPreference(context, attrs) {
|
||||
init {
|
||||
summaryProvider = SimpleSummaryProvider.getInstance()
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.philj56.gbcc.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.EditTextPreference
|
||||
|
||||
class TurboPreference(context: Context, attrs: AttributeSet) :
|
||||
EditTextPreference(context, attrs) {
|
||||
init {
|
||||
summaryProvider = TurboSummaryProvider
|
||||
|
||||
// This is currently unneeded, as it's hardcoded into
|
||||
// MaterialTurboPreferenceDialogFragmentCompat.
|
||||
// If that ever gets changed back to a less hacky solution, we'll want this again.
|
||||
// setOnBindEditTextListener { editText ->
|
||||
// editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||
// editText.selectAll()
|
||||
// }
|
||||
}
|
||||
|
||||
override fun setText(text: String?) {
|
||||
if (text?.toFloatOrNull() == null) {
|
||||
super.setText("0")
|
||||
} else {
|
||||
super.setText(text)
|
||||
}
|
||||
}
|
||||
|
||||
private object TurboSummaryProvider : SummaryProvider<EditTextPreference> {
|
||||
override fun provideSummary(preference: EditTextPreference): CharSequence {
|
||||
val text = preference.text?.ifEmpty {
|
||||
"0"
|
||||
}
|
||||
return when (text?.toFloat()) {
|
||||
0F -> "0 (Unlimited)"
|
||||
else -> "$text×"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.philj56.gbcc.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import com.philj56.gbcc.R
|
||||
import com.philj56.gbcc.materialPreferences.MaterialSeekbarPreference
|
||||
|
||||
class UnitSeekbarPreference(context: Context, attrs: AttributeSet) :
|
||||
MaterialSeekbarPreference(context, attrs) {
|
||||
|
||||
lateinit var textView: TextView
|
||||
val watcher = UnitSeekbarPreferenceWatcher()
|
||||
|
||||
override fun onBindViewHolder(view: PreferenceViewHolder) {
|
||||
textView = view.findViewById(R.id.seekbar_value) as TextView
|
||||
textView.removeTextChangedListener(watcher)
|
||||
textView.addTextChangedListener(watcher)
|
||||
super.onBindViewHolder(view)
|
||||
}
|
||||
|
||||
inner class UnitSeekbarPreferenceWatcher : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
textView.removeTextChangedListener(watcher)
|
||||
s?.insert(s.length, context.resources.getString(R.string.settings_bluetooth_latency_units))
|
||||
textView.addTextChangedListener(watcher)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
and ignores any further touches. -->
|
||||
</ImageView>
|
||||
|
||||
<com.philj56.gbcc.ScreenPlaceholder
|
||||
<com.philj56.gbcc.arrange.ScreenPlaceholder
|
||||
android:id="@+id/screen"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -75,7 +75,7 @@
|
||||
app:layout_constraintEnd_toEndOf="@+id/screenBorderRight"
|
||||
app:layout_constraintStart_toStartOf="@+id/screenBorderLeft" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonA"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
@ -93,7 +93,7 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/screenBorderRight"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonB"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
@ -109,7 +109,7 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/screenBorderRight"
|
||||
app:layout_constraintTop_toBottomOf="@+id/buttonA" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonSelect"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="48dp"
|
||||
@ -128,7 +128,7 @@
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintVertical_weight="0" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonStart"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="48dp"
|
||||
@ -216,7 +216,6 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/resetSizes"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
@ -227,7 +226,6 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/resetLayout"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
|
@ -23,7 +23,7 @@
|
||||
and ignores any further touches. -->
|
||||
</ImageView>
|
||||
|
||||
<com.philj56.gbcc.ScreenPlaceholder
|
||||
<com.philj56.gbcc.arrange.ScreenPlaceholder
|
||||
android:id="@+id/screen"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -75,7 +75,7 @@
|
||||
app:layout_constraintEnd_toEndOf="@+id/screenBorderRight"
|
||||
app:layout_constraintStart_toStartOf="@+id/screenBorderLeft" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonA"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
@ -90,7 +90,7 @@
|
||||
app:layout_constraintTop_toTopOf="@+id/dpad"
|
||||
app:layout_constraintVertical_bias="0.33" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonB"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
@ -109,7 +109,7 @@
|
||||
app:layout_constraintTop_toTopOf="@+id/buttonA"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonStart"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="48dp"
|
||||
@ -125,7 +125,7 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/buttonSelect"
|
||||
app:layout_constraintTop_toTopOf="@+id/buttonSelect" />
|
||||
|
||||
<com.philj56.gbcc.ResizableImage
|
||||
<com.philj56.gbcc.arrange.ResizableImage
|
||||
android:id="@+id/buttonSelect"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="48dp"
|
||||
|
@ -38,7 +38,7 @@ the collapsing bar work properly when rotating / toggling night mode
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/romConfigFragment"
|
||||
android:name="com.philj56.gbcc.RomConfigFragment"
|
||||
android:name="com.philj56.gbcc.romConfig.RomConfigFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.philj56.gbcc.ResizableLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.philj56.gbcc.arrange.ResizableLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:contentDescription="@string/dpad_description"
|
||||
android:layout_width="match_parent"
|
||||
@ -19,4 +19,4 @@
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_button_dpad_highlight" />
|
||||
|
||||
</com.philj56.gbcc.ResizableLayout>
|
||||
</com.philj56.gbcc.arrange.ResizableLayout>
|
@ -78,7 +78,7 @@
|
||||
to the children of this container layout. Otherwise, the animated pressed state will also
|
||||
play for the thumb in the AbsSeekBar in addition to the preference's ripple background.
|
||||
The background of the SeekBar is also set to null to disable the ripple background -->
|
||||
<com.philj56.gbcc.preference.UnPressableLinearLayout
|
||||
<com.philj56.gbcc.materialPreferences.UnPressableLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
@ -120,6 +120,6 @@
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:scrollbars="none"/>
|
||||
</com.philj56.gbcc.preference.UnPressableLinearLayout>
|
||||
</com.philj56.gbcc.materialPreferences.UnPressableLinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.philj56.gbcc.ResizableLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.philj56.gbcc.arrange.ResizableLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -16,4 +16,4 @@
|
||||
android:theme="@style/TurboToggleTheme"
|
||||
app:switchMinWidth="64dp" />
|
||||
|
||||
</com.philj56.gbcc.ResizableLayout>
|
||||
</com.philj56.gbcc.arrange.ResizableLayout>
|
@ -17,7 +17,7 @@
|
||||
app:title="@string/settings_audio_low_latency"
|
||||
app:summary="@string/settings_audio_low_latency_summary" />
|
||||
|
||||
<com.philj56.gbcc.UnitSeekbarPreference
|
||||
<com.philj56.gbcc.settings.UnitSeekbarPreference
|
||||
android:layout="@layout/preference_widget_slider"
|
||||
app:dependency="audio_low_latency"
|
||||
app:min="40"
|
||||
@ -29,7 +29,7 @@
|
||||
app:summary="@string/settings_audio_latency_summary"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.UnitSeekbarPreference
|
||||
<com.philj56.gbcc.settings.UnitSeekbarPreference
|
||||
android:layout="@layout/preference_widget_slider"
|
||||
app:min="40"
|
||||
android:max="500"
|
||||
@ -40,7 +40,7 @@
|
||||
app:summary="@string/settings_bluetooth_latency_summary"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.preference.SliderPreference
|
||||
<com.philj56.gbcc.materialPreferences.MaterialSeekbarPreference
|
||||
android:layout="@layout/preference_widget_slider"
|
||||
app:min="0"
|
||||
android:max="100"
|
||||
|
@ -35,7 +35,7 @@
|
||||
app:key="haptic_key_release"
|
||||
app:title="@string/settings_haptic_key_release" />
|
||||
|
||||
<com.philj56.gbcc.TurboPreference
|
||||
<com.philj56.gbcc.settings.TurboPreference
|
||||
app:defaultValue="0"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="turbo_speed"
|
||||
|
@ -27,7 +27,7 @@
|
||||
app:title="@string/settings_turbo_dpad"
|
||||
app:summary="@string/settings_turbo_dpad_summary" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="auto"
|
||||
app:entries="@array/skin_names_array"
|
||||
app:entryValues="@array/skin_values_array"
|
||||
@ -35,7 +35,7 @@
|
||||
app:title="@string/settings_skin"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="Light"
|
||||
app:entries="@array/dmg_color_names_array"
|
||||
app:entryValues="@array/dmg_color_values_array"
|
||||
@ -43,7 +43,7 @@
|
||||
app:title="@string/settings_dmg_color"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="Teal"
|
||||
app:entries="@array/color_names_array"
|
||||
app:entryValues="@array/color_values_array"
|
||||
@ -51,7 +51,7 @@
|
||||
app:title="@string/settings_color"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="-1"
|
||||
app:entries="@array/orientation_names_array"
|
||||
app:entryValues="@array/orientation_values_array"
|
||||
|
@ -22,7 +22,7 @@
|
||||
app:title="@string/settings_vsync"
|
||||
app:summary="@string/settings_vsync_summary"/>
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="@string/settings_shader_dot_matrix"
|
||||
app:entries="@array/shader_names_array"
|
||||
app:entryValues="@array/shader_values_array"
|
||||
@ -30,7 +30,7 @@
|
||||
app:title="@string/settings_shader_dmg"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="@string/settings_shader_subpixel"
|
||||
app:entries="@array/shader_names_array"
|
||||
app:entryValues="@array/shader_values_array"
|
||||
@ -38,7 +38,7 @@
|
||||
app:title="@string/settings_shader_gbc"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="Default"
|
||||
app:entries="@array/dmg_palettes_array"
|
||||
app:entryValues="@array/dmg_palettes_array"
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="Teal"
|
||||
app:entries="@array/color_names_array"
|
||||
app:entryValues="@array/color_values_array"
|
||||
@ -10,7 +10,7 @@
|
||||
app:title="@string/settings_theme"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="auto"
|
||||
app:entries="@array/night_mode_names_array"
|
||||
app:entryValues="@array/night_mode_values_array"
|
||||
@ -25,7 +25,7 @@
|
||||
app:summary="@string/settings_oled_night_mode_summary"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="back"
|
||||
app:entries="@array/camera_names_array"
|
||||
app:entryValues="@array/camera_values_array"
|
||||
@ -33,7 +33,7 @@
|
||||
app:title="@string/settings_camera"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.preference.SliderPreference
|
||||
<com.philj56.gbcc.materialPreferences.MaterialSeekbarPreference
|
||||
android:layout="@layout/preference_widget_slider"
|
||||
app:defaultValue="255"
|
||||
app:min="1"
|
||||
|
@ -32,13 +32,13 @@
|
||||
app:key="vsync"
|
||||
app:title="@string/settings_vsync" />
|
||||
|
||||
<com.philj56.gbcc.TurboPreference
|
||||
<com.philj56.gbcc.settings.TurboPreference
|
||||
app:defaultValue="0"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="turbo"
|
||||
app:title="@string/settings_turbo_speed" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="Default"
|
||||
app:entries="@array/dmg_palettes_array"
|
||||
app:entryValues="@array/dmg_palettes_array"
|
||||
@ -46,7 +46,7 @@
|
||||
app:title="@string/rom_config_palette"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<com.philj56.gbcc.SummaryListPreference
|
||||
<com.philj56.gbcc.settings.SummaryListPreference
|
||||
app:defaultValue="@string/settings_shader_subpixel"
|
||||
app:entries="@array/shader_names_array"
|
||||
app:entryValues="@array/shader_values_array"
|
||||
|
Loading…
x
Reference in New Issue
Block a user