Add context menu for song/track details dialog (#849)

* Add context menu for song/track details dialog

This addresses #506

* Add GPL 3 boilerplate to files

* Refactor track and disc number string for song details
This commit is contained in:
Toby Hsieh 2018-10-16 23:24:53 -07:00 committed by Adrian Ulrich
parent 7339681907
commit 71acc1782d
12 changed files with 329 additions and 115 deletions

View File

@ -259,6 +259,21 @@ public class MediaMetadataExtractor extends HashMap<String, ArrayList<String>> {
return result;
}
/**
* @return a human-friendly description of the mime type and bit rate
*/
public String getFormat() {
StringBuilder sb = new StringBuilder(18);
sb.append(decodeMimeType(getFirst(MIME_TYPE)));
String bitrate = getFirst(BITRATE);
if (bitrate != null && bitrate.length() > 3) {
sb.append(' ')
.append(bitrate.substring(0, bitrate.length() - 3))
.append("kbps");
}
return sb.toString();
}
/**
* Returns true if this file contains any (interesting) data
* @return true if file is considered to be media data
@ -460,4 +475,24 @@ public class MediaMetadataExtractor extends HashMap<String, ArrayList<String>> {
}
}
/**
* Decode the given MIME type into a more human-friendly description.
*
* @return a human-friendly description of the MIME type
*/
private static String decodeMimeType(String mime)
{
if ("audio/mpeg".equals(mime)) {
return "MP3";
} else if ("audio/mp4".equals(mime)) {
return "AAC";
} else if ("audio/vorbis".equals(mime)) {
return "Ogg Vorbis";
} else if ("application/ogg".equals(mime)) {
return "Ogg Vorbis";
} else if ("audio/flac".equals(mime)) {
return "FLAC";
}
return mime;
}
}

View File

@ -533,25 +533,12 @@ public class FullPlaybackActivity extends SlidingPlaybackActivity
MediaMetadataExtractor data = new MediaMetadataExtractor(song.path);
mGenre = data.getFirst(MediaMetadataExtractor.GENRE);
mTrack = data.getFirst(MediaMetadataExtractor.TRACK_NUMBER);
mTrack = song.getTrackAndDiscNumber();
mComposer = data.getFirst(MediaMetadataExtractor.COMPOSER);
mYear = data.getFirst(MediaMetadataExtractor.YEAR);
mPath = song.path;
mTrack = String.format("%d", song.trackNumber);
if (song.discNumber > 0) {
mTrack += String.format(" (%d💿)", song.discNumber);
}
StringBuilder sb = new StringBuilder(12);
sb.append(decodeMimeType(data.getFirst(MediaMetadataExtractor.MIME_TYPE)));
String bitrate = data.getFirst(MediaMetadataExtractor.BITRATE);
if (bitrate != null && bitrate.length() > 3) {
sb.append(' ');
sb.append(bitrate.substring(0, bitrate.length() - 3));
sb.append("kbps");
}
mFormat = sb.toString();
mFormat = data.getFormat();
BastpUtil.GainValues rg = PlaybackService.get(this).getReplayGainValues(song.path);
mReplayGain = String.format("base=%.2f, track=%.2f, album=%.2f", rg.base, rg.track, rg.album);
@ -560,25 +547,6 @@ public class FullPlaybackActivity extends SlidingPlaybackActivity
mUiHandler.sendEmptyMessage(MSG_COMMIT_INFO);
}
/**
* Decode the given mime type into a more human-friendly description.
*/
private static String decodeMimeType(String mime)
{
if ("audio/mpeg".equals(mime)) {
return "MP3";
} else if ("audio/mp4".equals(mime)) {
return "AAC";
} else if ("audio/vorbis".equals(mime)) {
return "Ogg Vorbis";
} else if ("application/ogg".equals(mime)) {
return "Ogg Vorbis";
} else if ("audio/flac".equals(mime)) {
return "FLAC";
}
return mime;
}
/**
* Save the hidden_controls preference to storage.
*/

View File

@ -615,6 +615,7 @@ public class LibraryActivity
private static final int CTX_MENU_MORE_FROM_ARTIST = 9;
private static final int CTX_MENU_OPEN_EXTERNAL = 10;
private static final int CTX_MENU_PLUGINS = 11;
private static final int CTX_MENU_SHOW_DETAILS = 12;
/**
* Creates a context menu for an adapter row.
@ -647,6 +648,9 @@ public class LibraryActivity
} else if (rowData.getBooleanExtra(LibraryAdapter.DATA_EXPANDABLE, false)) {
menu.add(0, CTX_MENU_EXPAND, 0, R.string.expand).setIntent(rowData);
}
if (type == MediaUtils.TYPE_SONG) {
menu.add(0, CTX_MENU_SHOW_DETAILS, 0, R.string.details).setIntent(rowData);
}
if (type == MediaUtils.TYPE_ALBUM || type == MediaUtils.TYPE_SONG)
menu.add(0, CTX_MENU_MORE_FROM_ARTIST, 0, R.string.more_from_artist).setIntent(rowData);
if (type == MediaUtils.TYPE_SONG) {
@ -734,6 +738,10 @@ public class LibraryActivity
FileUtils.dispatchIntent(this, intent);
break;
}
case CTX_MENU_SHOW_DETAILS:
long songId = intent.getLongExtra(LibraryAdapter.DATA_ID, -1);
TrackDetailsDialog.show(getFragmentManager(), songId);
break;
case CTX_MENU_PLUGINS: {
queryPluginsForIntent(intent);
break;

View File

@ -190,6 +190,7 @@ public class PlaylistActivity extends Activity
private static final int MENU_ENQUEUE_ALL = LibraryActivity.ACTION_ENQUEUE_ALL;
private static final int MENU_ENQUEUE_AS_NEXT = LibraryActivity.ACTION_ENQUEUE_AS_NEXT;
private static final int MENU_REMOVE = -1;
private static final int MENU_SHOW_DETAILS = -2;
@Override
public void onCreateContextMenu(ContextMenu menu, View listView, ContextMenu.ContextMenuInfo absInfo)
@ -205,6 +206,7 @@ public class PlaylistActivity extends Activity
menu.add(0, MENU_ENQUEUE_AS_NEXT, 0, R.string.enqueue_as_next).setIntent(intent);
menu.add(0, MENU_ENQUEUE, 0, R.string.enqueue).setIntent(intent);
menu.add(0, MENU_ENQUEUE_ALL, 0, R.string.enqueue_all).setIntent(intent);
menu.add(0, MENU_SHOW_DETAILS, 0, R.string.details).setIntent(intent);
menu.add(0, MENU_REMOVE, 0, R.string.remove).setIntent(intent);
}
@ -217,6 +219,9 @@ public class PlaylistActivity extends Activity
if (itemId == MENU_REMOVE) {
mAdapter.removeItem(pos - mListView.getHeaderViewsCount());
} else if (itemId == MENU_SHOW_DETAILS) {
long songId = intent.getLongExtra("audioId", -1);
TrackDetailsDialog.show(getFragmentManager(), songId);
} else {
performAction(itemId, pos, intent.getLongExtra("audioId", -1));
}

View File

@ -88,6 +88,7 @@ public class ShowQueueFragment extends Fragment
private final static int CTX_MENU_ENQUEUE_ARTIST = 102;
private final static int CTX_MENU_ENQUEUE_GENRE = 103;
private final static int CTX_MENU_REMOVE = 104;
private final static int CTX_MENU_SHOW_DETAILS = 105;
/**
* Called by Android on long press. Builds the long press context menu.
@ -107,6 +108,7 @@ public class ShowQueueFragment extends Fragment
menu.add(0, CTX_MENU_ENQUEUE_ARTIST, 0, R.string.enqueue_current_artist).setIntent(intent).setOnMenuItemClickListener(this);
menu.add(0, CTX_MENU_ENQUEUE_GENRE, 0, R.string.enqueue_current_genre).setIntent(intent).setOnMenuItemClickListener(this);
menu.addSubMenu(0, SlidingPlaybackActivity.CTX_MENU_ADD_TO_PLAYLIST, 0, R.string.add_to_playlist).getItem().setIntent(intent); // handled by fragment parent
menu.add(0, CTX_MENU_SHOW_DETAILS, 0, R.string.details).setIntent(intent).setOnMenuItemClickListener(this);
menu.add(0, CTX_MENU_REMOVE, 0, R.string.remove).setIntent(intent).setOnMenuItemClickListener(this);
}
@ -118,7 +120,6 @@ public class ShowQueueFragment extends Fragment
@Override
public boolean onMenuItemClick(MenuItem item) {
Intent intent = item.getIntent();
int itemId = item.getItemId();
int pos = intent.getIntExtra("position", -1);
PlaybackService service = playbackService();
@ -136,6 +137,9 @@ public class ShowQueueFragment extends Fragment
case CTX_MENU_ENQUEUE_GENRE:
service.enqueueFromSong(song, MediaUtils.TYPE_GENRE);
break;
case CTX_MENU_SHOW_DETAILS:
TrackDetailsDialog.show(getFragmentManager(), song.id);
break;
case CTX_MENU_REMOVE:
remove(pos);
break;

View File

@ -203,6 +203,17 @@ public class Song implements Comparable<Song> {
return song.id;
}
/**
* @return track and disc number of this song within its album
*/
public String getTrackAndDiscNumber() {
String result = Integer.toString(trackNumber);
if (discNumber > 0) {
result += String.format(" (%d💿)", discNumber);
}
return result;
}
/**
* Query the large album art for this song.
*

View File

@ -0,0 +1,123 @@
/*
* 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.DialogFragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import ch.blinkenlights.android.medialibrary.MediaMetadataExtractor;
/**
* A dialog to show details about a track/song
*/
public class TrackDetailsDialog extends DialogFragment {
private static String SONG_ID = "song_id";
private HandlerThread mHandlerThread;
public TrackDetailsDialog() {
}
/**
* Creates a new dialog to show details about the given song
*
* @param songId ID of song this dialog is for
* @return a new dialog
*/
public static TrackDetailsDialog newInstance(long songId) {
TrackDetailsDialog dialog = new TrackDetailsDialog();
Bundle args = new Bundle();
args.putLong(SONG_ID, songId);
dialog.setArguments(args);
return dialog;
}
/**
* Creates and shows a dialog containing details about the given song
*
* @param manager the FragmentManager to add the newly created dialog to
* @param songId ID of song to show dialog for
*/
public static void show(FragmentManager manager, long songId) {
newInstance(songId).show(manager, "TrackDetailsDialog");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandlerThread = new HandlerThread(getClass().getName());
mHandlerThread.start();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_track_details, container);
final TextView titleView = view.findViewById(R.id.title);
final TextView artistView = view.findViewById(R.id.artist);
final TextView albumView = view.findViewById(R.id.album);
final TextView genreView = view.findViewById(R.id.genre);
final TextView trackView = view.findViewById(R.id.track);
final TextView yearView = view.findViewById(R.id.year);
final TextView composerView = view.findViewById(R.id.composer);
final TextView pathView = view.findViewById(R.id.path);
final TextView formatView = view.findViewById(R.id.format);
final long songId = getArguments().getLong(SONG_ID);
Handler handler = new Handler(mHandlerThread.getLooper());
handler.post(new Runnable() {
@Override
public void run() {
final Song song = MediaUtils.getSongByTypeId(getActivity(), MediaUtils.TYPE_SONG, songId);
if (song == null) {
return;
}
final MediaMetadataExtractor metadata = new MediaMetadataExtractor(song.path);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
titleView.setText(song.title);
artistView.setText(song.artist);
albumView.setText(song.album);
genreView.setText(metadata.getFirst(MediaMetadataExtractor.GENRE));
trackView.setText(song.getTrackAndDiscNumber());
yearView.setText(metadata.getFirst(MediaMetadataExtractor.YEAR));
composerView.setText(metadata.getFirst(MediaMetadataExtractor.COMPOSER));
pathView.setText(song.path);
formatView.setText(metadata.getFormat());
}
});
}
});
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
}
}

View File

@ -0,0 +1,27 @@
<?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/>.
-->
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/track_details_dialog_padding"
android:stretchColumns="1"
android:shrinkColumns="1">
<include layout="@layout/track_details" />
</TableLayout>

View File

@ -37,86 +37,9 @@ THE SOFTWARE.
android:background="?overlay_background_color"
android:paddingLeft="5dip"
android:paddingRight="5dip">
<TableRow>
<TextView
android:text="@string/_title"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView
android:id="@+id/title"
android:textColor="?overlay_foreground_color"
android:textStyle="bold" />
<TextView
android:id="@+id/queue_pos"
android:singleLine="true"
android:gravity="right"
android:layout_gravity="top" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_artist"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/artist" android:textColor="?overlay_foreground_color"/>
</TableRow>
<TableRow>
<TextView
android:text="@string/_album"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/album" android:textColor="?overlay_foreground_color"/>
</TableRow>
<TableRow>
<TextView
android:text="@string/_genre"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/genre" android:textColor="?overlay_foreground_color"/>
</TableRow>
<TableRow>
<TextView
android:text="@string/_track"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/track" android:textColor="?overlay_foreground_color"/>
</TableRow>
<TableRow>
<TextView
android:text="@string/_year"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/year" android:textColor="?overlay_foreground_color"/>
</TableRow>
<TableRow>
<TextView
android:text="@string/_composer"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/composer" android:textColor="?overlay_foreground_color"/>
</TableRow>
<TableRow>
<TextView
android:text="@string/_path"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/path" android:textColor="?overlay_foreground_color"/>
</TableRow>
<TableRow>
<TextView
android:text="@string/_format"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/format" android:textColor="?overlay_foreground_color"/>
</TableRow>
<include layout="@layout/track_details" />
<TableRow>
<TextView
android:text="@string/_replaygain"
@ -125,6 +48,7 @@ THE SOFTWARE.
android:gravity="right" />
<TextView android:id="@+id/replaygain" android:textColor="?overlay_foreground_color"/>
</TableRow>
<LinearLayout android:id="@+id/controls_top">
<TextView
android:id="@+id/elapsed"

View File

@ -0,0 +1,107 @@
<?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/>.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TableRow>
<TextView
android:text="@string/_title"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView
android:id="@+id/title"
android:textColor="?overlay_foreground_color"
android:textStyle="bold" />
<TextView
android:id="@+id/queue_pos"
android:singleLine="true"
android:gravity="right"
android:layout_gravity="top" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_artist"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/artist" android:textColor="?overlay_foreground_color" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_album"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/album" android:textColor="?overlay_foreground_color" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_genre"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/genre" android:textColor="?overlay_foreground_color" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_track"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/track" android:textColor="?overlay_foreground_color" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_year"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/year" android:textColor="?overlay_foreground_color" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_composer"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/composer" android:textColor="?overlay_foreground_color" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_path"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/path" android:textColor="?overlay_foreground_color" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_format"
android:textColor="?overlay_foreground_color"
android:paddingRight="5dip"
android:gravity="right" />
<TextView android:id="@+id/format" android:textColor="?overlay_foreground_color" />
</TableRow>
</merge>

View File

@ -8,4 +8,5 @@
<dimen name="text_padding">14sp</dimen>
<dimen name="row_normal_height">56sp</dimen>
<dimen name="controls_padding">4dip</dimen>
<dimen name="track_details_dialog_padding">8dp</dimen>
</resources>

View File

@ -145,6 +145,7 @@ THE SOFTWARE.
<string name="play_all">Play all</string>
<string name="enqueue_all">Enqueue all</string>
<string name="all_songs">All tracks</string>
<string name="details">Details</string>
<string name="more_from_artist">More from artist</string>
<string name="more_from_album">More from album</string>