From d00b3e47694cc23a6ce0b642d8ae8c131ad7ace6 Mon Sep 17 00:00:00 2001 From: Toby Hsieh Date: Thu, 15 Nov 2018 09:28:00 -0800 Subject: [PATCH] Add a Jump to Time dialog (#868) * Add a Jump to Time dialog This implements #827 by adding a context menu entry to open a dialog that allows the user to input a time/position to seek to for the current song. * Use callback interface for JumpToTimeDialog position submission * Automatically show soft keyboard for JumpToTimeDialog * Automatically move to next EditText in JumpToTimeDialog * Crash if JumpToTimeDialog is started by wrong activity --- .../android/vanilla/JumpToTimeDialog.java | 135 ++++++++++++++++++ .../android/vanilla/PlaybackActivity.java | 1 + .../android/vanilla/PlaybackService.java | 14 +- .../vanilla/SlidingPlaybackActivity.java | 12 +- app/src/main/res/layout/duration_input.xml | 59 ++++++++ app/src/main/res/values/translatable.xml | 7 + 6 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/ch/blinkenlights/android/vanilla/JumpToTimeDialog.java create mode 100644 app/src/main/res/layout/duration_input.xml diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/JumpToTimeDialog.java b/app/src/main/java/ch/blinkenlights/android/vanilla/JumpToTimeDialog.java new file mode 100644 index 00000000..2d77b7ed --- /dev/null +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/JumpToTimeDialog.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018 Toby Hsieh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ch.blinkenlights.android.vanilla; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.Toast; + +/** + * A dialog for the user to input a specific time to jump to for the current song + */ +public class JumpToTimeDialog extends DialogFragment implements DialogInterface.OnClickListener { + private EditText hoursView; + private EditText minutesView; + private EditText secondsView; + + /** + * Callback interface for an activity that shows JumpToTimeDialog + */ + public interface OnPositionSubmitListener { + /** + * Called when the user submits a position to jump/seek to for the current song. + * + * @param position position to seek/jump to in milliseconds + */ + void onPositionSubmit(int position); + } + + /** + * Creates and shows the dialog + * + * @param manager the FragmentManager to add the newly created dialog to + */ + public static void show(FragmentManager manager) { + new JumpToTimeDialog().show(manager, "JumpToTimeDialog"); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Watcher that moves to the next EditText when 2 digits are inserted + TextWatcher textWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + EditText editText = (EditText) getDialog().getCurrentFocus(); + if (editText.length() == 2) { + View view = editText.focusSearch(View.FOCUS_RIGHT); + if (view != null) { + view.requestFocus(); + } + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }; + + View view = LayoutInflater.from(getActivity()).inflate(R.layout.duration_input, null); + hoursView = view.findViewById(R.id.hours); + hoursView.addTextChangedListener(textWatcher); + minutesView = view.findViewById(R.id.minutes); + minutesView.addTextChangedListener(textWatcher); + secondsView = view.findViewById(R.id.seconds); + secondsView.addTextChangedListener(textWatcher); + + Dialog dialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.jump_to_time) + .setView(view) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, null) + .create(); + hoursView.requestFocus(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + return dialog; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + Activity activity = getActivity(); + try { + int hours = parseInteger(hoursView.getText().toString()); + int minutes = parseInteger(minutesView.getText().toString()); + int seconds = parseInteger(secondsView.getText().toString()); + int position = (hours * 3600 + minutes * 60 + seconds) * 1000; + ((OnPositionSubmitListener) activity).onPositionSubmit(position); + } catch (NumberFormatException e) { + Toast.makeText(activity, R.string.error_invalid_position, Toast.LENGTH_SHORT).show(); + } + } + } + + /** + * Parses the given string as an integer. This returns 0 if the given string is empty. + * + * @param s the string to parse + * @return the integer result + */ + static int parseInteger(String s) { + if (s.length() == 0) { + return 0; + } + return Integer.parseInt(s); + } +} diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackActivity.java b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackActivity.java index 923b8357..1b9e2ff7 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackActivity.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackActivity.java @@ -430,6 +430,7 @@ public abstract class PlaybackActivity extends Activity static final int MENU_MORE_ARTIST = 23; static final int MENU_MORE_GENRE = 24; static final int MENU_MORE_FOLDER = 25; + static final int MENU_JUMP_TO_TIME = 26; @Override public boolean onCreateOptionsMenu(Menu menu) diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java index 03faa1fb..d1c48484 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java @@ -1737,7 +1737,19 @@ public final class PlaybackService extends Service if (!mMediaPlayerInitialized) return; long position = (long)mMediaPlayer.getDuration() * progress / 1000; - mMediaPlayer.seekTo((int)position); + seekToPosition((int) position); + } + + /** + * Seeks to the given position in the current song. + * + * @param msec the offset in milliseconds from the start to seek to + */ + public void seekToPosition(int msec) { + if (!mMediaPlayerInitialized) { + return; + } + mMediaPlayer.seekTo(msec); mHandler.sendEmptyMessage(MSG_BROADCAST_SEEK); } diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/SlidingPlaybackActivity.java b/app/src/main/java/ch/blinkenlights/android/vanilla/SlidingPlaybackActivity.java index 40748733..395e9469 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/SlidingPlaybackActivity.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/SlidingPlaybackActivity.java @@ -34,7 +34,8 @@ import java.net.InetAddress; public class SlidingPlaybackActivity extends PlaybackActivity implements SlidingView.Callback, SeekBar.OnSeekBarChangeListener, - PlaylistDialog.Callback + PlaylistDialog.Callback, + JumpToTimeDialog.OnPositionSubmitListener { /** * Reference to the inflated menu @@ -127,6 +128,7 @@ public class SlidingPlaybackActivity extends PlaybackActivity menu.add(0, MENU_CLEAR_QUEUE, 20, R.string.dequeue_rest); menu.add(0, MENU_EMPTY_QUEUE, 20, R.string.empty_the_queue); menu.add(0, MENU_SAVE_QUEUE, 20, R.string.save_as_playlist); + menu.add(0, MENU_JUMP_TO_TIME, 20, R.string.jump_to_time); // This should only be required on ICS. onSlideExpansionChanged(SlidingView.EXPANSION_PARTIAL); return true; @@ -147,6 +149,9 @@ public class SlidingPlaybackActivity extends PlaybackActivity PlaylistDialog dialog = PlaylistDialog.newInstance(this, null, null); dialog.show(getFragmentManager(), "PlaylistDialog"); break; + case MENU_JUMP_TO_TIME: + JumpToTimeDialog.show(getFragmentManager()); + break; default: return super.onOptionsItemSelected(item); } @@ -336,4 +341,9 @@ public class SlidingPlaybackActivity extends PlaybackActivity } } + @Override + public void onPositionSubmit(int position) { + PlaybackService.get(this).seekToPosition(position); + updateElapsedTime(); + } } diff --git a/app/src/main/res/layout/duration_input.xml b/app/src/main/res/layout/duration_input.xml new file mode 100644 index 00000000..d807a829 --- /dev/null +++ b/app/src/main/res/layout/duration_input.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/translatable.xml b/app/src/main/res/values/translatable.xml index a001fe73..f38a2b77 100644 --- a/app/src/main/res/values/translatable.xml +++ b/app/src/main/res/values/translatable.xml @@ -329,6 +329,13 @@ THE SOFTWARE. Enqueue artist Enqueue genre + Jump to Time + : + HH + MM + SS + Invalid position + Filebrowser home Filebrowser starts at this directory Select