diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 130e425ce3..ef8e2bafe9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -215,6 +215,7 @@ object NativeLibrary { external fun playTimeManagerGetPlayTime(programId: String): Long external fun playTimeManagerGetCurrentTitleId(): Long external fun playTimeManagerResetProgramPlayTime(programId: String) + external fun playTimeManagerSetPlayTime(programId: String, playTimeSeconds: Long) var coreErrorAlertResult = false val coreErrorAlertLock = Object() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index eb2d333206..8688ae7e62 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -145,11 +145,95 @@ class GamePropertiesFragment : Fragment() { else -> "${seconds}s" } - append("Playtime: ") + append(getString(R.string.playtime)) append(readablePlayTime) } + + binding.playtime.setOnClickListener { + showEditPlaytimeDialog() + } } + private fun showEditPlaytimeDialog() { + val dialogView = layoutInflater.inflate(R.layout.dialog_edit_playtime, null) + val hoursLayout = + dialogView.findViewById(R.id.layout_hours) + val minutesLayout = + dialogView.findViewById(R.id.layout_minutes) + val secondsLayout = + dialogView.findViewById(R.id.layout_seconds) + val hoursInput = + dialogView.findViewById(R.id.input_hours) + val minutesInput = + dialogView.findViewById(R.id.input_minutes) + val secondsInput = + dialogView.findViewById(R.id.input_seconds) + + val playTimeSeconds = NativeLibrary.playTimeManagerGetPlayTime(args.game.programId) + val hours = playTimeSeconds / 3600 + val minutes = (playTimeSeconds % 3600) / 60 + val seconds = playTimeSeconds % 60 + + hoursInput.setText(hours.toString()) + minutesInput.setText(minutes.toString()) + secondsInput.setText(seconds.toString()) + + val dialog = com.google.android.material.dialog.MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.edit_playtime) + .setView(dialogView) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.cancel, null) + .create() + + dialog.setOnShowListener { + val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE) + positiveButton.setOnClickListener { + hoursLayout.error = null + minutesLayout.error = null + secondsLayout.error = null + + val hoursText = hoursInput.text.toString() + val minutesText = minutesInput.text.toString() + val secondsText = secondsInput.text.toString() + + val hoursValue = hoursText.toLongOrNull() ?: 0 + val minutesValue = minutesText.toLongOrNull() ?: 0 + val secondsValue = secondsText.toLongOrNull() ?: 0 + + var hasError = false + + // normally cant be above 9999 + if (hoursValue < 0 || hoursValue > 9999) { + hoursLayout.error = getString(R.string.hours_must_be_between_0_and_9999) + hasError = true + } + + if (minutesValue < 0 || minutesValue > 59) { + minutesLayout.error = getString(R.string.minutes_must_be_between_0_and_59) + hasError = true + } + + if (secondsValue < 0 || secondsValue > 59) { + secondsLayout.error = getString(R.string.seconds_must_be_between_0_and_59) + hasError = true + } + + if (!hasError) { + val totalSeconds = hoursValue * 3600 + minutesValue * 60 + secondsValue + NativeLibrary.playTimeManagerSetPlayTime(args.game.programId, totalSeconds) + getPlayTime() + Toast.makeText( + requireContext(), + R.string.playtime_updated_successfully, + Toast.LENGTH_SHORT + ).show() + dialog.dismiss() + } + } + } + + dialog.show() + } private fun reloadList() { _binding ?: return @@ -331,6 +415,7 @@ class GamePropertiesFragment : Fragment() { override fun onResume() { super.onResume() driverViewModel.updateDriverNameForGame(args.game) + getPlayTime() } private fun setInsets() = diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index cd15a229e4..78f0ec1521 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -779,6 +779,14 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerResetProgramPlayTime(J } } +void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerSetPlayTime(JNIEnv* env, jobject obj, + jstring jprogramId, jlong playTimeSeconds) { + u64 program_id = EmulationSession::GetProgramId(env, jprogramId); + if (play_time_manager) { + play_time_manager->SetPlayTime(program_id, static_cast(playTimeSeconds)); + } +} + jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz, jlong jid) { auto bis_system = diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml b/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml index 5595b096f2..0fa2fa2a29 100644 --- a/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml +++ b/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml @@ -110,9 +110,9 @@ style="?attr/textAppearanceBodyMedium" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layout_constraintStart_toStartOf="@id/title" - app:layout_constraintTop_toBottomOf="@+id/about_game_filename" - android:ellipsize="none" + android:layout_marginHorizontal="16dp" + android:layout_marginBottom="8dp" + android:textAlignment="center" tools:text="Game Playtime" /> diff --git a/src/android/app/src/main/res/layout/dialog_edit_playtime.xml b/src/android/app/src/main/res/layout/dialog_edit_playtime.xml new file mode 100644 index 0000000000..e20508125f --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_edit_playtime.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/fragment_game_properties.xml b/src/android/app/src/main/res/layout/fragment_game_properties.xml index c54da10638..9a3437404f 100644 --- a/src/android/app/src/main/res/layout/fragment_game_properties.xml +++ b/src/android/app/src/main/res/layout/fragment_game_properties.xml @@ -85,8 +85,8 @@ style="?attr/textAppearanceBodyMedium" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:layout_constraintStart_toStartOf="@id/title" - app:layout_constraintTop_toBottomOf="@+id/about_game_filename" + android:layout_marginBottom="8dp" + android:textAlignment="center" tools:text="Game Playtime" /> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c02b91c9fa..536874ddba 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -757,6 +757,18 @@ Copy details Add-ons Toggle mods, updates and DLC + Playtime: + Clear Playtime + Reset the current game\'s playtime back to 0 seconds + This will clear the current game\'s playtime data. Are you sure? + Playtime has been reset + Edit Playtime + Hours + Minutes + Hours must be between 0 and 9999 + Minutes must be between 0 and 59 + Seconds must be between 0 and 59 + Playtime updated successfully Clear shader cache Removes all shaders built while playing this game You will experience more stuttering as the shader cache regenerates @@ -1632,9 +1644,4 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Playtime: %1$d h, %2$d m - Clear Playtime - Reset the current game\'s playtime back to 0 seconds - This will clear the current game\'s playtime data. Are you sure? - Playtime has been reset