From 7832e294f9e1b8f464be34481d939543bc7872ca Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 17 Dec 2024 16:59:47 -0300 Subject: [PATCH 01/16] Fix potential crash during game over animation when video background is enabled --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index a1b08058b..e890a2742 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2340,9 +2340,13 @@ public void onUpdate(float pSecondsElapsed) { scene.setTimeMultiplier(decreasedSpeed); if (video != null) { - // Aparently MediaPlayer API doesn't support setting playback speed - // below 0.01 causing an IllegalStateException. - video.setPlaybackSpeed(Math.max(0.01f, decreasedSpeed)); + // In some devices this can throw an exception, unfortunately there's no + // documentation that explains how to avoid that scenario. Thanks Google. + try { + video.setPlaybackSpeed(decreasedSpeed); + } catch (Exception e) { + Log.e("GameScene", "Failed to change video playback speed during game over animation.", e); + } } songService.setFrequencyForcefully(decreasedFrequency); From 1805adbfe29f050fcffbc2374c2716f9ffc3ccba Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 17 Dec 2024 18:00:13 -0300 Subject: [PATCH 02/16] Rename method to act as a overload --- src/ru/nsu/ccfit/zuev/osu/ToastLogger.java | 2 +- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 8 ++++---- src/ru/nsu/ccfit/zuev/osu/menu/ModMenu.java | 2 +- src/ru/nsu/ccfit/zuev/osu/menu/PauseMenu.java | 2 +- src/ru/nsu/ccfit/zuev/osu/menu/SongMenu.java | 2 +- src/ru/nsu/ccfit/zuev/osu/scoring/Replay.java | 4 ++-- src/ru/nsu/ccfit/zuev/osu/scoring/ScoringScene.java | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/ToastLogger.java b/src/ru/nsu/ccfit/zuev/osu/ToastLogger.java index eae8680c6..6e8ea5701 100644 --- a/src/ru/nsu/ccfit/zuev/osu/ToastLogger.java +++ b/src/ru/nsu/ccfit/zuev/osu/ToastLogger.java @@ -33,7 +33,7 @@ public static void showText(final String message, final boolean showlong) { showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show()); } - public static void showTextId(@StringRes final int resID, final boolean showlong) { + public static void showText(@StringRes final int resID, final boolean showlong) { showText(StringTable.get(resID), showlong); } diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index e890a2742..7180f54ba 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -379,7 +379,7 @@ private void setBackground() { private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, final CoroutineScope scope) { if (!SecurityUtils.verifyFileIntegrity(GlobalManager.getInstance().getMainActivity())) { - ToastLogger.showTextId(com.osudroid.resources.R.string.file_integrity_tampered, true); + ToastLogger.showText(com.osudroid.resources.R.string.file_integrity_tampered, true); return false; } @@ -390,7 +390,7 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina MD5Calculator.getStringMD5(rFile) + ".odr"; Debug.i("ReplayFile = " + replayFilePath); if (!OnlineFileOperator.downloadFile(rFile, this.replayFilePath)) { - ToastLogger.showTextId(com.osudroid.resources.R.string.replay_cantdownload, true); + ToastLogger.showText(com.osudroid.resources.R.string.replay_cantdownload, true); return false; } } else @@ -421,7 +421,7 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina } if (!parsedBeatmap.getMd5().equals(beatmapInfo.getMD5())) { - ToastLogger.showTextId(com.osudroid.resources.R.string.file_integrity_tampered, true); + ToastLogger.showText(com.osudroid.resources.R.string.file_integrity_tampered, true); return false; } @@ -567,7 +567,7 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina if (replayFilePath != null) { replaying = replay.load(replayFilePath, true); if (!replaying) { - ToastLogger.showTextId(com.osudroid.resources.R.string.replay_invalid, true); + ToastLogger.showText(com.osudroid.resources.R.string.replay_invalid, true); return false; } GameHelper.setReplayVersion(replay.replayVersion); diff --git a/src/ru/nsu/ccfit/zuev/osu/menu/ModMenu.java b/src/ru/nsu/ccfit/zuev/osu/menu/ModMenu.java index af230278e..159520a20 100644 --- a/src/ru/nsu/ccfit/zuev/osu/menu/ModMenu.java +++ b/src/ru/nsu/ccfit/zuev/osu/menu/ModMenu.java @@ -528,7 +528,7 @@ public boolean handleCustomDifficultyStatisticsFlags() { } if (modsRemoved) { - ToastLogger.showTextId(com.osudroid.resources.R.string.force_diffstat_mod_unpickable, false); + ToastLogger.showText(com.osudroid.resources.R.string.force_diffstat_mod_unpickable, false); } return modsRemoved; diff --git a/src/ru/nsu/ccfit/zuev/osu/menu/PauseMenu.java b/src/ru/nsu/ccfit/zuev/osu/menu/PauseMenu.java index 6e3bfdde0..4ac151b27 100644 --- a/src/ru/nsu/ccfit/zuev/osu/menu/PauseMenu.java +++ b/src/ru/nsu/ccfit/zuev/osu/menu/PauseMenu.java @@ -99,7 +99,7 @@ public boolean onMenuItemClicked(final MenuScene pMenuScene, switch (pMenuItem.getID()) { case ITEM_SAVE_REPLAY: if(fail && !replaySaved && !game.getReplaying() && game.saveFailedReplay()){ - ToastLogger.showTextId(com.osudroid.resources.R.string.message_save_replay_successful, true); + ToastLogger.showText(com.osudroid.resources.R.string.message_save_replay_successful, true); replaySaved = true; } return true; diff --git a/src/ru/nsu/ccfit/zuev/osu/menu/SongMenu.java b/src/ru/nsu/ccfit/zuev/osu/menu/SongMenu.java index e11ce73c2..d73f1865d 100644 --- a/src/ru/nsu/ccfit/zuev/osu/menu/SongMenu.java +++ b/src/ru/nsu/ccfit/zuev/osu/menu/SongMenu.java @@ -1229,7 +1229,7 @@ public void updateScore() { public void openScore(final int id, boolean showOnline, final String playerName) { if (showOnline) { engine.setScene(new LoadingScreen().getScene()); - ToastLogger.showTextId(com.osudroid.resources.R.string.online_loadrecord, false); + ToastLogger.showText(com.osudroid.resources.R.string.online_loadrecord, false); cancelCalculationJobs(); cancelMapStatusLoadingJob(); diff --git a/src/ru/nsu/ccfit/zuev/osu/scoring/Replay.java b/src/ru/nsu/ccfit/zuev/osu/scoring/Replay.java index 037316ace..14c462da7 100644 --- a/src/ru/nsu/ccfit/zuev/osu/scoring/Replay.java +++ b/src/ru/nsu/ccfit/zuev/osu/scoring/Replay.java @@ -281,10 +281,10 @@ public boolean load(InputStream inputStream, String replayFilename, boolean with } catch (EOFException e) { Debug.e("O_o eof..."); Debug.e(e); - ToastLogger.showTextId(com.osudroid.resources.R.string.replay_corrupted, true); + ToastLogger.showText(com.osudroid.resources.R.string.replay_corrupted, true); return false; } catch (Exception e) { - ToastLogger.showTextId(com.osudroid.resources.R.string.replay_corrupted, true); + ToastLogger.showText(com.osudroid.resources.R.string.replay_corrupted, true); Debug.e("Cannot load replay: " + e.getMessage(), e); return false; } diff --git a/src/ru/nsu/ccfit/zuev/osu/scoring/ScoringScene.java b/src/ru/nsu/ccfit/zuev/osu/scoring/ScoringScene.java index db54f2b12..afdad2303 100644 --- a/src/ru/nsu/ccfit/zuev/osu/scoring/ScoringScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/scoring/ScoringScene.java @@ -487,7 +487,7 @@ public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, // Do not save and submit score if note count does not match, since it indicates a corrupted score // (potentially from bugging the gameplay by any unnecessary means). if (totalNotes != beatmap.getTotalHitObjectCount()) { - ToastLogger.showTextId(com.osudroid.resources.R.string.replay_corrupted, true); + ToastLogger.showText(com.osudroid.resources.R.string.replay_corrupted, true); return; } From 01d047f64628b21b3f6b0ab816b3a8b8e3032bca Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 17 Dec 2024 18:44:35 -0300 Subject: [PATCH 03/16] Add missing method in Config --- src/ru/nsu/ccfit/zuev/osu/Config.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ru/nsu/ccfit/zuev/osu/Config.java b/src/ru/nsu/ccfit/zuev/osu/Config.java index b15389526..234f02bc8 100644 --- a/src/ru/nsu/ccfit/zuev/osu/Config.java +++ b/src/ru/nsu/ccfit/zuev/osu/Config.java @@ -843,6 +843,14 @@ public static void setInt(String key, int value) { sharedPreferences.edit().putInt(key, value).commit(); } + public static long getLong(String key, long defaultValue) { + return sharedPreferences.getLong(key, defaultValue); + } + + public static void setLong(String key, long value) { + sharedPreferences.edit().putLong(key, value).commit(); + } + public static String getString(String key, String defaultValue) { return sharedPreferences.getString(key, defaultValue); } From eb64c2b068e02465ce320812697d184d809d0e73 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 17 Dec 2024 18:46:15 -0300 Subject: [PATCH 04/16] Allow to change dialogs dynamically --- src/com/edlplan/ui/fragment/BaseFragment.kt | 15 ++- src/com/reco1l/osu/ui/Dialog.kt | 134 ++++++++++++++------ src/com/reco1l/osu/ui/ListDialog.kt | 28 ++-- 3 files changed, 123 insertions(+), 54 deletions(-) diff --git a/src/com/edlplan/ui/fragment/BaseFragment.kt b/src/com/edlplan/ui/fragment/BaseFragment.kt index 877633dba..c1c9d7b3b 100644 --- a/src/com/edlplan/ui/fragment/BaseFragment.kt +++ b/src/com/edlplan/ui/fragment/BaseFragment.kt @@ -10,27 +10,32 @@ import androidx.fragment.app.Fragment import com.edlplan.framework.easing.Easing import com.edlplan.ui.ActivityOverlay import com.edlplan.ui.EasingHelper -import com.reco1l.osu.* import ru.nsu.ccfit.zuev.osuplus.R abstract class BaseFragment : Fragment(), BackPressListener { var root: View? = null private set - private var background: View? = null var onDismissListener: OnDismissListener? = null var isDismissOnBackgroundClick = false - var isCreated = false - private set var isDismissOnBackPress = true + /** + * Whether the fragment is created. This is set to true after [onCreateView] is called. + */ + var isCreated = false + private set /** * If true, the fragment will intercept back press event when it's received. */ var interceptBackPress = true + /** + * Whether the fragment is loaded. This is set to true after [onLoadView] is called. + */ + var isLoaded = false + private set - private var isLoaded = false private var isDismissCalled = false diff --git a/src/com/reco1l/osu/ui/Dialog.kt b/src/com/reco1l/osu/ui/Dialog.kt index b86ed31e1..fa0c8e7a1 100644 --- a/src/com/reco1l/osu/ui/Dialog.kt +++ b/src/com/reco1l/osu/ui/Dialog.kt @@ -1,8 +1,8 @@ package com.reco1l.osu.ui import android.graphics.Color -import android.text.Html import android.text.method.LinkMovementMethod +import android.util.Log import android.view.ContextThemeWrapper import android.view.Gravity.* import android.view.View @@ -11,6 +11,7 @@ import android.widget.EditText import android.widget.LinearLayout import android.widget.TextView import androidx.core.text.HtmlCompat +import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import com.edlplan.framework.easing.Easing @@ -38,54 +39,95 @@ open class MessageDialog : BaseFragment() { override val layoutID = R.layout.dialog_message_fragment - protected var title: CharSequence = "Alert" + /** + * The title to be displayed in the dialog. + */ + var title: CharSequence = "Alert" + set(value) { + field = value + if (isCreated) { + findViewById(R.id.title)?.text = value + } + } - protected var message: CharSequence = "" + /** + * The message to be displayed in the dialog. + */ + var message: CharSequence = "" + set(value) { + field = value + if (isCreated) { + findViewById(R.id.message)?.text = ( + if (isHTMLMessage) HtmlCompat.fromHtml(value.toString(), FROM_HTML_MODE_LEGACY) + else value + ) + } + } - protected var isHTMLMessage = false + /** + * Whether the message is HTML formatted or not. + */ + var isHTMLMessage = false + set(value) { + field = value + if (isCreated) { + message = message + } + } + + /** + * The buttons to be displayed in the dialog. + */ + var buttons = mutableListOf() + set(value) { + field = value + if (isCreated) { + + val layout = findViewById(R.id.button_layout) + if (layout == null) { + Log.e("MessageDialog", "Buttons layout not found.") + return + } + + layout.removeAllViews() + + for (button in value) { + + val buttonView = Button(ContextThemeWrapper(context, R.style.button_borderless)) + buttonView.minWidth = 300.dp + buttonView.minHeight = 56.dp + buttonView.gravity = CENTER + buttonView.background = requireContext().getDrawable(R.drawable.ripple) + buttonView.text = button.text + buttonView.fontColor = button.tint + buttonView.compoundDrawablePadding = 0 + buttonView.setOnClickListener { button.clickListener(this@MessageDialog) } + + layout.addView(buttonView) + } + } + } protected var allowDismiss = true protected var onDismiss: (() -> Unit)? = null - protected var buttons = mutableListOf() - override fun onLoadView() { - findViewById(R.id.title)?.text = title + title = title + message = message + buttons = buttons - if (isHTMLMessage) { - findViewById(R.id.message)?.apply { - text = HtmlCompat.fromHtml(message.toString(), HtmlCompat.FROM_HTML_MODE_LEGACY) - isClickable = true - movementMethod = LinkMovementMethod.getInstance() - } - } else { - findViewById(R.id.message)?.text = message + findViewById(R.id.message)?.apply { + isClickable = true + movementMethod = LinkMovementMethod.getInstance() } - findViewById(R.id.button_layout)?.also { buttonLayout -> - - for (button in buttons) { - buttonLayout.addView(Button(ContextThemeWrapper(context, R.style.button_borderless)).apply { - - minWidth = 300.dp - minHeight = 56.dp - gravity = CENTER - background = context.getDrawable(R.drawable.ripple) - text = button.text - fontColor = button.tint - compoundDrawablePadding = 0 - - setOnClickListener { button.clickListener(this@MessageDialog) } - }) - } - } val background = findViewById(R.id.frg_background)!! - background.setOnClickListener { callDismissOnBackPress() } + background.setOnClickListener { callDismissOnBackPress() } background.cancelAnimators() .toAlpha(0f) .toAlpha(1f, 200, ease = EasingHelper.asInterpolator(Easing.Out)) @@ -167,10 +209,28 @@ open class PromptDialog : MessageDialog() { * The text input by user. */ var input: String? = null - protected set + set(value) { + field = value + if (isCreated) { + findViewById(R.id.input)?.apply { + if (text.toString() != value) { + setText(value) + } + } + } + } + /** + * The text to be show displayed in the input hint. + */ + var hint: String? = null + set(value) { + field = value + if (isCreated) { + findViewById(R.id.input)?.hint = value + } + } - private var hint: String? = null private var onTextChanged: ((PromptDialog) -> Unit)? = null @@ -197,7 +257,9 @@ open class PromptDialog : MessageDialog() { } - + /** + * The text input by user. + */ fun setInput(text: String?): PromptDialog { input = text return this diff --git a/src/com/reco1l/osu/ui/ListDialog.kt b/src/com/reco1l/osu/ui/ListDialog.kt index 1e4cc917c..5bacff02f 100644 --- a/src/com/reco1l/osu/ui/ListDialog.kt +++ b/src/com/reco1l/osu/ui/ListDialog.kt @@ -56,29 +56,29 @@ open class SelectDialog : MessageDialog() { field = value - if (::recyclerView.isInitialized) { - recyclerView.adapter!!.notifyDataSetChanged() + if (isLoaded) { + findViewById(R.id.list)!!.adapter!!.notifyDataSetChanged() } } protected var selected: Any? = null + set(value) { + field = value + if (isLoaded) { + findViewById(R.id.list)!!.adapter!!.notifyDataSetChanged() + } + } protected var onSelect: ((Any?) -> Unit)? = null - private lateinit var recyclerView: RecyclerView - - override fun onLoadView() { super.onLoadView() - recyclerView = findViewById(R.id.list)!!.apply { - - layoutManager = LinearLayoutManager(context) - adapter = Adapter() - - } + val recyclerView = findViewById(R.id.list)!! + recyclerView.layoutManager = LinearLayoutManager(context) + recyclerView.adapter = Adapter() } @@ -88,7 +88,7 @@ open class SelectDialog : MessageDialog() { * @param replace If true, the current options will be replaced with the new ones. */ @JvmOverloads - fun setOptions(options: List