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
This commit is contained in:
Toby Hsieh 2018-11-15 09:28:00 -08:00 committed by Adrian Ulrich
parent c112925890
commit d00b3e4769
6 changed files with 226 additions and 2 deletions

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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)

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <https://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<EditText
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="2"
android:hint="@string/hour_hint" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_separator"/>
<EditText
android:id="@+id/minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="2"
android:hint="@string/minute_hint" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_separator"/>
<EditText
android:id="@+id/seconds"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="2"
android:hint="@string/second_hint" />
</LinearLayout>

View File

@ -329,6 +329,13 @@ THE SOFTWARE.
<string name="preferences_action_enqueue_current_artist">Enqueue artist</string>
<string name="preferences_action_enqueue_current_genre">Enqueue genre</string>
<string name="jump_to_time">Jump to Time</string>
<string name="time_separator">:</string>
<string name="hour_hint">HH</string>
<string name="minute_hint">MM</string>
<string name="second_hint">SS</string>
<string name="error_invalid_position">Invalid position</string>
<string name="filebrowser_start">Filebrowser home</string>
<string name="customize_filebrowser_start">Filebrowser starts at this directory</string>
<string name="select">Select</string>