diff options
Diffstat (limited to 'app/src/main')
49 files changed, 1024 insertions, 160 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 999fb150..65305cbd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,11 +8,11 @@ android:label="Tests" /> <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.READ_PHONE_STATE"/> + <uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="22"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.RECORD_AUDIO"/> + <uses-permission android:name="android.permission.RECORD_AUDIO" android:maxSdkVersion="22"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.BLUETOOTH"/> diff --git a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java index ab846ba0..9b14f4f6 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java @@ -32,12 +32,15 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.support.design.widget.NavigationView; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AppCompatDelegate; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.KeyEvent; @@ -77,10 +80,13 @@ import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.DrawableTint; import github.daneren2005.dsub.util.ImageLoader; import github.daneren2005.dsub.util.SilentBackgroundTask; +import github.daneren2005.dsub.util.ThemeUtil; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.view.UpdateView; import github.daneren2005.dsub.util.UserUtil; +import static android.Manifest.*; + public class SubsonicActivity extends AppCompatActivity implements OnItemSelectedListener { private static final String TAG = SubsonicActivity.class.getSimpleName(); private static ImageLoader IMAGE_LOADER; @@ -89,6 +95,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte protected static boolean actionbarColored; private static final int MENU_GROUP_SERVER = 10; private static final int MENU_ITEM_SERVER_BASE = 100; + private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; private final List<Runnable> afterServiceAvailable = new ArrayList<>(); private boolean drawerIdle = true; @@ -117,6 +124,10 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte boolean drawerOpen = false; SharedPreferences.OnSharedPreferenceChangeListener preferencesListener; + static { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO); + } + @Override protected void onCreate(Bundle bundle) { UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); @@ -151,6 +162,9 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte case Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED: setDrawerItemVisible(R.id.drawer_bookmarks, false); break; + case Constants.PREFERENCES_KEY_INTERNET_RADIO_ENABLED: + setDrawerItemVisible(R.id.drawer_internet_radio_stations, false); + break; case Constants.PREFERENCES_KEY_SHARED_ENABLED: setDrawerItemVisible(R.id.drawer_shares, false); break; @@ -165,6 +179,25 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte }; Util.getPreferences(this).registerOnSharedPreferenceChangeListener(preferencesListener); } + + if (ContextCompat.checkSelfPermission(this, permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{ permission.WRITE_EXTERNAL_STORAGE }, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + switch (requestCode) { + case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + } else { + Util.toast(this, R.string.permission_external_storage_failed); + finish(); + } + } + } } @Override @@ -191,7 +224,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte protected void createCustomActionBarView() { actionBarSpinner = (Spinner) getLayoutInflater().inflate(R.layout.actionbar_spinner, null); - if((this instanceof SubsonicFragmentActivity || this instanceof SettingsActivity) && (Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true) || Util.getThemeRes(this) != R.style.Theme_DSub_Light_No_Color)) { + if((this instanceof SubsonicFragmentActivity || this instanceof SettingsActivity) && (Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true) || ThemeUtil.getThemeRes(this) != R.style.Theme_DSub_Light_No_Color)) { actionBarSpinner.setBackgroundDrawable(DrawableTint.getTintedDrawableFromColor(this, R.drawable.abc_spinner_mtrl_am_alpha, android.R.color.white)); } spinnerAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item); @@ -209,7 +242,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte // Make sure to update theme SharedPreferences prefs = Util.getPreferences(this); - if (theme != null && !theme.equals(Util.getTheme(this)) || fullScreen != prefs.getBoolean(Constants.PREFERENCES_KEY_FULL_SCREEN, false) || actionbarColored != prefs.getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) { + if (theme != null && !theme.equals(ThemeUtil.getTheme(this)) || fullScreen != prefs.getBoolean(Constants.PREFERENCES_KEY_FULL_SCREEN, false) || actionbarColored != prefs.getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) { restart(); overridePendingTransition(R.anim.fade_in, R.anim.fade_out); DrawableTint.wipeTintCache(); @@ -281,6 +314,9 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte case R.id.drawer_bookmarks: drawerItemSelected("Bookmark"); return true; + case R.id.drawer_internet_radio_stations: + drawerItemSelected("Internet Radio"); + return true; case R.id.drawer_shares: drawerItemSelected("Share"); return true; @@ -556,6 +592,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte SharedPreferences prefs = Util.getPreferences(this); boolean podcastsEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_PODCASTS_ENABLED, true); boolean bookmarksEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED, true) && !Util.isOffline(this) && ServerInfo.canBookmark(this); + boolean internetRadioEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_INTERNET_RADIO_ENABLED, true) && !Util.isOffline(this) && ServerInfo.canInternetRadio(this); boolean sharedEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_SHARED_ENABLED, true) && !Util.isOffline(this); boolean chatEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_CHAT_ENABLED, true) && !Util.isOffline(this); boolean adminEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_ADMIN_ENABLED, true) && !Util.isOffline(this); @@ -585,6 +622,9 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte if(!bookmarksEnabled) { setDrawerItemVisible(R.id.drawer_bookmarks, false); } + if(!internetRadioEnabled) { + setDrawerItemVisible(R.id.drawer_internet_radio_stations, false); + } if(!sharedEnabled) { setDrawerItemVisible(R.id.drawer_shares, false); } @@ -897,14 +937,14 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte } private void applyTheme() { - theme = Util.getTheme(this); + theme = ThemeUtil.getTheme(this); if(theme != null && theme.indexOf("fullscreen") != -1) { theme = theme.substring(0, theme.indexOf("_fullscreen")); - Util.setTheme(this, theme); + ThemeUtil.setTheme(this, theme); } - Util.applyTheme(this, theme); + ThemeUtil.applyTheme(this, theme); actionbarColored = Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true); } private void applyFullscreen() { @@ -1161,6 +1201,8 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte return R.id.drawer_podcasts; case "Bookmark": return R.id.drawer_bookmarks; + case "Internet Radio": + return R.id.drawer_internet_radio_stations; case "Share": return R.id.drawer_shares; case "Chat": diff --git a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java index ca6dd168..c7190046 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java @@ -66,6 +66,7 @@ import github.daneren2005.dsub.fragments.SearchFragment; import github.daneren2005.dsub.fragments.SelectArtistFragment; import github.daneren2005.dsub.fragments.SelectBookmarkFragment; import github.daneren2005.dsub.fragments.SelectDirectoryFragment; +import github.daneren2005.dsub.fragments.SelectInternetRadioStationFragment; import github.daneren2005.dsub.fragments.SelectPlaylistFragment; import github.daneren2005.dsub.fragments.SelectPodcastsFragment; import github.daneren2005.dsub.fragments.SelectShareFragment; @@ -662,6 +663,8 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo return new SelectPodcastsFragment(); } else if("Bookmark".equals(fragmentType)) { return new SelectBookmarkFragment(); + } else if("Internet Radio".equals(fragmentType)) { + return new SelectInternetRadioStationFragment(); } else if("Share".equals(fragmentType)) { return new SelectShareFragment(); } else if("Admin".equals(fragmentType)) { @@ -922,7 +925,13 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo if (currentPlaying != null) { song = currentPlaying.getSong(); trackView.setText(song.getTitle()); - artistView.setText(song.getArtist()); + + if(song.getArtist() != null) { + artistView.setVisibility(View.VISIBLE); + artistView.setText(song.getArtist()); + } else { + artistView.setVisibility(View.GONE); + } } else { trackView.setText(R.string.main_title); artistView.setText(R.string.main_artist); @@ -939,18 +948,25 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo getImageLoader().loadImage(coverArtView, song, false, height, false); } - if(currentPlaying != null && currentPlaying.getSong() != null && (currentPlaying.getSong().isPodcast() || currentPlaying.getSong().isAudioBook())) { + if(getDownloadService().isCurrentPlayingSingle()) { previousButton.setVisibility(View.GONE); nextButton.setVisibility(View.GONE); - - rewindButton.setVisibility(View.VISIBLE); - fastforwardButton.setVisibility(View.VISIBLE); - } else { - previousButton.setVisibility(View.VISIBLE); - nextButton.setVisibility(View.VISIBLE); - rewindButton.setVisibility(View.GONE); fastforwardButton.setVisibility(View.GONE); + } else { + if (currentPlaying != null && currentPlaying.getSong() != null && (currentPlaying.getSong().isPodcast() || currentPlaying.getSong().isAudioBook())) { + previousButton.setVisibility(View.GONE); + nextButton.setVisibility(View.GONE); + + rewindButton.setVisibility(View.VISIBLE); + fastforwardButton.setVisibility(View.VISIBLE); + } else { + previousButton.setVisibility(View.VISIBLE); + nextButton.setVisibility(View.VISIBLE); + + rewindButton.setVisibility(View.GONE); + fastforwardButton.setVisibility(View.GONE); + } } } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java new file mode 100644 index 00000000..9d47d70c --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java @@ -0,0 +1,56 @@ +/* + This file is part of Subsonic. + Subsonic 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. + Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2016 (C) Scott Jackson +*/ +package github.daneren2005.dsub.adapter; + +import android.content.Context; +import android.view.ViewGroup; + +import java.util.List; + +import github.daneren2005.dsub.domain.InternetRadioStation; +import github.daneren2005.dsub.view.FastScroller; +import github.daneren2005.dsub.view.InternetRadioStationView; +import github.daneren2005.dsub.view.UpdateView; + +public class InternetRadioStationAdapter extends SectionAdapter<InternetRadioStation> implements FastScroller.BubbleTextGetter { + public static int VIEW_TYPE_INTERNET_RADIO_STATION = 1; + + public InternetRadioStationAdapter(Context context, List<InternetRadioStation> stations, OnItemClickedListener listener) { + super(context, stations); + this.onItemClickedListener = listener; + } + + @Override + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new InternetRadioStationView(context)); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, InternetRadioStation station, int viewType) { + holder.getUpdateView().setObject(station); + holder.setItem(station); + } + + @Override + public int getItemViewType(InternetRadioStation station) { + return VIEW_TYPE_INTERNET_RADIO_STATION; + } + + @Override + public String getTextToShowInBubble(int position) { + InternetRadioStation item = getItemForPosition(position); + return item.getTitle(); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java b/app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java new file mode 100644 index 00000000..47d79b99 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java @@ -0,0 +1,43 @@ +/* + This file is part of Subsonic. + + Subsonic 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. + + Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2016 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.domain; + +public class InternetRadioStation extends MusicDirectory.Entry { + private String streamUrl; + private String homePageUrl; + + public InternetRadioStation() {} + + public String getStreamUrl() { + return streamUrl; + } + + public void setStreamUrl(String streamUrl) { + this.streamUrl = streamUrl; + } + + public String getHomePageUrl() { + return homePageUrl; + } + + public void setHomePageUrl(String homePageUrl) { + this.homePageUrl = homePageUrl; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java b/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java index 7f538484..e41a9503 100644 --- a/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java +++ b/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java @@ -24,6 +24,7 @@ import java.io.Serializable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.Util; @@ -215,6 +216,9 @@ public class ServerInfo implements Serializable { public static boolean canBookmark(Context context) { return checkServerVersion(context, "1.9"); } + public static boolean canInternetRadio(Context context) { + return checkServerVersion(context, "1.9"); + } public static boolean canSavePlayQueue(Context context) { return ServerInfo.checkServerVersion(context, "1.12") && (!ServerInfo.isMadsonic(context) || checkServerVersion(context, "2.0")); @@ -231,8 +235,15 @@ public class ServerInfo implements Serializable { return canUseToken(context, Util.getActiveServer(context)); } public static boolean canUseToken(Context context, int instance) { - return false; /*isStockSubsonic(context, instance) && checkServerVersion(context, "1.13", instance) || - isMadsonic(context, instance) && checkServerVersion(context, "2.0", instance);*/ + if(isStockSubsonic(context, instance) && checkServerVersion(context, "1.14", instance)) { + if(Util.getBlockTokenUse(context, instance)) { + return false; + } else { + return true; + } + } else { + return false; + } } public static boolean hasSimilarArtists(Context context) { return !ServerInfo.isMadsonic(context) || ServerInfo.checkServerVersion(context, "2.0"); diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java index 187f0d55..552712f7 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java @@ -47,7 +47,7 @@ public class AdminFragment extends SelectRecyclerFragment<User> { switch (item.getItemId()) { case R.id.menu_add_user: - UserUtil.addNewUser(context, this, objects.get(0)); + UserUtil.addNewUser(context, this, (objects.size() > 0) ? objects.get(0) : null); break; } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java index 7c20dc28..f55dbe1d 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java @@ -400,11 +400,6 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { - DownloadService downloadService = getDownloadService(); - if (downloadService == null) { - return false; - } - float playbackSpeed = 1.0f; switch (menuItem.getItemId()) { case R.id.playback_speed_half: @@ -419,10 +414,12 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis case R.id.playback_speed_tripple: playbackSpeed = 3.0f; break; + case R.id.playback_speed_custom: + setPlaybackSpeed(); + return true; } - downloadService.setPlaybackSpeed(playbackSpeed); - updateTitle(); + setPlaybackSpeed(playbackSpeed); return true; } }); @@ -525,7 +522,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis menu.removeItem(R.id.menu_equalizer); } - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isRemoteEnabled) { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || isRemoteEnabled) { playbackSpeedButton.setVisibility(View.GONE); } else { playbackSpeedButton.setVisibility(View.VISIBLE); @@ -538,6 +535,15 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis mediaRouteButton.setDialogFactory(new CustomMediaRouteDialogFactory()); mediaRouteButton.setRouteSelector(downloadService.getRemoteSelector()); } + + if(downloadService.isCurrentPlayingSingle()) { + if(!Util.isOffline(context)) { + menu.removeItem(R.id.menu_save_playlist); + } + + menu.removeItem(R.id.menu_batch_mode); + menu.removeItem(R.id.menu_remove_played); + } } if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_BATCH_MODE, false)) { @@ -870,6 +876,11 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis } private void setControlsVisible(boolean visible) { + DownloadService downloadService = getDownloadService(); + if(downloadService != null && downloadService.isCurrentPlayingSingle()) { + return; + } + try { long duration = 1700L; FadeOutAnimation.createAndStart(rootView.findViewById(R.id.download_overlay_buttons), !visible, duration); @@ -1245,18 +1256,25 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis this.currentPlaying = currentPlaying; setupSubtitle(currentPlayingIndex); - if(currentPlaying != null && !currentPlaying.isSong()) { + if(getDownloadService().isCurrentPlayingSingle()) { previousButton.setVisibility(View.GONE); nextButton.setVisibility(View.GONE); - - rewindButton.setVisibility(View.VISIBLE); - fastforwardButton.setVisibility(View.VISIBLE); - } else { - previousButton.setVisibility(View.VISIBLE); - nextButton.setVisibility(View.VISIBLE); - rewindButton.setVisibility(View.GONE); fastforwardButton.setVisibility(View.GONE); + } else { + if (currentPlaying != null && !currentPlaying.isSong()) { + previousButton.setVisibility(View.GONE); + nextButton.setVisibility(View.GONE); + + rewindButton.setVisibility(View.VISIBLE); + fastforwardButton.setVisibility(View.VISIBLE); + } else { + previousButton.setVisibility(View.VISIBLE); + nextButton.setVisibility(View.VISIBLE); + + rewindButton.setVisibility(View.GONE); + fastforwardButton.setVisibility(View.GONE); + } } updateTitle(); } @@ -1268,7 +1286,9 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis getImageLoader().loadImage(albumArtImageView, song, true, true); DownloadService downloadService = getDownloadService(); - if(downloadService.isShufflePlayEnabled()) { + if(downloadService.isCurrentPlayingSingle()) { + setSubtitle(null); + } else if(downloadService.isShufflePlayEnabled()) { setSubtitle(context.getResources().getString(R.string.download_playerstate_playing_shuffle)); } else if(downloadService.isArtistRadio()) { setSubtitle(context.getResources().getString(R.string.download_playerstate_playing_artist_radio)); @@ -1317,6 +1337,14 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis } else { setupSubtitle(currentPlayingIndex); } + + if(downloadService.isCurrentPlayingSingle()) { + toggleListButton.setVisibility(View.GONE); + repeatButton.setVisibility(View.GONE); + } else { + toggleListButton.setVisibility(View.VISIBLE); + repeatButton.setVisibility(View.VISIBLE); + } } @Override @@ -1371,11 +1399,16 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis break; default: if(currentPlaying != null) { - String artist = ""; - if(currentPlaying.getSong().getArtist() != null) { - artist = currentPlaying.getSong().getArtist() + " - "; + Entry entry = currentPlaying.getSong(); + if(entry.getAlbum() != null) { + String artist = ""; + if (entry.getArtist() != null) { + artist = currentPlaying.getSong().getArtist() + " - "; + } + statusTextView.setText(artist + entry.getAlbum()); + } else { + statusTextView.setText(null); } - statusTextView.setText(artist + currentPlaying.getSong().getAlbum()); } else { statusTextView.setText(null); } @@ -1485,8 +1518,15 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis stringRes = R.string.download_playback_speed_tripple; } + String playbackSpeedText = null; if(stringRes != -1) { - title += " (" + context.getResources().getString(stringRes) + ")"; + playbackSpeedText = context.getResources().getString(stringRes); + } else if(Math.abs(playbackSpeed - 1.0) > 0.01) { + playbackSpeedText = Float.toString(playbackSpeed) + "x"; + } + + if(playbackSpeedText != null) { + title += " (" + playbackSpeedText + ")"; } setTitle(title); } @@ -1504,4 +1544,64 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis return entries; } + + private void setPlaybackSpeed() { + View dialogView = context.getLayoutInflater().inflate(R.layout.set_playback_speed, null); + + // Setup playbackSpeed label + final TextView playbackSpeedBox = (TextView) dialogView.findViewById(R.id.playback_speed_label); + final SharedPreferences prefs = Util.getPreferences(context); + String playbackSpeedString = prefs.getString(Constants.PREFERENCES_KEY_CUSTOM_PLAYBACK_SPEED, "17"); + int playbackSpeed = Integer.parseInt(playbackSpeedString); + playbackSpeedBox.setText(new Float(playbackSpeed / 10.0).toString()); + + // Setup playbackSpeed slider + final SeekBar playbackSpeedBar = (SeekBar) dialogView.findViewById(R.id.playback_speed_bar); + playbackSpeedBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + playbackSpeedBox.setText(new Float((progress + 5) / 10.0).toString()); + seekBar.setProgress(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + playbackSpeedBar.setProgress(playbackSpeed - 5); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.download_playback_speed_custom) + .setView(dialogView) + .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + int playbackSpeed = playbackSpeedBar.getProgress() + 5; + + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(Constants.PREFERENCES_KEY_CUSTOM_PLAYBACK_SPEED, Integer.toString(playbackSpeed)); + editor.commit(); + + setPlaybackSpeed(new Float(playbackSpeed / 10.0)); + } + }) + .setNegativeButton(R.string.common_cancel, null); + AlertDialog dialog = builder.create(); + dialog.show(); + } + private void setPlaybackSpeed(float playbackSpeed) { + DownloadService downloadService = getDownloadService(); + if (downloadService == null) { + return; + } + + downloadService.setPlaybackSpeed(playbackSpeed); + updateTitle(); + } } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java new file mode 100644 index 00000000..c39e9f61 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java @@ -0,0 +1,157 @@ +/* + This file is part of Subsonic. + + Subsonic 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. + + Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2016 (C) Scott Jackson +*/ +package github.daneren2005.dsub.fragments; + +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.InternetRadioStationAdapter; +import github.daneren2005.dsub.adapter.SectionAdapter; +import github.daneren2005.dsub.domain.InternetRadioStation; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.util.ProgressListener; +import github.daneren2005.dsub.util.TabBackgroundTask; +import github.daneren2005.dsub.util.Util; +import github.daneren2005.dsub.view.UpdateView; + +public class SelectInternetRadioStationFragment extends SelectRecyclerFragment<InternetRadioStation> { + private static final String TAG = SelectInternetRadioStationFragment.class.getSimpleName(); + + @Override + public int getOptionsMenu() { + return R.menu.abstract_top_menu; + } + + @Override + public SectionAdapter<InternetRadioStation> getAdapter(List<InternetRadioStation> objs) { + return new InternetRadioStationAdapter(context, objs, this); + } + + @Override + public List<InternetRadioStation> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception { + return musicService.getInternetRadioStations(refresh, context, listener); + } + + @Override + public int getTitleResource() { + return R.string.button_bar_internet_radio; + } + + @Override + public void onItemClicked(UpdateView<InternetRadioStation> updateView, final InternetRadioStation item) { + new TabBackgroundTask<Void>(this) { + @Override + protected Void doInBackground() throws Throwable { + DownloadService downloadService = getDownloadService(); + if(downloadService == null) { + return null; + } + + getStreamFromPlaylist(item); + downloadService.download(item); + return null; + } + + @Override + protected void done(Void result) { + context.openNowPlaying(); + } + }.execute(); + } + + @Override + public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<InternetRadioStation> updateView, InternetRadioStation item) { + menuInflater.inflate(R.menu.select_internet_radio_context, menu); + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem, UpdateView<InternetRadioStation> updateView, InternetRadioStation item) { + switch (menuItem.getItemId()) { + case R.id.internet_radio_info: + displayInternetRadioStationInfo(item); + break; + } + + return false; + } + + private void getStreamFromPlaylist(InternetRadioStation internetRadioStation) { + if(internetRadioStation.getStreamUrl() != null && (internetRadioStation.getStreamUrl().indexOf(".m3u") != -1 || internetRadioStation.getStreamUrl().indexOf(".pls") != -1)) { + try { + URL url = new URL(internetRadioStation.getStreamUrl()); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + try { + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String line; + while((line = in.readLine()) != null) { + // Not blank line or comment + if(line.length() > 0 && line.indexOf('#') != 0) { + if(internetRadioStation.getStreamUrl().indexOf(".m3u") != -1) { + internetRadioStation.setStreamUrl(line); + break; + } else { + if(line.indexOf("File1=") == 0) { + internetRadioStation.setStreamUrl(line.replace("File1=", "")); + } else if(line.indexOf("Title1=") == 0) { + internetRadioStation.setTitle(line.replace("Title1=", "")); + } + } + } + } + } finally { + connection.disconnect(); + } + } catch (Exception e) { + Log.e(TAG, "Failed to get stream data from playlist"); + } + + } + } + + private void displayInternetRadioStationInfo(final InternetRadioStation station) { + List<Integer> headers = new ArrayList<>(); + List<String> details = new ArrayList<>(); + + headers.add(R.string.details_title); + details.add(station.getTitle()); + + headers.add(R.string.details_home_page); + details.add(station.getHomePageUrl()); + + headers.add(R.string.details_stream_url); + details.add(station.getStreamUrl()); + + Util.showDetailsDialog(context, R.string.details_title_internet_radio_station, headers, details); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java index ad064d01..d5ba25f5 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java @@ -431,6 +431,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared for (ServerSettings ss : serverSettings.values()) { if(!ss.update()) { serversCategory.removePreference(ss.getScreen()); + serverCount--; } } } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java index b7478c8a..818324ed 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java @@ -309,6 +309,12 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR if(entry.getBookmark() == null) { menu.removeItem(R.id.bookmark_menu_delete); } + + + String songPressAction = Util.getSongPressAction(context); + if(!"next".equals(songPressAction) && !"last".equals(songPressAction)) { + menu.setGroupVisible(R.id.hide_play_now, false); + } } } else { if(Util.isOffline(context)) { @@ -430,6 +436,9 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR case R.id.album_menu_share: createShare(songs); break; + case R.id.song_menu_play_now: + playNow(songs); + break; case R.id.song_menu_play_next: getDownloadService().download(songs, false, false, true, false); break; diff --git a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java index 1a17dfb3..00fb4624 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java @@ -39,6 +39,7 @@ import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.ChatMessage; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.PlayerQueue; import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.RemoteStatus; @@ -1218,6 +1219,22 @@ public class CachedMusicService implements MusicService { } @Override + public List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + List<InternetRadioStation> result = null; + + if(!refresh) { + result = FileUtil.deserialize(context, getCacheName(context, "internetRadioStations"), ArrayList.class); + } + + if(result == null) { + result = musicService.getInternetRadioStations(refresh, context, progressListener); + FileUtil.serialize(context, new ArrayList<>(result), getCacheName(context, "internetRadioStations")); + } + + return result; + } + + @Override public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{ return musicService.processOfflineSyncs(context, progressListener); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java index c2007139..2df290cf 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java +++ b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java @@ -41,6 +41,7 @@ import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.EnvironmentVariables; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.util.compat.CastCompat; @@ -465,14 +466,14 @@ public class ChromeCastController extends RemoteController { void launchApplication() { try { - Cast.CastApi.launchApplication(apiClient, CastCompat.APPLICATION_ID, false).setResultCallback(resultCallback); + Cast.CastApi.launchApplication(apiClient, EnvironmentVariables.CAST_APPLICATION_ID, false).setResultCallback(resultCallback); } catch (Exception e) { Log.e(TAG, "Failed to launch application", e); } } void reconnectApplication() { try { - Cast.CastApi.joinApplication(apiClient, CastCompat.APPLICATION_ID, sessionId).setResultCallback(resultCallback); + Cast.CastApi.joinApplication(apiClient, EnvironmentVariables.CAST_APPLICATION_ID, sessionId).setResultCallback(resultCallback); } catch (Exception e) { Log.e(TAG, "Failed to reconnect application", e); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java index e4bab798..d1c594ce 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java @@ -29,6 +29,8 @@ import android.content.Context; import android.net.wifi.WifiManager; import android.os.PowerManager; import android.util.Log; + +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.SilentBackgroundTask; @@ -377,6 +379,18 @@ public class DownloadFile implements BufferFile { } } + public boolean isStream() { + return song != null && song instanceof InternetRadioStation; + } + public String getStream() { + if(song != null && song instanceof InternetRadioStation) { + InternetRadioStation station = (InternetRadioStation) song; + return station.getStreamUrl(); + } else { + return null; + } + } + @Override public String toString() { return "DownloadFile (" + song + ")"; diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java index cec98865..a6bbc327 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java @@ -35,6 +35,7 @@ import github.daneren2005.dsub.activity.SubsonicActivity; import github.daneren2005.dsub.audiofx.AudioEffectsController; import github.daneren2005.dsub.audiofx.EqualizerController; import github.daneren2005.dsub.domain.Bookmark; +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; import github.daneren2005.dsub.domain.PodcastEpisode; @@ -60,6 +61,7 @@ import github.daneren2005.serverproxy.BufferProxy; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Iterator; @@ -123,7 +125,7 @@ public class DownloadService extends Service { private RemoteControlClientBase mRemoteControl; - private final IBinder binder = new SimpleServiceBinder<DownloadService>(this); + private final IBinder binder = new SimpleServiceBinder<>(this); private Looper mediaPlayerLooper; private MediaPlayer mediaPlayer; private MediaPlayer nextMediaPlayer; @@ -382,6 +384,10 @@ public class DownloadService extends Service { handler.postDelayed(r, millis); } + public synchronized void download(InternetRadioStation station) { + clear(); + download(Arrays.asList((MusicDirectory.Entry) station), false, true, false, false); + } public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle) { download(songs, save, autoplay, playNext, shuffle, 0, 0); } @@ -394,7 +400,10 @@ public class DownloadService extends Service { if (songs.isEmpty()) { return; + } else if(isCurrentPlayingSingle()) { + clear(); } + if (playNext) { if (autoplay && getCurrentPlayingIndex() >= 0) { offset = 0; @@ -996,6 +1005,21 @@ public class DownloadService extends Service { public List<DownloadFile> getToDelete() { return toDelete; } + public boolean isCurrentPlayingSingle() { + if(currentPlaying != null && currentPlaying.getSong() instanceof InternetRadioStation) { + return true; + } else { + return false; + } + } + public boolean isCurrentPlayingStream() { + if(currentPlaying != null) { + return currentPlaying.isStream(); + } else { + return false; + } + } + public synchronized List<DownloadFile> getDownloads() { List<DownloadFile> temp = new ArrayList<DownloadFile>(); temp.addAll(downloadList); @@ -1819,7 +1843,7 @@ public class DownloadService extends Service { bufferAndPlay(position, true); } private synchronized void bufferAndPlay(int position, boolean start) { - if(!currentPlaying.isCompleteFileAvailable()) { + if(!currentPlaying.isCompleteFileAvailable() && !currentPlaying.isStream()) { if(Util.isAllowedToDownload(this)) { reset(); @@ -1835,11 +1859,6 @@ public class DownloadService extends Service { private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start) { try { - downloadFile.setPlaying(true); - final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile(); - boolean isPartial = file.equals(downloadFile.getPartialFile()); - downloadFile.updateModificationDate(); - subtractPosition = 0; mediaPlayer.setOnCompletionListener(null); mediaPlayer.setOnPreparedListener(null); @@ -1851,19 +1870,33 @@ public class DownloadService extends Service { } catch(Throwable e) { mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); } - String dataSource = file.getAbsolutePath(); - if(isPartial && !Util.isOffline(this)) { - if (proxy == null) { - proxy = new BufferProxy(this); - proxy.start(); - } - proxy.setBufferFile(downloadFile); - dataSource = proxy.getPrivateAddress(dataSource); + + String dataSource; + boolean isPartial = false; + if(downloadFile.isStream()) { + dataSource = downloadFile.getStream(); Log.i(TAG, "Data Source: " + dataSource); - } else if(proxy != null) { - proxy.stop(); - proxy = null; + } else { + downloadFile.setPlaying(true); + final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile(); + isPartial = file.equals(downloadFile.getPartialFile()); + downloadFile.updateModificationDate(); + + dataSource = file.getAbsolutePath(); + if (isPartial && !Util.isOffline(this)) { + if (proxy == null) { + proxy = new BufferProxy(this); + proxy.start(); + } + proxy.setBufferFile(downloadFile); + dataSource = proxy.getPrivateAddress(dataSource); + Log.i(TAG, "Data Source: " + dataSource); + } else if (proxy != null) { + proxy.stop(); + proxy = null; + } } + mediaPlayer.setDataSource(dataSource); setPlayerState(PREPARING); @@ -2160,13 +2193,16 @@ public class DownloadService extends Service { checkArtistRadio(); } - if (!Util.isNetworkConnected(this, true) || Util.isOffline(this)) { + if (!Util.isAllowedToDownload(this)) { return; } if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) { return; } + if(currentPlaying != null && currentPlaying.isStream()) { + return; + } // Need to download current playing and not casting? if (currentPlaying != null && remoteState == LOCAL && currentPlaying != currentDownloading && !currentPlaying.isWorkDone()) { diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java index f8272356..1c80b622 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java @@ -36,6 +36,8 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; import android.view.KeyEvent; + +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerQueue; import github.daneren2005.dsub.domain.PlayerState; @@ -155,6 +157,8 @@ public class DownloadServiceLifecycleSupport { // Pause temporarily on incoming phone calls. phoneStateListener = new MyPhoneStateListener(); + + // Android 6.0 removes requirement for android.Manifest.permission.READ_PHONE_STATE; TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE); telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); @@ -308,7 +312,7 @@ public class DownloadServiceLifecycleSupport { FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER); // If we are on Subsonic 5.2+, save play queue - if(serializeRemote && ServerInfo.canSavePlayQueue(downloadService) && !Util.isOffline(downloadService) && state.songs.size() > 0) { + if(serializeRemote && ServerInfo.canSavePlayQueue(downloadService) && !Util.isOffline(downloadService) && state.songs.size() > 0 && !(state.songs.get(0) instanceof InternetRadioStation)) { // Cancel any currently running tasks if(currentSavePlayQueueTask != null) { currentSavePlayQueueTask.cancel(); diff --git a/app/src/main/java/github/daneren2005/dsub/service/MusicService.java b/app/src/main/java/github/daneren2005/dsub/service/MusicService.java index 22f154c4..1e275108 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/MusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/MusicService.java @@ -30,6 +30,7 @@ import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.ChatMessage; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.PlayerQueue; import github.daneren2005.dsub.domain.RemoteStatus; import github.daneren2005.dsub.domain.Lyrics; @@ -194,6 +195,8 @@ public interface MusicService { void savePlayQueue(List<MusicDirectory.Entry> songs, MusicDirectory.Entry currentPlaying, int position, Context context, ProgressListener progressListener) throws Exception; PlayerQueue getPlayQueue(Context context, ProgressListener progressListener) throws Exception; + + List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception; int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception; diff --git a/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java index e004101d..2c439ec4 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java @@ -41,6 +41,7 @@ import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.ChatMessage; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.MusicDirectory.Entry; import github.daneren2005.dsub.domain.PlayerQueue; import github.daneren2005.dsub.domain.PodcastEpisode; @@ -890,6 +891,11 @@ public class OfflineMusicService implements MusicService { } @Override + public List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException(ERRORMSG); + } + + @Override public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{ throw new OfflineException(ERRORMSG); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index 1f9e5494..6ccf562c 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -80,6 +80,7 @@ import github.daneren2005.dsub.service.parser.ChatMessageParser; import github.daneren2005.dsub.service.parser.ErrorParser; import github.daneren2005.dsub.service.parser.GenreParser; import github.daneren2005.dsub.service.parser.IndexesParser; +import github.daneren2005.dsub.service.parser.InternetRadioStationParser; import github.daneren2005.dsub.service.parser.JukeboxStatusParser; import github.daneren2005.dsub.service.parser.LicenseParser; import github.daneren2005.dsub.service.parser.LyricsParser; @@ -1765,6 +1766,18 @@ public class RESTMusicService implements MusicService { } @Override + public List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.9", null); + + Reader reader = getReader(context, progressListener, "getInternetRadioStations", null); + try { + return new InternetRadioStationParser(context, getInstance(context)).parse(reader, progressListener); + } finally { + Util.close(reader); + } + } + + @Override public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{ return processOfflineScrobbles(context, progressListener) + processOfflineStars(context, progressListener); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java b/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java index 168a7777..c7ad639e 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java +++ b/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java @@ -3,6 +3,7 @@ package github.daneren2005.dsub.service; import android.content.Context; import android.util.Log; +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.util.SilentBackgroundTask; @@ -69,7 +70,7 @@ public class Scrobbler { return null; } // Ignore podcasts - else if(song.getSong() instanceof PodcastEpisode) { + else if(song.getSong() instanceof PodcastEpisode || song.getSong() instanceof InternetRadioStation) { return null; } diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java index d6e1a002..d4c090c1 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java +++ b/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java @@ -18,6 +18,7 @@ */ package github.daneren2005.dsub.service.parser; +import java.io.IOException; import java.io.Reader; import org.xmlpull.v1.XmlPullParser; @@ -72,6 +73,11 @@ public abstract class AbstractParser { case 40: message = context.getResources().getString(R.string.parser_not_authenticated); break; + case 41: + Util.setBlockTokenUse(context, instance, true); + + // Throw IOException so RESTMusicService knows to retry + throw new IOException(); case 50: message = context.getResources().getString(R.string.parser_not_authorized); break; diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java new file mode 100644 index 00000000..77d7bc4a --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java @@ -0,0 +1,63 @@ +/* + This file is part of Subsonic. + + Subsonic 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. + + Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2016 (C) Scott Jackson +*/ +package github.daneren2005.dsub.service.parser; + +import android.content.Context; +import github.daneren2005.dsub.domain.InternetRadioStation; +import github.daneren2005.dsub.util.ProgressListener; +import org.xmlpull.v1.XmlPullParser; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +public class InternetRadioStationParser extends ErrorParser { + public InternetRadioStationParser(Context context, int instance) { + super(context, instance); + } + + public List<InternetRadioStation> parse(Reader reader, ProgressListener progressListener) throws Exception { + init(reader); + + List<InternetRadioStation> result = new ArrayList<>(); + int eventType; + do { + eventType = nextParseEvent(); + if (eventType == XmlPullParser.START_TAG) { + String name = getElementName(); + if ("internetRadioStation".equals(name)) { + InternetRadioStation station = new InternetRadioStation(); + + station.setId(get("id")); + station.setTitle(get("name")); + station.setStreamUrl(get("streamUrl")); + station.setHomePageUrl(get("homePageUrl")); + + result.add(station); + } else if ("error".equals(name)) { + handleError(); + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + + validate(); + return result; + } + +}
\ No newline at end of file diff --git a/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java b/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java index 31e83200..2b0c6279 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java +++ b/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.xmlpull.v1.XmlPullParserException; @@ -54,6 +55,7 @@ public abstract class BackgroundTask<T> implements ProgressListener { private static final Collection<Thread> threads = Collections.synchronizedCollection(new ArrayList<Thread>()); protected static final BlockingQueue<BackgroundTask.Task> queue = new LinkedBlockingQueue<BackgroundTask.Task>(10); private static Handler handler = null; + private static AtomicInteger currentlyRunning = new AtomicInteger(0); static { try { handler = new Handler(Looper.getMainLooper()); @@ -71,6 +73,11 @@ public abstract class BackgroundTask<T> implements ProgressListener { threads.add(thread); thread.start(); } + } else if(currentlyRunning.get() >= threads.size()) { + Log.w(TAG, "Emergency add new thread: " + (threads.size() + 1)); + Thread thread = new Thread(new TaskRunnable(), String.format("BackgroundTask_%d", threads.size())); + threads.add(thread); + thread.start(); } if(handler == null) { try { @@ -304,22 +311,30 @@ public abstract class BackgroundTask<T> implements ProgressListener { @Override public void run() { Looper.prepare(); + final Thread currentThread = Thread.currentThread(); while(running) { try { Task task = queue.take(); + currentlyRunning.incrementAndGet(); task.execute(); } catch(InterruptedException stop) { Log.e(TAG, "Thread died"); running = false; - threads.remove(Thread.currentThread()); } catch(Throwable t) { Log.e(TAG, "Unexpected crash in BackgroundTask thread", t); + running = false; } + + currentlyRunning.decrementAndGet(); + } + + if(threads.contains(currentThread)) { + threads.remove(currentThread); } } } - public static interface OnCancelListener { + public interface OnCancelListener { void onCancel(); } } diff --git a/app/src/main/java/github/daneren2005/dsub/util/Constants.java b/app/src/main/java/github/daneren2005/dsub/util/Constants.java index 4109ea67..2d4301d9 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Constants.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Constants.java @@ -134,6 +134,7 @@ public final class Constants { public static final String PREFERENCES_KEY_HIDE_WIDGET = "hideWidget"; public static final String PREFERENCES_KEY_PODCASTS_ENABLED = "podcastsEnabled"; public static final String PREFERENCES_KEY_BOOKMARKS_ENABLED = "bookmarksEnabled"; + public static final String PREFERENCES_KEY_INTERNET_RADIO_ENABLED = "internetRadioEnabled"; public static final String PREFERENCES_KEY_CUSTOM_SORT_ENABLED = "customSortEnabled"; public static final String PREFERENCES_KEY_MENU_PLAY_NOW = "showPlayNow"; public static final String PREFERENCES_KEY_MENU_PLAY_SHUFFLED = "showPlayShuffled"; @@ -176,6 +177,7 @@ public final class Constants { public static final String PREFERENCES_KEY_HEADS_UP_NOTIFICATION = "headsUpNotification"; public static final String PREFERENCES_KEY_CAST_CACHE = "castCache"; public static final String PREFERENCES_KEY_PLAYBACK_SPEED = "playbackSpeed"; + public static final String PREFERENCES_KEY_CUSTOM_PLAYBACK_SPEED = "customPlaybackSpeed"; public static final String OFFLINE_SCROBBLE_COUNT = "scrobbleCount"; public static final String OFFLINE_SCROBBLE_ID = "scrobbleID"; @@ -188,6 +190,7 @@ public final class Constants { public static final String CACHE_KEY_IGNORE = "ignoreArticles"; public static final String CACHE_AUDIO_SESSION_ID = "audioSessionId"; + public static final String CACHE_BLOCK_TOKEN_USE = "blockTokenUse"; public static final String MAIN_BACK_STACK = "backStackIds"; public static final String MAIN_BACK_STACK_SIZE = "backStackIdsSize"; diff --git a/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java b/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java index d8046d1b..710d5232 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java +++ b/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java @@ -17,4 +17,5 @@ package github.daneren2005.dsub.util; public final class EnvironmentVariables { public static final String PASTEBIN_DEV_KEY = ""; + public static final String CAST_APPLICATION_ID = ""; } diff --git a/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java b/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java index a85141df..2321e69e 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java +++ b/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java @@ -43,6 +43,7 @@ import java.lang.ref.WeakReference; import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.ArtistInfo; +import github.daneren2005.dsub.domain.InternetRadioStation; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.PodcastChannel; @@ -221,8 +222,11 @@ public class ImageLoader { return loadImage(view, entry, large, size, crossfade); } public SilentBackgroundTask loadImage(View view, MusicDirectory.Entry entry, boolean large, int size, boolean crossfade) { - // TODO: If we know this a artist, try to load artist info instead - if(entry != null && !entry.isAlbum() && ServerInfo.checkServerVersion(context, "1.11") && !Util.isOffline(context)) { + if(entry != null && entry instanceof InternetRadioStation) { + // Continue on and load a null bitmap + } + // If we know this a artist, try to load artist info instead + else if(entry != null && !entry.isAlbum() && ServerInfo.checkServerVersion(context, "1.11") && !Util.isOffline(context)) { SilentBackgroundTask task = new ArtistImageTask(view.getContext(), entry, size, imageSizeLarge, large, view, crossfade); task.execute(); return task; diff --git a/app/src/main/java/github/daneren2005/dsub/util/Notifications.java b/app/src/main/java/github/daneren2005/dsub/util/Notifications.java index 2948844b..d6a92b07 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Notifications.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Notifications.java @@ -67,9 +67,10 @@ public final class Notifications { notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; } boolean remote = downloadService.isRemoteEnabled(); + boolean isSingle = downloadService.isCurrentPlayingSingle(); if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){ RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded); - setupViews(expandedContentView ,context, song, true, playing, remote); + setupViews(expandedContentView ,context, song, true, playing, remote, isSingle); notification.bigContentView = expandedContentView; notification.priority = Notification.PRIORITY_HIGH; } @@ -82,7 +83,7 @@ public final class Notifications { } RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification); - setupViews(smallContentView, context, song, false, playing, remote); + setupViews(smallContentView, context, song, false, playing, remote, isSingle); notification.contentView = smallContentView; Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class); @@ -98,7 +99,12 @@ public final class Notifications { public void run() { downloadService.stopForeground(true); showDownloadingNotification(context, downloadService, handler, downloadService.getCurrentDownloading(), downloadService.getBackgroundDownloads().size()); - downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification); + + try { + downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification); + } catch(Exception e) { + Log.e(TAG, "Failed to start notifications after stopping foreground download"); + } } }); } else { @@ -106,13 +112,22 @@ public final class Notifications { @Override public void run() { if (playing) { - downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification); + try { + downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification); + } catch(Exception e) { + Log.e(TAG, "Failed to start notifications while playing"); + } } else { playShowing = false; persistentPlayingShowing = true; NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); downloadService.stopForeground(false); - notificationManager.notify(NOTIFICATION_ID_PLAYING, notification); + + try { + notificationManager.notify(NOTIFICATION_ID_PLAYING, notification); + } catch(Exception e) { + Log.e(TAG, "Failed to start notifications while paused"); + } } } }); @@ -122,7 +137,7 @@ public final class Notifications { DSubWidgetProvider.notifyInstances(context, downloadService, playing); } - private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean expanded, boolean playing, boolean remote) { + private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean expanded, boolean playing, boolean remote, boolean isSingleFile) { boolean isLongFile = song.isAudioBook() || song.isPodcast(); // Use the same text for the ticker and the expanded notification @@ -209,6 +224,27 @@ public final class Notifications { close = R.id.notification_close; rv.setViewVisibility(close, View.VISIBLE); } + + if(isSingleFile) { + if(previous > 0) { + rv.setViewVisibility(previous, View.GONE); + previous = 0; + } + if(rewind > 0) { + rv.setViewVisibility(rewind, View.GONE); + rewind = 0; + } + + if(next > 0) { + rv.setViewVisibility(next, View.GONE); + next = 0; + } + + if(fastForward > 0) { + rv.setViewVisibility(fastForward, View.GONE); + fastForward = 0; + } + } if(previous > 0) { Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS"); diff --git a/app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java b/app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java new file mode 100644 index 00000000..7de4f928 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java @@ -0,0 +1,112 @@ +/* + This file is part of Subsonic. + Subsonic 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. + Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2016 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; + +import java.util.Locale; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.activity.SettingsActivity; +import github.daneren2005.dsub.activity.SubsonicFragmentActivity; + +public final class ThemeUtil { + public static final String THEME_DARK = "dark"; + public static final String THEME_BLACK = "black"; + public static final String THEME_LIGHT = "light"; + public static final String THEME_HOLO = "holo"; + public static final String THEME_DAY_NIGHT = "day/night"; + public static final String THEME_DAY_BLACK_NIGHT = "day/black"; + + public static String getTheme(Context context) { + SharedPreferences prefs = Util.getPreferences(context); + String theme = prefs.getString(Constants.PREFERENCES_KEY_THEME, null); + + if(THEME_DAY_NIGHT.equals(theme)) { + int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + if(currentNightMode == Configuration.UI_MODE_NIGHT_YES) { + theme = THEME_DARK; + } else { + theme = THEME_LIGHT; + } + } else if(THEME_DAY_BLACK_NIGHT.equals(theme)) { + int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + if(currentNightMode == Configuration.UI_MODE_NIGHT_YES) { + theme = THEME_BLACK; + } else { + theme = THEME_LIGHT; + } + } + + return theme; + } + public static int getThemeRes(Context context) { + return getThemeRes(context, getTheme(context)); + } + public static int getThemeRes(Context context, String theme) { + if(context instanceof SubsonicFragmentActivity || context instanceof SettingsActivity) { + if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) { + if (THEME_DARK.equals(theme)) { + return R.style.Theme_DSub_Dark_No_Actionbar; + } else if (THEME_BLACK.equals(theme)) { + return R.style.Theme_DSub_Black_No_Actionbar; + } else if (THEME_HOLO.equals(theme)) { + return R.style.Theme_DSub_Holo_No_Actionbar; + } else { + return R.style.Theme_DSub_Light_No_Actionbar; + } + } else { + if (THEME_DARK.equals(theme)) { + return R.style.Theme_DSub_Dark_No_Color; + } else if (THEME_BLACK.equals(theme)) { + return R.style.Theme_DSub_Black_No_Color; + } else if (THEME_HOLO.equals(theme)) { + return R.style.Theme_DSub_Holo_No_Color; + } else { + return R.style.Theme_DSub_Light_No_Color; + } + } + } else { + if (THEME_DARK.equals(theme)) { + return R.style.Theme_DSub_Dark; + } else if (THEME_BLACK.equals(theme)) { + return R.style.Theme_DSub_Black; + } else if (THEME_HOLO.equals(theme)) { + return R.style.Theme_DSub_Holo; + } else { + return R.style.Theme_DSub_Light; + } + } + } + public static void setTheme(Context context, String theme) { + SharedPreferences.Editor editor = Util.getPreferences(context).edit(); + editor.putString(Constants.PREFERENCES_KEY_THEME, theme); + editor.commit(); + } + + public static void applyTheme(Context context, String theme) { + context.setTheme(getThemeRes(context, theme)); + + SharedPreferences prefs = Util.getPreferences(context); + if(prefs.getBoolean(Constants.PREFERENCES_KEY_OVERRIDE_SYSTEM_LANGUAGE, false)) { + Configuration config = new Configuration(); + config.locale = Locale.ENGLISH; + context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics()); + } + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java index 24d3906b..db1c628f 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java +++ b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java @@ -406,7 +406,7 @@ public final class UserUtil { } } - if(sampleUser.getMusicFolderSettings() != null) { + if(sampleUser != null && sampleUser.getMusicFolderSettings() != null) { for(User.Setting setting: sampleUser.getMusicFolderSettings()) { User.MusicFolderSetting musicFolderSetting = (User.MusicFolderSetting) setting; user.addMusicFolder(musicFolderSetting, true); diff --git a/app/src/main/java/github/daneren2005/dsub/util/Util.java b/app/src/main/java/github/daneren2005/dsub/util/Util.java index fc7292f6..b69ea55e 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Util.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Util.java @@ -274,65 +274,6 @@ public final class Util { editor.putBoolean(Constants.PREFERENCES_KEY_ALBUMS_PER_FOLDER + instance, perFolder); editor.commit(); } - - public static String getTheme(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getString(Constants.PREFERENCES_KEY_THEME, null); - } - public static int getThemeRes(Context context) { - return getThemeRes(context, getTheme(context)); - } - public static int getThemeRes(Context context, String theme) { - if(context instanceof SubsonicFragmentActivity || context instanceof SettingsActivity) { - if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) { - if ("dark".equals(theme)) { - return R.style.Theme_DSub_Dark_No_Actionbar; - } else if ("black".equals(theme)) { - return R.style.Theme_DSub_Black_No_Actionbar; - } else if ("holo".equals(theme)) { - return R.style.Theme_DSub_Holo_No_Actionbar; - } else { - return R.style.Theme_DSub_Light_No_Actionbar; - } - } else { - if ("dark".equals(theme)) { - return R.style.Theme_DSub_Dark_No_Color; - } else if ("black".equals(theme)) { - return R.style.Theme_DSub_Black_No_Color; - } else if ("holo".equals(theme)) { - return R.style.Theme_DSub_Holo_No_Color; - } else { - return R.style.Theme_DSub_Light_No_Color; - } - } - } else { - if ("dark".equals(theme)) { - return R.style.Theme_DSub_Dark; - } else if ("black".equals(theme)) { - return R.style.Theme_DSub_Black; - } else if ("holo".equals(theme)) { - return R.style.Theme_DSub_Holo; - } else { - return R.style.Theme_DSub_Light; - } - } - } - public static void setTheme(Context context, String theme) { - SharedPreferences.Editor editor = getPreferences(context).edit(); - editor.putString(Constants.PREFERENCES_KEY_THEME, theme); - editor.commit(); - } - - public static void applyTheme(Context context, String theme) { - context.setTheme(getThemeRes(context, theme)); - - SharedPreferences prefs = Util.getPreferences(context); - if(prefs.getBoolean(Constants.PREFERENCES_KEY_OVERRIDE_SYSTEM_LANGUAGE, false)) { - Configuration config = new Configuration(); - config.locale = Locale.ENGLISH; - context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics()); - } - } public static boolean getDisplayTrack(Context context) { SharedPreferences prefs = getPreferences(context); @@ -480,6 +421,18 @@ public final class Util { return builder.toString().hashCode(); } + public static String getBlockTokenUsePref(Context context, int instance) { + return Constants.CACHE_BLOCK_TOKEN_USE + Util.getRestUrl(context, null, instance, false); + } + public static boolean getBlockTokenUse(Context context, int instance) { + return getPreferences(context).getBoolean(getBlockTokenUsePref(context, instance), false); + } + public static void setBlockTokenUse(Context context, int instance, boolean block) { + SharedPreferences.Editor editor = getPreferences(context).edit(); + editor.putBoolean(getBlockTokenUsePref(context, instance), block); + editor.commit(); + } + public static String replaceInternalUrl(Context context, String url) { // Only change to internal when using https if(url.indexOf("https") != -1) { @@ -1172,7 +1125,7 @@ public final class Util { } public static boolean isAllowedToDownload(Context context) { - return !isWifiRequiredForDownload(context) || isWifiConnected(context); + return isNetworkConnected(context, true) && !isOffline(context); } public static boolean isWifiRequiredForDownload(Context context) { SharedPreferences prefs = getPreferences(context); @@ -1198,22 +1151,22 @@ public final class Util { showDialog(context, android.R.drawable.ic_dialog_info, title, message, linkify); } - private static void showDialog(Context context, int icon, int titleId, int messageId) { + public static void showDialog(Context context, int icon, int titleId, int messageId) { showDialog(context, icon, titleId, messageId, true); } - private static void showDialog(Context context, int icon, int titleId, String message) { + public static void showDialog(Context context, int icon, int titleId, String message) { showDialog(context, icon, titleId, message, true); } - private static void showDialog(Context context, int icon, String title, String message) { + public static void showDialog(Context context, int icon, String title, String message) { showDialog(context, icon, title, message, true); } - private static void showDialog(Context context, int icon, int titleId, int messageId, boolean linkify) { + public static void showDialog(Context context, int icon, int titleId, int messageId, boolean linkify) { showDialog(context, icon, context.getResources().getString(titleId), context.getResources().getString(messageId), linkify); } - private static void showDialog(Context context, int icon, int titleId, String message, boolean linkify) { + public static void showDialog(Context context, int icon, int titleId, String message, boolean linkify) { showDialog(context, icon, context.getResources().getString(titleId), message, linkify); } - private static void showDialog(Context context, int icon, String title, String message, boolean linkify) { + public static void showDialog(Context context, int icon, String title, String message, boolean linkify) { SpannableString ss = new SpannableString(message); if(linkify) { Linkify.addLinks(ss, Linkify.ALL); diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java b/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java index ab64bca9..415106db 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java +++ b/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java @@ -23,13 +23,9 @@ import com.google.android.gms.cast.CastMediaControlIntent; import github.daneren2005.dsub.service.ChromeCastController; import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.service.RemoteController; +import github.daneren2005.dsub.util.EnvironmentVariables; -/** - * Created by owner on 2/9/14. - */ public final class CastCompat { - public static final String APPLICATION_ID = "5F85EBEB"; - static { try { Class.forName("com.google.android.gms.cast.CastDevice"); @@ -52,6 +48,6 @@ public final class CastCompat { } public static String getCastControlCategory() { - return CastMediaControlIntent.categoryForCast(APPLICATION_ID); + return CastMediaControlIntent.categoryForCast(EnvironmentVariables.CAST_APPLICATION_ID); } } diff --git a/app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java b/app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java new file mode 100644 index 00000000..36aaa8af --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java @@ -0,0 +1,39 @@ +/* + This file is part of Subsonic. + Subsonic 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. + Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2016 (C) Scott Jackson +*/ +package github.daneren2005.dsub.view; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.ImageView; +import android.widget.TextView; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.domain.InternetRadioStation; + +public class InternetRadioStationView extends UpdateView<InternetRadioStation> { + private TextView titleView; + + public InternetRadioStationView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.basic_list_item, this, true); + + titleView = (TextView) findViewById(R.id.item_name); + moreButton = (ImageView) findViewById(R.id.item_more); + } + + protected void setObjectImpl(InternetRadioStation station) { + titleView.setText(station.getTitle()); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/view/SongView.java b/app/src/main/java/github/daneren2005/dsub/view/SongView.java index 8cb0c21c..7a02c91d 100644 --- a/app/src/main/java/github/daneren2005/dsub/view/SongView.java +++ b/app/src/main/java/github/daneren2005/dsub/view/SongView.java @@ -30,6 +30,7 @@ import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.service.DownloadFile; import github.daneren2005.dsub.util.DrawableTint; import github.daneren2005.dsub.util.SongDBHandler; +import github.daneren2005.dsub.util.ThemeUtil; import github.daneren2005.dsub.util.Util; import java.io.File; @@ -336,7 +337,7 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> { if(isRated == 1) { this.setBackgroundColor(Color.RED); - String theme = Util.getTheme(context); + String theme = ThemeUtil.getTheme(context); if("black".equals(theme)) { this.getBackground().setAlpha(80); } else if("dark".equals(theme) || "holo".equals(theme)) { diff --git a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java index da9b135f..a2c898b9 100644 --- a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java @@ -5,11 +5,12 @@ import android.os.Bundle; import android.support.v7.app.MediaRouteChooserDialog; import android.support.v7.app.MediaRouteChooserDialogFragment; +import github.daneren2005.dsub.util.ThemeUtil; import github.daneren2005.dsub.util.Util; public class CustomMediaRouteChooserDialogFragment extends MediaRouteChooserDialogFragment { @Override public MediaRouteChooserDialog onCreateChooserDialog(Context context, Bundle savedInstanceState) { - return new MediaRouteChooserDialog(context, Util.getThemeRes(context)); + return new MediaRouteChooserDialog(context, ThemeUtil.getThemeRes(context)); } } diff --git a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java index 7fd54142..ea890b9f 100644 --- a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java @@ -5,11 +5,12 @@ import android.os.Bundle; import android.support.v7.app.MediaRouteControllerDialog; import android.support.v7.app.MediaRouteControllerDialogFragment; +import github.daneren2005.dsub.util.ThemeUtil; import github.daneren2005.dsub.util.Util; public class CustomMediaRouteControllerDialogFragment extends MediaRouteControllerDialogFragment { @Override public MediaRouteControllerDialog onCreateControllerDialog(Context context, Bundle savedInstanceState) { - return new MediaRouteControllerDialog(context, Util.getThemeRes(context)); + return new MediaRouteControllerDialog(context, ThemeUtil.getThemeRes(context)); } } diff --git a/app/src/main/res/layout/set_playback_speed.xml b/app/src/main/res/layout/set_playback_speed.xml new file mode 100644 index 00000000..42f23a35 --- /dev/null +++ b/app/src/main/res/layout/set_playback_speed.xml @@ -0,0 +1,22 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_gravity="center"> + + <TextView + android:id="@+id/playback_speed_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:textSize="20dp" + android:paddingRight="10px" + android:layout_gravity="center" + android:textColor="?android:textColorPrimary"/> + + <SeekBar + android:id="@+id/playback_speed_bar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="25"/> +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/menu/drawer_navigation.xml b/app/src/main/res/menu/drawer_navigation.xml index bd309455..32de5cd5 100644 --- a/app/src/main/res/menu/drawer_navigation.xml +++ b/app/src/main/res/menu/drawer_navigation.xml @@ -22,6 +22,10 @@ android:icon="?attr/drawerBookmarks" android:title="@string/button_bar.bookmarks"/> <item + android:id="@+id/drawer_internet_radio_stations" + android:icon="?attr/drawerInternetRadioStations" + android:title="@string/button_bar.internet_radio"/> + <item android:id="@+id/drawer_shares" android:icon="?attr/drawerShares" android:title="@string/button_bar.shares"/> diff --git a/app/src/main/res/menu/playback_speed_options.xml b/app/src/main/res/menu/playback_speed_options.xml index e64ecd04..909d3b3c 100644 --- a/app/src/main/res/menu/playback_speed_options.xml +++ b/app/src/main/res/menu/playback_speed_options.xml @@ -19,4 +19,8 @@ <item android:id="@+id/playback_speed_tripple" android:title="@string/download.playback_speed_tripple"/> + + <item + android:id="@+id/playback_speed_custom" + android:title="@string/download.playback_speed_custom"/> </menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/select_internet_radio_context.xml b/app/src/main/res/menu/select_internet_radio_context.xml new file mode 100644 index 00000000..e739aec5 --- /dev/null +++ b/app/src/main/res/menu/select_internet_radio_context.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/internet_radio_info" + android:title="@string/common.info"/> + +</menu> diff --git a/app/src/main/res/menu/select_song_context.xml b/app/src/main/res/menu/select_song_context.xml index b68dcad9..46eaaa38 100644 --- a/app/src/main/res/menu/select_song_context.xml +++ b/app/src/main/res/menu/select_song_context.xml @@ -15,6 +15,12 @@ android:title="@string/menu.show_artist"/> </group> + <group android:id="@+id/hide_play_now"> + <item + android:id="@+id/song_menu_play_now" + android:title="@string/common.play_now"/> + </group> + <group android:id="@+id/hide_play_next"> <item android:id="@+id/song_menu_play_next" diff --git a/app/src/main/res/menu/select_song_context_offline.xml b/app/src/main/res/menu/select_song_context_offline.xml index d7182068..cc914563 100644 --- a/app/src/main/res/menu/select_song_context_offline.xml +++ b/app/src/main/res/menu/select_song_context_offline.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:compat="http://schemas.android.com/apk/res-auto"> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/song_menu_info" @@ -16,6 +15,12 @@ android:title="@string/menu.show_artist"/> </group> + <group android:id="@+id/hide_play_now"> + <item + android:id="@+id/song_menu_play_now" + android:title="@string/common.play_now"/> + </group> + <group android:id="@+id/hide_play_next"> <item android:id="@+id/song_menu_play_next" diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 8ac830a7..12102043 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -36,6 +36,7 @@ <string name="button_bar.now_playing">Várólista</string> <string name="button_bar.podcasts">Podcastok</string> <string name="button_bar.bookmarks">Könyvjelzők</string> + <string name="button_bar.internet_radio">Internet rádió</string> <string name="button_bar.shares">Megosztások</string> <string name="button_bar.chat">Csevegés (Chat)</string> <string name="button_bar.admin">Admin</string> @@ -220,6 +221,12 @@ <string name="download.thumbs_up">Jó</string> <string name="download.thumbs_down">Nem jó</string> <string name="download.batch_mode">Kötegelt mód</string> + <string name="download.playback_speed_half">0.5x</string> + <string name="download.playback_speed_normal">1x</string> + <string name="download.playback_speed_one_half">1.5x</string> + <string name="download.playback_speed_double">2x</string> + <string name="download.playback_speed_tripple">3x</string> + <string name="download.playback_speed_custom">Egyéni</string> <string name="sync.new_podcasts">Új podcastok: \"%s\"</string> <string name="sync.new_playlists">Új lejátszási listák: \"%s\"</string> @@ -300,6 +307,8 @@ <string name="settings.theme_dark">Sötét</string> <string name="settings.theme_black">Fekete</string> <string name="settings.theme_holo">Holo</string> + <string name="settings.theme_day_night">Nappal/Éjszaka</string> + <string name="settings.theme_day_black_night">Nappal/Fekete éjszaka</string> <string name="settings.theme_fullscreen">Teljes képernyős</string> <string name="settings.theme_fullscreen_summary">Teljes képernyős üzemmód (értesítési sáv elrejtése).</string> <string name="settings.track_title">Dalsorszám megjelenítése</string> @@ -402,6 +411,8 @@ <string name="settings.podcasts_enabled_summary">Podcastok menüpont megjelenítése az elhúzható oldalsávon.</string> <string name="settings.bookmarks_enabled">Könyvjelzők engedélyezése</string> <string name="settings.bookmarks_enabled_summary">Könyvjelzők menüpont megjelenítése az elhúzható oldalsávon.</string> + <string name="settings.internet_radio_enabled">Internet rádió engedélyezése</string> + <string name="settings.internet_radio_enabled_summary">Internet rádió menüpont megjelenítése az elhúzható oldalsávon.</string> <string name="settings.shares_enabled">Megosztások engedélyezése</string> <string name="settings.shares_enabled_summary">Megosztások menüpont megjelenítése az elhúzható oldalsávon.</string> <string name="settings.sync_title">Szinkronizálás</string> @@ -442,6 +453,11 @@ <string name="settings.override_system_language">A rendszer nyelvének felülbírálása</string> <string name="settings.override_system_language_summary">A Dsub megjelenítése angol nyelven abban az esetben is, ha rendelkezik fordítással. Az alkalmazást törölni kell a memóriából, mert a beállítás csak újraindítás után lép érvénybe!</string> <string name="settings.drawer_items_title">Oldalsáv elemei</string> + <string name="settings.song_press_action">Viselkedés dal megérintésekor</string> + <string name="settings.song_press_play_single">Dal lejátszása</string> + <string name="settings.song_press_play_all">A teljes album hozzáadása a várólistához</string> + <string name="settings.song_press_play_next">Dal sorba állítása következőnek</string> + <string name="settings.song_press_play_last">Dal sorba állítása utolsónak</string> <string name="settings.large_album_art">Nagyméretű albumborítók</string> <string name="settings.large_album_art_summary">Albumok megjelenítése rácsnézetben és nagyméretű albumborítóval a listanézet helyett.</string> <string name="settings.admin_enabled">Admin engedélyezése</string> @@ -454,7 +470,7 @@ <string name="settings.replay_gain_type.track">Dal értékeiből</string> <string name="settings.replay_gain_bump">Hangerő-kiegyenlítés előerősítése</string> <string name="settings.replay_gain_untagged">Dalok hangerő-kiegyenlítés nélkül</string> - <string name="settings.casting">Casting (Tartalmak átküldése)</string> + <string name="settings.casting">Casting (tartalmak átküldése)</string> <string name="settings.casting_proxy">Eszköz használata proxyként</string> <string name="settings.casting_proxy_summary">Streamelés az eszközön (mint egy proxyn) keresztül. Ez megoldást jelenthet néhány esetben, pl. saját aláírású tanúsítvány használatakor.</string> <string name="settings.rename_duplicates">Duplikált dalok átnevezése</string> @@ -530,7 +546,7 @@ <string name="admin.role.jukebox">Jukebox vezérlése</string> <string name="admin.role.share">Megosztások kezelése</string> <string name="admin.role.video_conversion">Videók konvertálása</string> - <string name="admin.role.lastfm">Last.fm funkció használata</string> + <string name="admin.role.lastfm">Last.fm szolgáltatás használata</string> <string name="music_service.retry">Hálózati hiba történt! Újrapróbálkozás %1$d/%2$d.</string> @@ -555,8 +571,8 @@ <string name="equalizer.label">Equalizer</string> <string name="equalizer.enabled">Engedélyezve</string> <string name="equalizer.preset">Profil kiválasztása</string> - <string name="equalizer.bass_booster">Basszus fokozás</string> - <string name="equalizer.voice_booster">Beszédhang fokozás</string> + <string name="equalizer.bass_booster">Basszus fokozása</string> + <string name="equalizer.voice_booster">Beszédhang fokozása</string> <string name="equalizer.db_size">%d dB</string> <string name="equalizer.bass_size">%d ezer</string> @@ -578,7 +594,9 @@ <string name="changelog_ok_button">OK</string> <string name="changelog_show_full">Továbbiak…</string> - <string name="chat.send_a_message">Üzenet küldése</string> + <string name="chat.send_a_message">Üzenet küldése</string> + + <string name="changelog_version_format">Verzió %s</string> <string name="tasker.start_playing">Lejátszás indítása</string> <string name="tasker.start_playing_shuffled">Lejátszás indítása kevert sorrendben</string> @@ -624,7 +642,7 @@ <string name="details.version">Verzió</string> <string name="details.files_cached">Gyorsítótárazott fájlok</string> <string name="details.files_permanent">Megőrzött fájlok</string> - <string name="details.used_space">Felhasznált tároló</string> + <string name="details.used_space">Felhasznált tárhely</string> <string name="details.available_space">Rendelkezésre álló hely</string> <string name="details.of">%1$s/%2$s</string> <string name="details.song">Dal</string> diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 01296e9e..e9aadce7 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -6,6 +6,7 @@ <item>@string/button_bar.playlists</item> <item>@string/button_bar.podcasts</item> <item>@string/button_bar.bookmarks</item> + <item>@string/button_bar.internet_radio</item> <item>@string/button_bar.shares</item> <item>@string/button_bar.chat</item> </string-array> @@ -16,6 +17,7 @@ <item>Playlist</item> <item>Podcast</item> <item>Bookmark</item> + <item>Internet Radio</item> <item>Share</item> <item>Chat</item> </string-array> @@ -25,6 +27,8 @@ <item>dark</item> <item>black</item> <item>holo</item> + <item>day/night</item> + <item>day/black</item> </string-array> <string-array name="themeNames"> @@ -32,6 +36,8 @@ <item>@string/settings.theme_dark</item> <item>@string/settings.theme_black</item> <item>@string/settings.theme_holo</item> + <item>@string/settings.theme_day_night</item> + <item>@string/settings.theme_day_black_night</item> </string-array> <string-array name="sleepTimerValues"> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 7cd447f0..055726b8 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -49,6 +49,7 @@ <attr name="drawerPlaylists" format="reference"/> <attr name="drawerPodcasts" format="reference"/> <attr name="drawerBookmarks" format="reference"/> + <attr name="drawerInternetRadioStations" format="reference"/> <attr name="drawerShares" format="reference"/> <attr name="drawerChat" format="reference"/> <attr name="drawerAdmin" format="reference"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d372940f..9ac609c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,6 +36,7 @@ <string name="button_bar.now_playing">Now Playing</string> <string name="button_bar.podcasts">Podcasts</string> <string name="button_bar.bookmarks">Bookmarks</string> + <string name="button_bar.internet_radio">Internet Radio</string> <string name="button_bar.shares">Shares</string> <string name="button_bar.chat">Chat</string> <string name="button_bar.admin">Admin</string> @@ -225,6 +226,7 @@ <string name="download.playback_speed_one_half">1.5x</string> <string name="download.playback_speed_double">2x</string> <string name="download.playback_speed_tripple">3x</string> + <string name="download.playback_speed_custom">Custom</string> <string name="sync.new_podcasts">New podcasts available</string> <string name="sync.new_playlists">New songs in playlists</string> @@ -305,6 +307,8 @@ <string name="settings.theme_dark">Dark</string> <string name="settings.theme_black">Black</string> <string name="settings.theme_holo">Holo</string> + <string name="settings.theme_day_night">Day/Night</string> + <string name="settings.theme_day_black_night">Day/Black Night</string> <string name="settings.theme_fullscreen">Fullscreen</string> <string name="settings.theme_fullscreen_summary">Hide as many UI elements as Android will allow</string> <string name="settings.track_title">Display Track #</string> @@ -407,6 +411,8 @@ <string name="settings.podcasts_enabled_summary">Whether or not to display the podcast listing in the pull out drawer</string> <string name="settings.bookmarks_enabled">Bookmarks Enabled</string> <string name="settings.bookmarks_enabled_summary">Whether or not to display the bookmarks listing in the pull out drawer</string> + <string name="settings.internet_radio_enabled">Internet Radio Enabled</string> + <string name="settings.internet_radio_enabled_summary">Whether or not to display the internet radio listing in the pull out drawer</string> <string name="settings.shares_enabled">Shares Enabled</string> <string name="settings.shares_enabled_summary">Whether or not to display the shares listing in the pull out drawer</string> <string name="settings.sync_title">Sync</string> @@ -607,6 +613,7 @@ <string name="details.title.podcast">Podcast Details</string> <string name="details.title.playlist">Playlist Details</string> <string name="details.title.artist">Artist Details</string> + <string name="details.title.internet_radio_station">Internet Radio Details</string> <string name="details.podcast">Podcast</string> <string name="details.status">Status</string> <string name="details.artist">Artist</string> @@ -646,6 +653,10 @@ <string name="details.last_played">Last Played</string> <string name="details.expiration">Expiration</string> <string name="details.played_count">Played Count</string> + <string name="details.stream_url">Stream URL</string> + <string name="details.home_page">Home Page</string> + + <string name="permission.external_storage.failed">DSub cannot function without the ability to write to storage</string> <plurals name="select_album_n_songs"> <item quantity="zero">No songs</item> diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 9e95fe9d..8cccab93 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -49,6 +49,7 @@ <item name="drawerPlaylists">@drawable/ic_menu_playlist_light</item> <item name="drawerPodcasts">@drawable/ic_menu_podcast_light</item> <item name="drawerBookmarks">@drawable/ic_menu_bookmark_light</item> + <item name="drawerInternetRadioStations">@drawable/ic_menu_radio_light</item> <item name="drawerShares">@drawable/ic_menu_share_light</item> <item name="drawerChat">@drawable/ic_menu_chat_light</item> <item name="drawerAdmin">@drawable/ic_menu_admin_light</item> @@ -120,6 +121,7 @@ <item name="drawerPlaylists">@drawable/ic_menu_playlist_dark</item> <item name="drawerPodcasts">@drawable/ic_menu_podcast_dark</item> <item name="drawerBookmarks">@drawable/ic_menu_bookmark_dark</item> + <item name="drawerInternetRadioStations">@drawable/ic_menu_radio_dark</item> <item name="drawerShares">@drawable/ic_menu_share_dark</item> <item name="drawerChat">@drawable/ic_menu_chat_dark</item> <item name="drawerAdmin">@drawable/ic_menu_admin_dark</item> diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index 490c427a..89f076f7 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -1,5 +1,16 @@ <?xml version="1.0" encoding="utf-8"?> <changelog> + <release version="5.3" versioncode="186" releasedate="9/23/2016"> + <change>Listen to Radio Internet Stations</change> + <change>Automatic Day/Night theme</change> + <change>Android 6.0 runtime permissions</change> + <change>Custom variable playback speed</change> + <change>Add Play Now for songs when click action is add to queue</change> + <change>More secure connections with tokens (Subsonic 6+)</change> + <change>Auto skip uncached songs when no network</change> + <change>Shrink install size</change> + <change>Fix DLNA casting on Android 7.0+</change> + </release> <release version="5.2.2" versioncode="184" releasedate="8/30/2016"> <change>Fix lagging in landscape view on the Now Playing screen</change> </release> diff --git a/app/src/main/res/xml/settings_drawer.xml b/app/src/main/res/xml/settings_drawer.xml index 4b92737e..f89fb990 100644 --- a/app/src/main/res/xml/settings_drawer.xml +++ b/app/src/main/res/xml/settings_drawer.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:myns="http://schemas.android.com/apk/res/github.daneren2005.dsub" + xmlns:myns="http://schemas.android.com/apk/res-auto" android:title="@string/settings.drawer_items_title"> <PreferenceCategory @@ -19,6 +19,12 @@ android:defaultValue="true"/> <CheckBoxPreference + android:title="@string/settings.internet_radio_enabled" + android:summary="@string/settings.internet_radio_enabled_summary" + android:key="internetRadioEnabled" + android:defaultValue="true"/> + + <CheckBoxPreference android:title="@string/settings.shares_enabled" android:summary="@string/settings.shares_enabled_summary" android:key="sharedEnabled" diff --git a/app/src/main/res/xml/settings_playback.xml b/app/src/main/res/xml/settings_playback.xml index 566a8218..da31d071 100644 --- a/app/src/main/res/xml/settings_playback.xml +++ b/app/src/main/res/xml/settings_playback.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:myns="http://schemas.android.com/apk/res/github.daneren2005.dsub" + xmlns:myns="http://schemas.android.com/apk/res-auto" android:title="@string/settings.playback_title"> <PreferenceCategory |