diff --git a/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java b/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java index 680ad7c4..3c444ce0 100644 --- a/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java +++ b/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java @@ -259,6 +259,21 @@ public class MediaMetadataExtractor extends HashMap> { 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> { } } + /** + * 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; + } } diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/FullPlaybackActivity.java b/app/src/main/java/ch/blinkenlights/android/vanilla/FullPlaybackActivity.java index ab05effc..44b78de6 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/FullPlaybackActivity.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/FullPlaybackActivity.java @@ -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. */ diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/LibraryActivity.java b/app/src/main/java/ch/blinkenlights/android/vanilla/LibraryActivity.java index a561b3f2..dd83ad6d 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/LibraryActivity.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/LibraryActivity.java @@ -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; diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistActivity.java b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistActivity.java index 9790e9f7..835909a9 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistActivity.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistActivity.java @@ -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)); } diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/ShowQueueFragment.java b/app/src/main/java/ch/blinkenlights/android/vanilla/ShowQueueFragment.java index 3dc07d70..094065a1 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/ShowQueueFragment.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/ShowQueueFragment.java @@ -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; diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/Song.java b/app/src/main/java/ch/blinkenlights/android/vanilla/Song.java index 092cc143..6f68938b 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/Song.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/Song.java @@ -203,6 +203,17 @@ public class Song implements Comparable { 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. * diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/TrackDetailsDialog.java b/app/src/main/java/ch/blinkenlights/android/vanilla/TrackDetailsDialog.java new file mode 100644 index 00000000..57c8bb74 --- /dev/null +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/TrackDetailsDialog.java @@ -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 . + */ + +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(); + } +} diff --git a/app/src/main/res/layout/fragment_track_details.xml b/app/src/main/res/layout/fragment_track_details.xml new file mode 100644 index 00000000..713d71cf --- /dev/null +++ b/app/src/main/res/layout/fragment_track_details.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/app/src/main/res/layout/full_playback_alt.xml b/app/src/main/res/layout/full_playback_alt.xml index ed9bd194..fdf672af 100644 --- a/app/src/main/res/layout/full_playback_alt.xml +++ b/app/src/main/res/layout/full_playback_alt.xml @@ -37,86 +37,9 @@ THE SOFTWARE. android:background="?overlay_background_color" android:paddingLeft="5dip" android:paddingRight="5dip"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 1f87c8b2..f75c1f17 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -8,4 +8,5 @@ 14sp 56sp 4dip + 8dp diff --git a/app/src/main/res/values/translatable.xml b/app/src/main/res/values/translatable.xml index b8778c66..3f9805cd 100644 --- a/app/src/main/res/values/translatable.xml +++ b/app/src/main/res/values/translatable.xml @@ -145,6 +145,7 @@ THE SOFTWARE. Play all Enqueue all All tracks + Details More from artist More from album