summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java/org
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-03-15 15:33:53 -0400
committerCharles Lombardo <clombardo169@gmail.com>2023-03-15 17:35:48 -0400
commit9020b6aeb929198f4aa3b11ab6c83d8d96753ad1 (patch)
treeb9706d62e158307f17aa3b21a975d1e13a279986 /Source/Android/app/src/main/java/org
parent673c8d9cb2696ce273b6e1977b1e9c288eca932c (diff)
Android: Convert SettingsAdapter to Kotlin
Diffstat (limited to 'Source/Android/app/src/main/java/org')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java703
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt590
2 files changed, 590 insertions, 703 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java
deleted file mode 100644
index e062dd71b2..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java
+++ /dev/null
@@ -1,703 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.ui;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.provider.DocumentsContract;
-import android.text.format.DateFormat;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.android.material.color.MaterialColors;
-import com.google.android.material.datepicker.CalendarConstraints;
-import com.google.android.material.datepicker.MaterialDatePicker;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.elevation.ElevationOverlayProvider;
-import com.google.android.material.slider.Slider;
-import com.google.android.material.textfield.TextInputEditText;
-import com.google.android.material.timepicker.MaterialTimePicker;
-import com.google.android.material.timepicker.TimeFormat;
-
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding;
-import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding;
-import org.dolphinemu.dolphinemu.databinding.DialogSliderBinding;
-import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding;
-import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding;
-import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
-import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding;
-import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding;
-import org.dolphinemu.dolphinemu.features.input.ui.AdvancedMappingDialog;
-import org.dolphinemu.dolphinemu.features.input.ui.MotionAlertDialog;
-import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
-import org.dolphinemu.dolphinemu.features.input.ui.viewholder.InputMappingControlSettingViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.model.Settings;
-import org.dolphinemu.dolphinemu.features.settings.model.view.DateTimeChoiceSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker;
-import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.InputStringSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SubmenuSetting;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.DateTimeSettingViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.FilePickerViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderHyperLinkViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputStringSettingViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RunRunnableViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SliderViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SubmenuViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SwitchSettingViewHolder;
-import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
-import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
-import org.dolphinemu.dolphinemu.utils.Log;
-import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.TimeZone;
-
-public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolder>
- implements DialogInterface.OnClickListener, Slider.OnChangeListener
-{
- private final SettingsFragmentView mView;
- private final Context mContext;
- private ArrayList<SettingsItem> mSettings;
-
- private SettingsItem mClickedItem;
- private int mClickedPosition;
- private int mSeekbarProgress;
-
- private AlertDialog mDialog;
- private TextView mTextSliderValue;
-
- public SettingsAdapter(SettingsFragmentView view, Context context)
- {
- mView = view;
- mContext = context;
- mClickedPosition = -1;
- }
-
- @NonNull
- @Override
- public SettingViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
- {
- LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- switch (viewType)
- {
- case SettingsItem.TYPE_HEADER:
- return new HeaderViewHolder(ListItemHeaderBinding.inflate(inflater), this);
-
- case SettingsItem.TYPE_SWITCH:
- return new SwitchSettingViewHolder(ListItemSettingSwitchBinding.inflate(inflater),
- this);
-
- case SettingsItem.TYPE_STRING_SINGLE_CHOICE:
- case SettingsItem.TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS:
- case SettingsItem.TYPE_SINGLE_CHOICE:
- return new SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this);
-
- case SettingsItem.TYPE_SLIDER:
- return new SliderViewHolder(ListItemSettingBinding.inflate(inflater), this, mContext);
-
- case SettingsItem.TYPE_SUBMENU:
- return new SubmenuViewHolder(ListItemSubmenuBinding.inflate(inflater), this);
-
- case SettingsItem.TYPE_INPUT_MAPPING_CONTROL:
- return new InputMappingControlSettingViewHolder(ListItemMappingBinding.inflate(inflater),
- this);
-
- case SettingsItem.TYPE_FILE_PICKER:
- return new FilePickerViewHolder(ListItemSettingBinding.inflate(inflater), this);
-
- case SettingsItem.TYPE_RUN_RUNNABLE:
- return new RunRunnableViewHolder(ListItemSettingBinding.inflate(inflater), this, mContext);
-
- case SettingsItem.TYPE_STRING:
- return new InputStringSettingViewHolder(ListItemSettingBinding.inflate(inflater), this);
-
- case SettingsItem.TYPE_HYPERLINK_HEADER:
- return new HeaderHyperLinkViewHolder(ListItemHeaderBinding.inflate(inflater), this);
-
- case SettingsItem.TYPE_DATETIME_CHOICE:
- return new DateTimeSettingViewHolder(ListItemSettingBinding.inflate(inflater), this);
-
- default:
- throw new IllegalArgumentException("Invalid view type: " + viewType);
- }
- }
-
- @Override
- public void onBindViewHolder(@NonNull SettingViewHolder holder, int position)
- {
- holder.bind(getItem(position));
- }
-
- private SettingsItem getItem(int position)
- {
- return mSettings.get(position);
- }
-
- @Override
- public int getItemCount()
- {
- if (mSettings != null)
- {
- return mSettings.size();
- }
- else
- {
- return 0;
- }
- }
-
- @Override
- public int getItemViewType(int position)
- {
- return getItem(position).getType();
- }
-
- public Settings getSettings()
- {
- return mView.getSettings();
- }
-
- public void setSettings(ArrayList<SettingsItem> settings)
- {
- mSettings = settings;
- notifyDataSetChanged();
- }
-
- public void clearSetting(SettingsItem item)
- {
- item.clear(getSettings());
-
- mView.onSettingChanged();
- }
-
- public void notifyAllSettingsChanged()
- {
- notifyItemRangeChanged(0, getItemCount());
-
- mView.onSettingChanged();
- }
-
- public void onBooleanClick(SwitchSetting item, boolean checked)
- {
- item.setChecked(getSettings(), checked);
-
- mView.onSettingChanged();
- }
-
- public void onInputStringClick(InputStringSetting item, int position)
- {
- LayoutInflater inflater = LayoutInflater.from(mContext);
-
- DialogInputStringBinding binding = DialogInputStringBinding.inflate(inflater);
- TextInputEditText input = binding.input;
- input.setText(item.getSelectedValue());
-
- mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
- .setView(binding.getRoot())
- .setMessage(item.getDescription())
- .setPositiveButton(R.string.ok, (dialogInterface, i) ->
- {
- String editTextInput = input.getText().toString();
-
- if (!item.getSelectedValue().equals(editTextInput))
- {
- notifyItemChanged(position);
- mView.onSettingChanged();
- }
-
- item.setSelectedValue(mView.getSettings(), editTextInput);
- })
- .setNegativeButton(R.string.cancel, null)
- .show();
- }
-
- public void onSingleChoiceClick(SingleChoiceSetting item, int position)
- {
- mClickedItem = item;
- mClickedPosition = position;
-
- int value = getSelectionForSingleChoiceValue(item);
-
- mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
- .setTitle(item.getName())
- .setSingleChoiceItems(item.getChoicesId(), value, this)
- .show();
- }
-
- public void onStringSingleChoiceClick(StringSingleChoiceSetting item, int position)
- {
- mClickedItem = item;
- mClickedPosition = position;
-
- item.refreshChoicesAndValues();
-
- String[] choices = item.getChoices();
- int noChoicesAvailableString = item.getNoChoicesAvailableString();
- if (noChoicesAvailableString != 0 && choices.length == 0)
- {
- mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
- .setTitle(item.getName())
- .setMessage(noChoicesAvailableString)
- .setPositiveButton(R.string.ok, null)
- .show();
- }
- else
- {
- mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
- .setTitle(item.getName())
- .setSingleChoiceItems(item.getChoices(), item.getSelectedValueIndex(),
- this)
- .show();
- }
- }
-
- public void onSingleChoiceDynamicDescriptionsClick(SingleChoiceSettingDynamicDescriptions item,
- int position)
- {
- mClickedItem = item;
- mClickedPosition = position;
-
- int value = getSelectionForSingleChoiceDynamicDescriptionsValue(item);
-
- mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
- .setTitle(item.getName())
- .setSingleChoiceItems(item.getChoicesId(), value, this)
- .show();
- }
-
- public void onSliderClick(SliderSetting item, int position)
- {
- mClickedItem = item;
- mClickedPosition = position;
- mSeekbarProgress = item.getSelectedValue();
-
- LayoutInflater inflater = LayoutInflater.from(mView.getActivity());
- DialogSliderBinding binding = DialogSliderBinding.inflate(inflater);
-
- mTextSliderValue = binding.textValue;
- mTextSliderValue.setText(String.valueOf(mSeekbarProgress));
-
- binding.textUnits.setText(item.getUnits());
-
- Slider slider = binding.slider;
- slider.setValueFrom(item.getMin());
- slider.setValueTo(item.getMax());
- slider.setValue(mSeekbarProgress);
- slider.setStepSize(item.getStepSize());
-
- slider.addOnChangeListener(this);
-
- mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
- .setTitle(item.getName())
- .setView(binding.getRoot())
- .setPositiveButton(R.string.ok, this)
- .show();
- }
-
- public void onSubmenuClick(SubmenuSetting item)
- {
- mView.loadSubMenu(item.getMenuKey());
- }
-
- public void onInputMappingClick(final InputMappingControlSetting item, final int position)
- {
- if (item.getController().getDefaultDevice().isEmpty() && !mView.isMappingAllDevices())
- {
- new MaterialAlertDialogBuilder(mView.getActivity())
- .setMessage(R.string.input_binding_no_device)
- .setPositiveButton(R.string.ok, this)
- .show();
- return;
- }
-
- final MotionAlertDialog dialog = new MotionAlertDialog(mView.getActivity(), item,
- mView.isMappingAllDevices());
-
- Drawable background = ContextCompat.getDrawable(mContext, R.drawable.dialog_round);
- @ColorInt int color = new ElevationOverlayProvider(dialog.getContext()).compositeOverlay(
- MaterialColors.getColor(dialog.getWindow().getDecorView(), R.attr.colorSurface),
- dialog.getWindow().getDecorView().getElevation());
- background.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
- dialog.getWindow().setBackgroundDrawable(background);
-
- dialog.setTitle(R.string.input_binding);
- dialog.setMessage(String.format(mContext.getString(R.string.input_binding_description),
- item.getName()));
- dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
- dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear),
- (dialogInterface, i) -> item.clearValue());
- dialog.setOnDismissListener(dialog1 ->
- {
- notifyItemChanged(position);
- mView.onSettingChanged();
- });
- dialog.setCanceledOnTouchOutside(false);
- dialog.show();
- }
-
- public void onAdvancedInputMappingClick(final InputMappingControlSetting item, final int position)
- {
- LayoutInflater inflater = LayoutInflater.from(mContext);
-
- DialogAdvancedMappingBinding binding = DialogAdvancedMappingBinding.inflate(inflater);
-
- final AdvancedMappingDialog dialog = new AdvancedMappingDialog(mContext, binding,
- item.getControlReference(), item.getController());
-
- Drawable background = ContextCompat.getDrawable(mContext, R.drawable.dialog_round);
- @ColorInt int color = new ElevationOverlayProvider(dialog.getContext()).compositeOverlay(
- MaterialColors.getColor(dialog.getWindow().getDecorView(), R.attr.colorSurface),
- dialog.getWindow().getDecorView().getElevation());
- background.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
- dialog.getWindow().setBackgroundDrawable(background);
-
- dialog.setTitle(item.isInput() ?
- R.string.input_configure_input : R.string.input_configure_output);
- dialog.setView(binding.getRoot());
- dialog.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getString(R.string.ok),
- (dialogInterface, i) ->
- {
- item.setValue(dialog.getExpression());
- notifyItemChanged(position);
- mView.onSettingChanged();
- });
- dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
- dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear),
- (dialogInterface, i) ->
- {
- item.clearValue();
- notifyItemChanged(position);
- mView.onSettingChanged();
- });
- dialog.setCanceledOnTouchOutside(false);
- dialog.show();
- }
-
- public void onFilePickerDirectoryClick(SettingsItem item, int position)
- {
- mClickedItem = item;
- mClickedPosition = position;
-
- if (!PermissionsHandler.isExternalStorageLegacy())
- {
- new MaterialAlertDialogBuilder(mContext)
- .setMessage(R.string.path_not_changeable_scoped_storage)
- .setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss())
- .show();
- }
- else
- {
- FileBrowserHelper.openDirectoryPicker(mView.getActivity(), FileBrowserHelper.GAME_EXTENSIONS);
- }
- }
-
- public void onFilePickerFileClick(SettingsItem item, int position)
- {
- mClickedItem = item;
- mClickedPosition = position;
- FilePicker filePicker = (FilePicker) item;
-
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- {
- intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, filePicker.getSelectedValue());
- }
-
- mView.getActivity().startActivityForResult(intent, filePicker.getRequestType());
- }
-
- public void onDateTimeClick(DateTimeChoiceSetting item, int position)
- {
- mClickedItem = item;
- mClickedPosition = position;
- long storedTime = Long.decode(item.getSelectedValue()) * 1000;
-
- // Helper to extract hour and minute from epoch time
- Calendar calendar = Calendar.getInstance();
- calendar.setTimeInMillis(storedTime);
- calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
-
- // Start and end epoch times available for the Wii's date picker
- CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
- .setStart(946684800000L)
- .setEnd(2082672000000L)
- .build();
-
- int timeFormat = TimeFormat.CLOCK_12H;
- if (DateFormat.is24HourFormat(mView.getActivity()))
- {
- timeFormat = TimeFormat.CLOCK_24H;
- }
-
- MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
- .setSelection(storedTime)
- .setTitleText(R.string.select_rtc_date)
- .setCalendarConstraints(calendarConstraints)
- .build();
- MaterialTimePicker timePicker = new MaterialTimePicker.Builder()
- .setTimeFormat(timeFormat)
- .setHour(calendar.get(Calendar.HOUR_OF_DAY))
- .setMinute(calendar.get(Calendar.MINUTE))
- .setTitleText(R.string.select_rtc_time)
- .build();
-
- datePicker.addOnPositiveButtonClickListener(
- selection -> timePicker.show(mView.getActivity().getSupportFragmentManager(),
- "TimePicker"));
- timePicker.addOnPositiveButtonClickListener(selection ->
- {
- long epochTime = datePicker.getSelection() / 1000;
- epochTime += (long) timePicker.getHour() * 60 * 60;
- epochTime += (long) timePicker.getMinute() * 60;
- String rtcString = "0x" + Long.toHexString(epochTime);
- if (!item.getSelectedValue().equals(rtcString))
- {
- notifyItemChanged(mClickedPosition);
- mView.onSettingChanged();
- }
-
- item.setSelectedValue(mView.getSettings(), rtcString);
-
- mClickedItem = null;
- });
- datePicker.show(mView.getActivity().getSupportFragmentManager(), "DatePicker");
- }
-
- public void onFilePickerConfirmation(String selectedFile)
- {
- FilePicker filePicker = (FilePicker) mClickedItem;
-
- if (!filePicker.getSelectedValue().equals(selectedFile))
- {
- notifyItemChanged(mClickedPosition);
- mView.onSettingChanged();
- }
-
- filePicker.setSelectedValue(mView.getSettings(), selectedFile);
-
- mClickedItem = null;
- }
-
- public static void clearLog()
- {
- // Don't delete the log in case it is being monitored by another app.
- File log = new File(DirectoryInitialization.getUserDirectory() + "/Logs/dolphin.log");
-
- try
- {
- RandomAccessFile raf = new RandomAccessFile(log, "rw");
- raf.setLength(0);
- }
- catch (IOException e)
- {
- Log.error("[SettingsAdapter] Failed to clear log file: " + e.getMessage());
- }
- }
-
- public void onMenuTagAction(@NonNull MenuTag menuTag, int value)
- {
- mView.onMenuTagAction(menuTag, value);
- }
-
- public boolean hasMenuTagActionForValue(@NonNull MenuTag menuTag, int value)
- {
- return mView.hasMenuTagActionForValue(menuTag, value);
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- if (mClickedItem instanceof SingleChoiceSetting)
- {
- SingleChoiceSetting scSetting = (SingleChoiceSetting) mClickedItem;
-
- int value = getValueForSingleChoiceSelection(scSetting, which);
- if (scSetting.getSelectedValue() != value)
- mView.onSettingChanged();
-
- scSetting.setSelectedValue(getSettings(), value);
-
- closeDialog();
- }
- else if (mClickedItem instanceof SingleChoiceSettingDynamicDescriptions)
- {
- SingleChoiceSettingDynamicDescriptions scSetting =
- (SingleChoiceSettingDynamicDescriptions) mClickedItem;
-
- int value = getValueForSingleChoiceDynamicDescriptionsSelection(scSetting, which);
- if (scSetting.getSelectedValue() != value)
- mView.onSettingChanged();
-
- scSetting.setSelectedValue(getSettings(), value);
-
- closeDialog();
- }
- else if (mClickedItem instanceof StringSingleChoiceSetting)
- {
- StringSingleChoiceSetting scSetting = (StringSingleChoiceSetting) mClickedItem;
- String value = scSetting.getValueAt(which);
- if (!scSetting.getSelectedValue().equals(value))
- mView.onSettingChanged();
-
- scSetting.setSelectedValue(getSettings(), value);
-
- closeDialog();
- }
- else if (mClickedItem instanceof IntSliderSetting)
- {
- IntSliderSetting sliderSetting = (IntSliderSetting) mClickedItem;
- if (sliderSetting.getSelectedValue() != mSeekbarProgress)
- mView.onSettingChanged();
-
- sliderSetting.setSelectedValue(getSettings(), mSeekbarProgress);
-
- closeDialog();
- }
- else if (mClickedItem instanceof FloatSliderSetting)
- {
- FloatSliderSetting sliderSetting = (FloatSliderSetting) mClickedItem;
- if (sliderSetting.getSelectedValue() != mSeekbarProgress)
- mView.onSettingChanged();
-
- sliderSetting.setSelectedValue(getSettings(), mSeekbarProgress);
-
- closeDialog();
- }
-
- mClickedItem = null;
- mSeekbarProgress = -1;
- }
-
- public void closeDialog()
- {
- if (mDialog != null)
- {
- if (mClickedPosition != -1)
- {
- notifyItemChanged(mClickedPosition);
- mClickedPosition = -1;
- }
- mDialog.dismiss();
- mDialog = null;
- }
- }
-
- @Override
- public void onValueChange(@NonNull Slider slider, float progress, boolean fromUser)
- {
- mSeekbarProgress = (int) progress;
- mTextSliderValue.setText(String.valueOf(mSeekbarProgress));
- }
-
- private int getValueForSingleChoiceSelection(SingleChoiceSetting item, int which)
- {
- int valuesId = item.getValuesId();
-
- if (valuesId > 0)
- {
- int[] valuesArray = mContext.getResources().getIntArray(valuesId);
- return valuesArray[which];
- }
- else
- {
- return which;
- }
- }
-
- private int getSelectionForSingleChoiceValue(SingleChoiceSetting item)
- {
- int value = item.getSelectedValue();
- int valuesId = item.getValuesId();
-
- if (valuesId > 0)
- {
- int[] valuesArray = mContext.getResources().getIntArray(valuesId);
- for (int index = 0; index < valuesArray.length; index++)
- {
- int current = valuesArray[index];
- if (current == value)
- {
- return index;
- }
- }
- }
- else
- {
- return value;
- }
-
- return -1;
- }
-
- private int getValueForSingleChoiceDynamicDescriptionsSelection(
- SingleChoiceSettingDynamicDescriptions item, int which)
- {
- int valuesId = item.getValuesId();
-
- if (valuesId > 0)
- {
- int[] valuesArray = mContext.getResources().getIntArray(valuesId);
- return valuesArray[which];
- }
- else
- {
- return which;
- }
- }
-
- private int getSelectionForSingleChoiceDynamicDescriptionsValue(
- SingleChoiceSettingDynamicDescriptions item)
- {
- int value = item.getSelectedValue();
- int valuesId = item.getValuesId();
-
- if (valuesId > 0)
- {
- int[] valuesArray = mContext.getResources().getIntArray(valuesId);
- for (int index = 0; index < valuesArray.length; index++)
- {
- int current = valuesArray[index];
- if (current == value)
- {
- return index;
- }
- }
- }
- else
- {
- return value;
- }
-
- return -1;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt
new file mode 100644
index 0000000000..d5df7c0e09
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt
@@ -0,0 +1,590 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.settings.ui
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.graphics.PorterDuff
+import android.os.Build
+import android.provider.DocumentsContract
+import android.text.format.DateFormat
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.color.MaterialColors
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.elevation.ElevationOverlayProvider
+import com.google.android.material.slider.Slider
+import com.google.android.material.timepicker.MaterialTimePicker
+import com.google.android.material.timepicker.TimeFormat
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.databinding.*
+import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting
+import org.dolphinemu.dolphinemu.features.input.ui.AdvancedMappingDialog
+import org.dolphinemu.dolphinemu.features.input.ui.MotionAlertDialog
+import org.dolphinemu.dolphinemu.features.input.ui.viewholder.InputMappingControlSettingViewHolder
+import org.dolphinemu.dolphinemu.features.settings.model.Settings
+import org.dolphinemu.dolphinemu.features.settings.model.view.*
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.*
+import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
+import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
+import org.dolphinemu.dolphinemu.utils.Log
+import org.dolphinemu.dolphinemu.utils.PermissionsHandler
+import java.io.File
+import java.io.IOException
+import java.io.RandomAccessFile
+import java.util.*
+
+class SettingsAdapter(
+ private val fragmentView: SettingsFragmentView,
+ private val context: Context
+) :
+ RecyclerView.Adapter<SettingViewHolder>(), DialogInterface.OnClickListener,
+ Slider.OnChangeListener {
+ private var settingsList: ArrayList<SettingsItem>? = null
+ private var clickedItem: SettingsItem? = null
+ private var clickedPosition: Int = -1
+ private var seekbarProgress = 0
+ private var dialog: AlertDialog? = null
+ private var textSliderValue: TextView? = null
+
+ val settings: Settings?
+ get() = fragmentView.settings
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return when (viewType) {
+ SettingsItem.TYPE_HEADER -> HeaderViewHolder(
+ ListItemHeaderBinding.inflate(inflater),
+ this
+ )
+ SettingsItem.TYPE_SWITCH -> SwitchSettingViewHolder(
+ ListItemSettingSwitchBinding.inflate(inflater),
+ this
+ )
+ SettingsItem.TYPE_STRING_SINGLE_CHOICE,
+ SettingsItem.TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS,
+ SettingsItem.TYPE_SINGLE_CHOICE -> SingleChoiceViewHolder(
+ ListItemSettingBinding.inflate(inflater),
+ this
+ )
+ SettingsItem.TYPE_SLIDER -> SliderViewHolder(
+ ListItemSettingBinding.inflate(
+ inflater
+ ), this, context
+ )
+ SettingsItem.TYPE_SUBMENU -> SubmenuViewHolder(
+ ListItemSubmenuBinding.inflate(
+ inflater
+ ), this
+ )
+ SettingsItem.TYPE_INPUT_MAPPING_CONTROL -> InputMappingControlSettingViewHolder(
+ ListItemMappingBinding.inflate(inflater),
+ this
+ )
+ SettingsItem.TYPE_FILE_PICKER -> FilePickerViewHolder(
+ ListItemSettingBinding.inflate(
+ inflater
+ ), this
+ )
+ SettingsItem.TYPE_RUN_RUNNABLE -> RunRunnableViewHolder(
+ ListItemSettingBinding.inflate(
+ inflater
+ ), this, context
+ )
+ SettingsItem.TYPE_STRING -> InputStringSettingViewHolder(
+ ListItemSettingBinding.inflate(
+ inflater
+ ), this
+ )
+ SettingsItem.TYPE_HYPERLINK_HEADER -> HeaderHyperLinkViewHolder(
+ ListItemHeaderBinding.inflate(
+ inflater
+ ), this
+ )
+ SettingsItem.TYPE_DATETIME_CHOICE -> DateTimeSettingViewHolder(
+ ListItemSettingBinding.inflate(
+ inflater
+ ), this
+ )
+ else -> throw IllegalArgumentException("Invalid view type: $viewType")
+ }
+ }
+
+ override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ private fun getItem(position: Int): SettingsItem {
+ return settingsList!![position]
+ }
+
+ override fun getItemCount(): Int {
+ return if (settingsList != null) {
+ settingsList!!.size
+ } else {
+ 0
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return getItem(position).type
+ }
+
+ fun setSettings(settings: ArrayList<SettingsItem>?) {
+ settingsList = settings
+ notifyDataSetChanged()
+ }
+
+ fun clearSetting(item: SettingsItem) {
+ item.clear(settings!!)
+ fragmentView.onSettingChanged()
+ }
+
+ fun notifyAllSettingsChanged() {
+ notifyItemRangeChanged(0, itemCount)
+ fragmentView.onSettingChanged()
+ }
+
+ fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
+ item.setChecked(settings, checked)
+ fragmentView.onSettingChanged()
+ }
+
+ fun onInputStringClick(item: InputStringSetting, position: Int) {
+ val inflater = LayoutInflater.from(context)
+ val binding = DialogInputStringBinding.inflate(inflater)
+ val input = binding.input
+ input.setText(item.selectedValue)
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
+ .setView(binding.root)
+ .setMessage(item.description)
+ .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int ->
+ val editTextInput = input.text.toString()
+ if (item.selectedValue != editTextInput) {
+ notifyItemChanged(position)
+ fragmentView.onSettingChanged()
+ }
+ item.setSelectedValue(fragmentView.settings!!, editTextInput)
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ }
+
+ fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
+ clickedItem = item
+ clickedPosition = position
+ val value = getSelectionForSingleChoiceValue(item)
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
+ .setTitle(item.name)
+ .setSingleChoiceItems(item.choicesId, value, this)
+ .show()
+ }
+
+ fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
+ clickedItem = item
+ clickedPosition = position
+ item.refreshChoicesAndValues()
+ val choices = item.choices
+ val noChoicesAvailableString = item.noChoicesAvailableString
+ dialog = if (noChoicesAvailableString != 0 && choices!!.isEmpty()) {
+ MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
+ .setTitle(item.name)
+ .setMessage(noChoicesAvailableString)
+ .setPositiveButton(R.string.ok, null)
+ .show()
+ } else {
+ MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
+ .setTitle(item.name)
+ .setSingleChoiceItems(
+ item.choices, item.selectedValueIndex,
+ this
+ )
+ .show()
+ }
+ }
+
+ fun onSingleChoiceDynamicDescriptionsClick(
+ item: SingleChoiceSettingDynamicDescriptions,
+ position: Int
+ ) {
+ clickedItem = item
+ clickedPosition = position
+
+ val value = getSelectionForSingleChoiceDynamicDescriptionsValue(item)
+
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
+ .setTitle(item.name)
+ .setSingleChoiceItems(item.choicesId, value, this)
+ .show()
+ }
+
+ fun onSliderClick(item: SliderSetting, position: Int) {
+ clickedItem = item
+ clickedPosition = position
+ seekbarProgress = item.selectedValue
+
+ val inflater = LayoutInflater.from(fragmentView.fragmentActivity)
+ val binding = DialogSliderBinding.inflate(inflater)
+
+ textSliderValue = binding.textValue
+ textSliderValue!!.text = seekbarProgress.toString()
+
+ binding.textUnits.text = item.units
+
+ val slider = binding.slider
+ slider.valueFrom = item.min.toFloat()
+ slider.valueTo = item.max.toFloat()
+ slider.value = seekbarProgress.toFloat()
+ slider.stepSize = item.stepSize.toFloat()
+ slider.addOnChangeListener(this)
+
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
+ .setTitle(item.name)
+ .setView(binding.root)
+ .setPositiveButton(R.string.ok, this)
+ .show()
+ }
+
+ fun onSubmenuClick(item: SubmenuSetting) {
+ fragmentView.loadSubMenu(item.menuKey)
+ }
+
+ fun onInputMappingClick(item: InputMappingControlSetting, position: Int) {
+ if (item.controller.defaultDevice.isEmpty() && !fragmentView.isMappingAllDevices) {
+ MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
+ .setMessage(R.string.input_binding_no_device)
+ .setPositiveButton(R.string.ok, this)
+ .show()
+ return
+ }
+
+ val dialog = MotionAlertDialog(
+ fragmentView.fragmentActivity, item,
+ fragmentView.isMappingAllDevices
+ )
+
+ val background = ContextCompat.getDrawable(context, R.drawable.dialog_round)
+ @ColorInt val color = ElevationOverlayProvider(dialog.context).compositeOverlay(
+ MaterialColors.getColor(dialog.window!!.decorView, R.attr.colorSurface),
+ dialog.window!!.decorView.elevation
+ )
+ background!!.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ dialog.window!!.setBackgroundDrawable(background)
+
+ dialog.setTitle(R.string.input_binding)
+ dialog.setMessage(
+ String.format(
+ context.getString(R.string.input_binding_description),
+ item.name
+ )
+ )
+ dialog.setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(R.string.cancel), this)
+ dialog.setButton(
+ AlertDialog.BUTTON_NEUTRAL,
+ context.getString(R.string.clear)
+ ) { _: DialogInterface?, _: Int -> item.clearValue() }
+ dialog.setOnDismissListener {
+ notifyItemChanged(position)
+ fragmentView.onSettingChanged()
+ }
+ dialog.setCanceledOnTouchOutside(false)
+ dialog.show()
+ }
+
+ fun onAdvancedInputMappingClick(item: InputMappingControlSetting, position: Int) {
+ val inflater = LayoutInflater.from(context)
+ val binding = DialogAdvancedMappingBinding.inflate(inflater)
+ val dialog = AdvancedMappingDialog(
+ context,
+ binding,
+ item.controlReference,
+ item.controller
+ )
+
+ val background = ContextCompat.getDrawable(context, R.drawable.dialog_round)
+ @ColorInt val color = ElevationOverlayProvider(dialog.context).compositeOverlay(
+ MaterialColors.getColor(dialog.window!!.decorView, R.attr.colorSurface),
+ dialog.window!!.decorView.elevation
+ )
+ background!!.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ dialog.window!!.setBackgroundDrawable(background)
+
+ dialog.setTitle(if (item.isInput) R.string.input_configure_input else R.string.input_configure_output)
+ dialog.setView(binding.root)
+ dialog.setButton(
+ AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ok)
+ ) { _: DialogInterface?, _: Int ->
+ item.value = dialog.expression
+ notifyItemChanged(position)
+ fragmentView.onSettingChanged()
+ }
+ dialog.setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(R.string.cancel), this)
+ dialog.setButton(
+ AlertDialog.BUTTON_NEUTRAL,
+ context.getString(R.string.clear)
+ ) { _: DialogInterface?, _: Int ->
+ item.clearValue()
+ notifyItemChanged(position)
+ fragmentView.onSettingChanged()
+ }
+ dialog.setCanceledOnTouchOutside(false)
+ dialog.show()
+ }
+
+ fun onFilePickerDirectoryClick(item: SettingsItem?, position: Int) {
+ clickedItem = item
+ clickedPosition = position
+
+ if (!PermissionsHandler.isExternalStorageLegacy()) {
+ MaterialAlertDialogBuilder(context)
+ .setMessage(R.string.path_not_changeable_scoped_storage)
+ .setPositiveButton(R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
+ .show()
+ } else {
+ FileBrowserHelper.openDirectoryPicker(
+ fragmentView.fragmentActivity,
+ FileBrowserHelper.GAME_EXTENSIONS
+ )
+ }
+ }
+
+ fun onFilePickerFileClick(item: SettingsItem, position: Int) {
+ clickedItem = item
+ clickedPosition = position
+ val filePicker = item as FilePicker
+
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "*/*"
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, filePicker.getSelectedValue())
+ }
+
+ fragmentView.fragmentActivity.startActivityForResult(intent, filePicker.requestType)
+ }
+
+ fun onDateTimeClick(item: DateTimeChoiceSetting, position: Int) {
+ clickedItem = item
+ clickedPosition = position
+ val storedTime = java.lang.Long.decode(item.getSelectedValue()) * 1000
+
+ // Helper to extract hour and minute from epoch time
+ val calendar = Calendar.getInstance()
+ calendar.timeInMillis = storedTime
+ calendar.timeZone = TimeZone.getTimeZone("UTC")
+
+ // Start and end epoch times available for the Wii's date picker
+ val calendarConstraints = CalendarConstraints.Builder()
+ .setStart(946684800000L)
+ .setEnd(2082672000000L)
+ .build()
+
+ var timeFormat = TimeFormat.CLOCK_12H
+ if (DateFormat.is24HourFormat(fragmentView.fragmentActivity)) {
+ timeFormat = TimeFormat.CLOCK_24H
+ }
+
+ val datePicker = MaterialDatePicker.Builder.datePicker()
+ .setSelection(storedTime)
+ .setTitleText(R.string.select_rtc_date)
+ .setCalendarConstraints(calendarConstraints)
+ .build()
+ val timePicker = MaterialTimePicker.Builder()
+ .setTimeFormat(timeFormat)
+ .setHour(calendar[Calendar.HOUR_OF_DAY])
+ .setMinute(calendar[Calendar.MINUTE])
+ .setTitleText(R.string.select_rtc_time)
+ .build()
+
+ datePicker.addOnPositiveButtonClickListener {
+ timePicker.show(
+ fragmentView.fragmentActivity.supportFragmentManager,
+ "TimePicker"
+ )
+ }
+ timePicker.addOnPositiveButtonClickListener {
+ var epochTime = datePicker.selection!! / 1000
+ epochTime += timePicker.hour.toLong() * 60 * 60
+ epochTime += timePicker.minute.toLong() * 60
+ val rtcString = "0x" + java.lang.Long.toHexString(epochTime)
+ if (item.getSelectedValue() != rtcString) {
+ notifyItemChanged(clickedPosition)
+ fragmentView.onSettingChanged()
+ }
+ item.setSelectedValue(fragmentView.settings!!, rtcString)
+ clickedItem = null
+ }
+ datePicker.show(fragmentView.fragmentActivity.supportFragmentManager, "DatePicker")
+ }
+
+ fun onFilePickerConfirmation(selectedFile: String) {
+ val filePicker = clickedItem as FilePicker?
+
+ if (filePicker!!.getSelectedValue() != selectedFile) {
+ notifyItemChanged(clickedPosition)
+ fragmentView.onSettingChanged()
+ }
+
+ filePicker.setSelectedValue(fragmentView.settings!!, selectedFile)
+
+ clickedItem = null
+ }
+
+ fun onMenuTagAction(menuTag: MenuTag, value: Int) {
+ fragmentView.onMenuTagAction(menuTag, value)
+ }
+
+ fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean {
+ return fragmentView.hasMenuTagActionForValue(menuTag, value)
+ }
+
+ override fun onClick(dialog: DialogInterface, which: Int) {
+ when (clickedItem) {
+ is SingleChoiceSetting -> {
+ val scSetting = clickedItem as SingleChoiceSetting
+
+ val value = getValueForSingleChoiceSelection(scSetting, which)
+ if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
+
+ scSetting.setSelectedValue(settings, value)
+
+ closeDialog()
+ }
+ is SingleChoiceSettingDynamicDescriptions -> {
+ val scSetting = clickedItem as SingleChoiceSettingDynamicDescriptions
+
+ val value = getValueForSingleChoiceDynamicDescriptionsSelection(scSetting, which)
+ if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
+
+ scSetting.setSelectedValue(settings!!, value)
+
+ closeDialog()
+ }
+ is StringSingleChoiceSetting -> {
+ val scSetting = clickedItem as StringSingleChoiceSetting
+
+ val value = scSetting.getValueAt(which)
+ if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
+
+ scSetting.setSelectedValue(settings, value)
+
+ closeDialog()
+ }
+ is IntSliderSetting -> {
+ val sliderSetting = clickedItem as IntSliderSetting
+ if (sliderSetting.selectedValue != seekbarProgress) fragmentView.onSettingChanged()
+ sliderSetting.setSelectedValue(settings, seekbarProgress)
+ closeDialog()
+ }
+ is FloatSliderSetting -> {
+ val sliderSetting = clickedItem as FloatSliderSetting
+
+ if (sliderSetting.selectedValue != seekbarProgress) fragmentView.onSettingChanged()
+
+ sliderSetting.setSelectedValue(settings, seekbarProgress.toFloat())
+
+ closeDialog()
+ }
+ }
+ clickedItem = null
+ seekbarProgress = -1
+ }
+
+ fun closeDialog() {
+ if (dialog != null) {
+ if (clickedPosition != -1) {
+ notifyItemChanged(clickedPosition)
+ clickedPosition = -1
+ }
+ dialog!!.dismiss()
+ dialog = null
+ }
+ }
+
+ override fun onValueChange(slider: Slider, progress: Float, fromUser: Boolean) {
+ seekbarProgress = progress.toInt()
+ textSliderValue!!.text = seekbarProgress.toString()
+ }
+
+ private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
+ val valuesId = item.valuesId
+
+ return if (valuesId > 0) {
+ val valuesArray = context.resources.getIntArray(valuesId)
+ valuesArray[which]
+ } else {
+ which
+ }
+ }
+
+ private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
+ val value = item.selectedValue
+ val valuesId = item.valuesId
+
+ if (valuesId > 0) {
+ val valuesArray = context.resources.getIntArray(valuesId)
+ for (index in valuesArray.indices) {
+ val current = valuesArray[index]
+ if (current == value) {
+ return index
+ }
+ }
+ } else {
+ return value
+ }
+ return -1
+ }
+
+ private fun getValueForSingleChoiceDynamicDescriptionsSelection(
+ item: SingleChoiceSettingDynamicDescriptions,
+ which: Int
+ ): Int {
+ val valuesId = item.valuesId
+ return if (valuesId > 0) {
+ val valuesArray = context.resources.getIntArray(valuesId)
+ valuesArray[which]
+ } else {
+ which
+ }
+ }
+
+ private fun getSelectionForSingleChoiceDynamicDescriptionsValue(
+ item: SingleChoiceSettingDynamicDescriptions
+ ): Int {
+ val value = item.selectedValue
+ val valuesId = item.valuesId
+ if (valuesId > 0) {
+ val valuesArray = context.resources.getIntArray(valuesId)
+ for (index in valuesArray.indices) {
+ val current = valuesArray[index]
+ if (current == value) {
+ return index
+ }
+ }
+ } else {
+ return value
+ }
+ return -1
+ }
+
+ companion object {
+ fun clearLog() {
+ // Don't delete the log in case it is being monitored by another app.
+ val log = File(DirectoryInitialization.getUserDirectory() + "/Logs/dolphin.log")
+ try {
+ val raf = RandomAccessFile(log, "rw")
+ raf.setLength(0)
+ } catch (e: IOException) {
+ Log.error("[SettingsAdapter] Failed to clear log file: " + e.message)
+ }
+ }
+ }
+}