From 9ad035f0a78fadf5e390127388a9110cae8c1dac Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 29 Dec 2014 10:09:13 -0800 Subject: Move settings fragment out to it's own class so it doesn't crash on resume --- .../dsub/activity/SettingsActivity.java | 673 +------------------- .../dsub/fragments/SettingsFragment.java | 700 +++++++++++++++++++++ 2 files changed, 710 insertions(+), 663 deletions(-) create mode 100644 src/github/daneren2005/dsub/fragments/SettingsFragment.java (limited to 'src') diff --git a/src/github/daneren2005/dsub/activity/SettingsActivity.java b/src/github/daneren2005/dsub/activity/SettingsActivity.java index 56a89fd2..24188c39 100644 --- a/src/github/daneren2005/dsub/activity/SettingsActivity.java +++ b/src/github/daneren2005/dsub/activity/SettingsActivity.java @@ -46,6 +46,7 @@ import android.widget.FrameLayout; import github.daneren2005.dsub.R; import github.daneren2005.dsub.fragments.PreferenceCompatFragment; +import github.daneren2005.dsub.fragments.SettingsFragment; import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; @@ -64,42 +65,9 @@ import java.text.DecimalFormat; import java.util.LinkedHashMap; import java.util.Map; -public class SettingsActivity extends SubsonicActivity implements SharedPreferences.OnSharedPreferenceChangeListener { +public class SettingsActivity extends SubsonicActivity { private static final String TAG = SettingsActivity.class.getSimpleName(); - private final Map serverSettings = new LinkedHashMap(); - private boolean testingConnection; private PreferenceCompatFragment fragment; - private ListPreference theme; - private ListPreference maxBitrateWifi; - private ListPreference maxBitrateMobile; - private ListPreference maxVideoBitrateWifi; - private ListPreference maxVideoBitrateMobile; - private ListPreference networkTimeout; - private EditTextPreference cacheLocation; - private ListPreference preloadCountWifi; - private ListPreference preloadCountMobile; - private ListPreference tempLoss; - private ListPreference pauseDisconnect; - private Preference addServerPreference; - private PreferenceCategory serversCategory; - private ListPreference videoPlayer; - private ListPreference syncInterval; - private CheckBoxPreference syncEnabled; - private CheckBoxPreference syncWifi; - private CheckBoxPreference syncNotification; - private CheckBoxPreference syncStarred; - private CheckBoxPreference syncMostRecent; - private CheckBoxPreference replayGain; - private ListPreference replayGainType; - private Preference replayGainBump; - private Preference replayGainUntagged; - private String internalSSID; - private String internalSSIDDisplay; - private EditTextPreference cacheSize; - - private int serverCount = 3; - private SharedPreferences settings; - private DecimalFormat megabyteFromat; @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Override @@ -107,636 +75,15 @@ public class SettingsActivity extends SubsonicActivity implements SharedPreferen super.onCreate(savedInstanceState); setContentView(0); - fragment = new PreferenceCompatFragment() { - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - this.setTitle(getResources().getString(R.string.settings_title)); - initSettings(); - } - }; - Bundle args = new Bundle(); - args.putInt(Constants.INTENT_EXTRA_FRAGMENT_TYPE, R.xml.settings); - - fragment.setArguments(args); - this.getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, fragment, null).commit(); - currentFragment = fragment; - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - SharedPreferences prefs = Util.getPreferences(this); - prefs.unregisterOnSharedPreferenceChangeListener(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - - return false; - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - // Random error I have no idea how to reproduce - if(sharedPreferences == null) { - return; - } - - update(); - - if (Constants.PREFERENCES_KEY_HIDE_MEDIA.equals(key)) { - setHideMedia(sharedPreferences.getBoolean(key, false)); - } - else if (Constants.PREFERENCES_KEY_MEDIA_BUTTONS.equals(key)) { - setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true)); - } - else if (Constants.PREFERENCES_KEY_CACHE_LOCATION.equals(key)) { - setCacheLocation(sharedPreferences.getString(key, "")); - } - else if (Constants.PREFERENCES_KEY_SLEEP_TIMER_DURATION.equals(key)){ - DownloadService downloadService = DownloadService.getInstance(); - downloadService.setSleepTimerDuration(Integer.parseInt(sharedPreferences.getString(key, "60"))); - } - else if(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT.equals(key)) { - SyncUtil.removeMostRecentSyncFiles(this); - } else if(Constants.PREFERENCES_KEY_REPLAY_GAIN.equals(key) || Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP.equals(key) || Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED.equals(key)) { - DownloadService downloadService = DownloadService.getInstance(); - if(downloadService != null) { - downloadService.reapplyVolume(); - } - } - - scheduleBackup(); - } - - private void initSettings() { - internalSSID = Util.getSSID(this); - if(internalSSID == null) { - internalSSID = ""; - } - internalSSIDDisplay = this.getResources().getString(R.string.settings_server_local_network_ssid_hint, internalSSID); - - theme = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_THEME); - maxBitrateWifi = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); - maxBitrateMobile = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); - maxVideoBitrateWifi = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI); - maxVideoBitrateMobile = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE); - networkTimeout = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT); - cacheLocation = (EditTextPreference) fragment.findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION); - preloadCountWifi = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI); - preloadCountMobile = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE); - tempLoss = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_TEMP_LOSS); - pauseDisconnect = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_PAUSE_DISCONNECT); - serversCategory = (PreferenceCategory) fragment.findPreference(Constants.PREFERENCES_KEY_SERVER_KEY); - addServerPreference = fragment.findPreference(Constants.PREFERENCES_KEY_SERVER_ADD); - videoPlayer = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER); - syncInterval = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SYNC_INTERVAL); - syncEnabled = (CheckBoxPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SYNC_ENABLED); - syncWifi = (CheckBoxPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SYNC_WIFI); - syncNotification = (CheckBoxPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SYNC_NOTIFICATION); - syncStarred = (CheckBoxPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SYNC_STARRED); - syncMostRecent = (CheckBoxPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT); - replayGain = (CheckBoxPreference) fragment.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN); - replayGainType = (ListPreference) fragment.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_TYPE); - replayGainBump = fragment.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP); - replayGainUntagged = fragment.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED); - cacheSize = (EditTextPreference) fragment.findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); - - settings = Util.getPreferences(this); - serverCount = settings.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1); - - fragment.findPreference("clearCache").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - Util.confirmDialog(SettingsActivity.this, R.string.common_delete, R.string.common_confirm_message_cache, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new LoadingTask(SettingsActivity.this, false) { - @Override - protected Void doInBackground() throws Throwable { - FileUtil.deleteMusicDirectory(SettingsActivity.this); - FileUtil.deleteSerializedCache(SettingsActivity.this); - return null; - } - - @Override - protected void done(Void result) { - Util.toast(SettingsActivity.this, R.string.settings_cache_clear_complete); - } - - @Override - protected void error(Throwable error) { - Util.toast(SettingsActivity.this, getErrorMessage(error), false); - } - }.execute(); - } - }); - return false; - } - }); - - addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - serverCount++; - String instance = String.valueOf(serverCount); - - Preference addServerPreference = fragment.findPreference(Constants.PREFERENCES_KEY_SERVER_ADD); - serversCategory.addPreference(addServer(serverCount)); - - SharedPreferences.Editor editor = settings.edit(); - editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount); - // Reset set folder ID - editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null); - editor.commit(); - - serverSettings.put(instance, new ServerSettings(instance)); - - return true; - } - }); - - fragment.findPreference(Constants.PREFERENCES_KEY_SYNC_ENABLED).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - Boolean syncEnabled = (Boolean) newValue; - - Account account = new Account(Constants.SYNC_ACCOUNT_NAME, Constants.SYNC_ACCOUNT_TYPE); - ContentResolver.setSyncAutomatically(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, syncEnabled); - ContentResolver.setSyncAutomatically(account, Constants.SYNC_ACCOUNT_PODCAST_AUTHORITY, syncEnabled); - - return true; - } - }); - syncInterval.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - Integer syncInterval = Integer.parseInt(((String) newValue)); - - Account account = new Account(Constants.SYNC_ACCOUNT_NAME, Constants.SYNC_ACCOUNT_TYPE); - ContentResolver.addPeriodicSync(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, new Bundle(), 60L * syncInterval); - ContentResolver.addPeriodicSync(account, Constants.SYNC_ACCOUNT_PODCAST_AUTHORITY, new Bundle(), 60L * syncInterval); - - return true; - } - }); - - serversCategory.setOrderingAsAdded(false); - for (int i = 1; i <= serverCount; i++) { - String instance = String.valueOf(i); - serversCategory.addPreference(addServer(i)); - serverSettings.put(instance, new ServerSettings(instance)); - } - - SharedPreferences prefs = Util.getPreferences(this); - prefs.registerOnSharedPreferenceChangeListener(this); - - update(); - } - - private void scheduleBackup() { - try { - Class managerClass = Class.forName("android.app.backup.BackupManager"); - Constructor managerConstructor = managerClass.getConstructor(Context.class); - Object manager = managerConstructor.newInstance(this); - Method m = managerClass.getMethod("dataChanged"); - m.invoke(manager); - } catch(ClassNotFoundException e) { - Log.e(TAG, "No backup manager found"); - } catch(Throwable t) { - Log.e(TAG, "Scheduling backup failed " + t); - t.printStackTrace(); - } - } - - private void update() { - if (testingConnection) { - return; - } - - theme.setSummary(theme.getEntry()); - maxBitrateWifi.setSummary(maxBitrateWifi.getEntry()); - maxBitrateMobile.setSummary(maxBitrateMobile.getEntry()); - maxVideoBitrateWifi.setSummary(maxVideoBitrateWifi.getEntry()); - maxVideoBitrateMobile.setSummary(maxVideoBitrateMobile.getEntry()); - networkTimeout.setSummary(networkTimeout.getEntry()); - cacheLocation.setSummary(cacheLocation.getText()); - preloadCountWifi.setSummary(preloadCountWifi.getEntry()); - preloadCountMobile.setSummary(preloadCountMobile.getEntry()); - tempLoss.setSummary(tempLoss.getEntry()); - pauseDisconnect.setSummary(pauseDisconnect.getEntry()); - videoPlayer.setSummary(videoPlayer.getEntry()); - syncInterval.setSummary(syncInterval.getEntry()); - try { - if(megabyteFromat == null) { - megabyteFromat = new DecimalFormat(getResources().getString(R.string.util_bytes_format_megabyte)); - } - - cacheSize.setSummary(megabyteFromat.format((double) Integer.parseInt(cacheSize.getText())).replace(".00", "")); - } catch(Exception e) { - Log.e(TAG, "Failed to format cache size", e); - cacheSize.setSummary(cacheSize.getText()); - } - if(syncEnabled.isChecked()) { - if(!syncInterval.isEnabled()) { - syncInterval.setEnabled(true); - syncWifi.setEnabled(true); - syncNotification.setEnabled(true); - syncStarred.setEnabled(true); - syncMostRecent.setEnabled(true); - } - } else { - if(syncInterval.isEnabled()) { - syncInterval.setEnabled(false); - syncWifi.setEnabled(false); - syncNotification.setEnabled(false); - syncStarred.setEnabled(false); - syncMostRecent.setEnabled(false); - } - } - if(replayGain.isChecked()) { - replayGainType.setEnabled(true); - replayGainBump.setEnabled(true); - replayGainUntagged.setEnabled(true); - } else { - replayGainType.setEnabled(false); - replayGainBump.setEnabled(false); - replayGainUntagged.setEnabled(false); - } - replayGainType.setSummary(replayGainType.getEntry()); - - for (ServerSettings ss : serverSettings.values()) { - ss.update(); - } - } - - private PreferenceScreen addServer(final int instance) { - final PreferenceScreen screen = fragment.getPreferenceManager().createPreferenceScreen(this); - screen.setTitle(R.string.settings_server_unused); - screen.setKey(Constants.PREFERENCES_KEY_SERVER_KEY + instance); - - final EditTextPreference serverNamePreference = new EditTextPreference(this); - serverNamePreference.setKey(Constants.PREFERENCES_KEY_SERVER_NAME + instance); - serverNamePreference.setDefaultValue(getResources().getString(R.string.settings_server_unused)); - serverNamePreference.setTitle(R.string.settings_server_name); - serverNamePreference.setDialogTitle(R.string.settings_server_name); + if (savedInstanceState == null) { + fragment = new SettingsFragment(); + Bundle args = new Bundle(); + args.putInt(Constants.INTENT_EXTRA_FRAGMENT_TYPE, R.xml.settings); - if (serverNamePreference.getText() == null) { - serverNamePreference.setText(getResources().getString(R.string.settings_server_unused)); + fragment.setArguments(args); + fragment.setRetainInstance(true); + this.getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, fragment, null).commit(); + currentFragment = fragment; } - - serverNamePreference.setSummary(serverNamePreference.getText()); - - final EditTextPreference serverUrlPreference = new EditTextPreference(this); - serverUrlPreference.setKey(Constants.PREFERENCES_KEY_SERVER_URL + instance); - serverUrlPreference.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI); - serverUrlPreference.setDefaultValue("http://yourhost"); - serverUrlPreference.setTitle(R.string.settings_server_address); - serverUrlPreference.setDialogTitle(R.string.settings_server_address); - - if (serverUrlPreference.getText() == null) { - serverUrlPreference.setText("http://yourhost"); - } - - serverUrlPreference.setSummary(serverUrlPreference.getText()); - screen.setSummary(serverUrlPreference.getText()); - - final EditTextPreference serverLocalNetworkSSIDPreference = new EditTextPreference(this) { - @Override - protected void onAddEditTextToDialogView(View dialogView, final EditText editText) { - super.onAddEditTextToDialogView(dialogView, editText); - ViewGroup root = (ViewGroup) ((ViewGroup) dialogView).getChildAt(0); - - Button defaultButton = new Button(getContext()); - defaultButton.setText(internalSSIDDisplay); - defaultButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - editText.setText(internalSSID); - } - }); - root.addView(defaultButton); - } - }; - serverLocalNetworkSSIDPreference.setKey(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance); - serverLocalNetworkSSIDPreference.setTitle(R.string.settings_server_local_network_ssid); - serverLocalNetworkSSIDPreference.setDialogTitle(R.string.settings_server_local_network_ssid); - - final EditTextPreference serverInternalUrlPreference = new EditTextPreference(this); - serverInternalUrlPreference.setKey(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance); - serverInternalUrlPreference.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI); - serverInternalUrlPreference.setDefaultValue(""); - serverInternalUrlPreference.setTitle(R.string.settings_server_internal_address); - serverInternalUrlPreference.setDialogTitle(R.string.settings_server_internal_address); - serverInternalUrlPreference.setSummary(serverInternalUrlPreference.getText()); - - final EditTextPreference serverUsernamePreference = new EditTextPreference(this); - serverUsernamePreference.setKey(Constants.PREFERENCES_KEY_USERNAME + instance); - serverUsernamePreference.setTitle(R.string.settings_server_username); - serverUsernamePreference.setDialogTitle(R.string.settings_server_username); - - final EditTextPreference serverPasswordPreference = new EditTextPreference(this); - serverPasswordPreference.setKey(Constants.PREFERENCES_KEY_PASSWORD + instance); - serverPasswordPreference.getEditText().setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - serverPasswordPreference.setSummary("***"); - serverPasswordPreference.setTitle(R.string.settings_server_password); - - final CheckBoxPreference serverTagPreference = new CheckBoxPreference(this); - serverTagPreference.setKey(Constants.PREFERENCES_KEY_BROWSE_TAGS + instance); - serverTagPreference.setChecked(Util.isTagBrowsing(this, instance)); - serverTagPreference.setSummary(R.string.settings_browse_by_tags_summary); - serverTagPreference.setTitle(R.string.settings_browse_by_tags); - serverPasswordPreference.setDialogTitle(R.string.settings_server_password); - - final CheckBoxPreference serverSyncPreference = new CheckBoxPreference(this); - serverSyncPreference.setKey(Constants.PREFERENCES_KEY_SERVER_SYNC + instance); - serverSyncPreference.setChecked(Util.isSyncEnabled(this, instance)); - serverSyncPreference.setSummary(R.string.settings_server_sync_summary); - serverSyncPreference.setTitle(R.string.settings_server_sync); - - final Preference serverOpenBrowser = new Preference(this); - serverOpenBrowser.setKey(Constants.PREFERENCES_KEY_OPEN_BROWSER); - serverOpenBrowser.setPersistent(false); - serverOpenBrowser.setTitle(R.string.settings_server_open_browser); - serverOpenBrowser.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - openInBrowser(instance); - return true; - } - }); - - Preference serverRemoveServerPreference = new Preference(this); - serverRemoveServerPreference.setKey(Constants.PREFERENCES_KEY_SERVER_REMOVE + instance); - serverRemoveServerPreference.setPersistent(false); - serverRemoveServerPreference.setTitle(R.string.settings_servers_remove); - - serverRemoveServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - Util.confirmDialog(SettingsActivity.this, R.string.common_delete, screen.getTitle().toString(), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Reset values to null so when we ask for them again they are new - serverNamePreference.setText(null); - serverUrlPreference.setText(null); - serverUsernamePreference.setText(null); - serverPasswordPreference.setText(null); - - int activeServer = Util.getActiveServer(SettingsActivity.this); - for (int i = instance; i <= serverCount; i++) { - Util.removeInstanceName(SettingsActivity.this, i, activeServer); - } - - serverCount--; - SharedPreferences.Editor editor = settings.edit(); - editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount); - editor.commit(); - - serversCategory.removePreference(screen); - screen.getDialog().dismiss(); - } - }); - - return true; - } - }); - - Preference serverTestConnectionPreference = new Preference(this); - serverTestConnectionPreference.setKey(Constants.PREFERENCES_KEY_TEST_CONNECTION + instance); - serverTestConnectionPreference.setPersistent(false); - serverTestConnectionPreference.setTitle(R.string.settings_test_connection_title); - serverTestConnectionPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - testConnection(instance); - return false; - } - }); - - screen.addPreference(serverNamePreference); - screen.addPreference(serverUrlPreference); - screen.addPreference(serverInternalUrlPreference); - screen.addPreference(serverLocalNetworkSSIDPreference); - screen.addPreference(serverUsernamePreference); - screen.addPreference(serverPasswordPreference); - screen.addPreference(serverTagPreference); - screen.addPreference(serverSyncPreference); - screen.addPreference(serverTestConnectionPreference); - screen.addPreference(serverOpenBrowser); - screen.addPreference(serverRemoveServerPreference); - - screen.setOrder(instance); - - return screen; - } - - private void applyTheme() { - String activeTheme = Util.getTheme(this); - Util.applyTheme(this, activeTheme); - } - - private void setHideMedia(boolean hide) { - File nomediaDir = new File(FileUtil.getSubsonicDirectory(this), ".nomedia"); - File musicNoMedia = new File(FileUtil.getMusicDirectory(this), ".nomedia"); - if (hide && !nomediaDir.exists()) { - try { - if (!nomediaDir.createNewFile()) { - Log.w(TAG, "Failed to create " + nomediaDir); - } - } catch(Exception e) { - Log.w(TAG, "Failed to create " + nomediaDir, e); - } - - try { - if(!musicNoMedia.createNewFile()) { - Log.w(TAG, "Failed to create " + musicNoMedia); - } - } catch(Exception e) { - Log.w(TAG, "Failed to create " + musicNoMedia, e); - } - } else if (nomediaDir.exists()) { - if (!nomediaDir.delete()) { - Log.w(TAG, "Failed to delete " + nomediaDir); - } - if(!musicNoMedia.delete()) { - Log.w(TAG, "Failed to delete " + musicNoMedia); - } - } - Util.toast(this, R.string.settings_hide_media_toast, false); - } - - private void setMediaButtonsEnabled(boolean enabled) { - if (enabled) { - Util.registerMediaButtonEventReceiver(this); - } else { - Util.unregisterMediaButtonEventReceiver(this); - } - } - - private void setCacheLocation(String path) { - File dir = new File(path); - if (!FileUtil.verifyCanWrite(dir)) { - Util.toast(this, R.string.settings_cache_location_error, false); - - // Reset it to the default. - String defaultPath = FileUtil.getDefaultMusicDirectory(this).getPath(); - if (!defaultPath.equals(path)) { - SharedPreferences prefs = Util.getPreferences(this); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultPath); - editor.commit(); - cacheLocation.setSummary(defaultPath); - cacheLocation.setText(defaultPath); - } - - // Clear download queue. - DownloadService downloadService = DownloadService.getInstance(); - downloadService.clear(); - } - } - - private void testConnection(final int instance) { - LoadingTask task = new LoadingTask(this) { - private int previousInstance; - - @Override - protected Boolean doInBackground() throws Throwable { - updateProgress(R.string.settings_testing_connection); - - previousInstance = Util.getActiveServer(SettingsActivity.this); - testingConnection = true; - MusicService musicService = MusicServiceFactory.getMusicService(SettingsActivity.this); - try { - musicService.setInstance(instance); - musicService.ping(SettingsActivity.this, this); - return musicService.isLicenseValid(SettingsActivity.this, null); - } finally { - musicService.setInstance(null); - testingConnection = false; - } - } - - @Override - protected void done(Boolean licenseValid) { - if (licenseValid) { - Util.toast(SettingsActivity.this, R.string.settings_testing_ok); - } else { - Util.toast(SettingsActivity.this, R.string.settings_testing_unlicensed); - } - } - - @Override - public void cancel() { - super.cancel(); - Util.setActiveServer(SettingsActivity.this, previousInstance); - } - - @Override - protected void error(Throwable error) { - Log.w(TAG, error.toString(), error); - new ErrorDialog(SettingsActivity.this, getResources().getString(R.string.settings_connection_failure) + - " " + getErrorMessage(error), false); - } - }; - task.execute(); - } - - private void openInBrowser(final int instance) { - SharedPreferences prefs = Util.getPreferences(this); - String url = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null); - if(url == null) { - new ErrorDialog(SettingsActivity.this, R.string.settings_invalid_url, false); - return; - } - Uri uriServer = Uri.parse(url); - - Intent browserIntent = new Intent(Intent.ACTION_VIEW, uriServer); - startActivity(browserIntent); - } - - private class ServerSettings { - private EditTextPreference serverName; - private EditTextPreference serverUrl; - private EditTextPreference serverLocalNetworkSSID; - private EditTextPreference serverInternalUrl; - private EditTextPreference username; - private PreferenceScreen screen; - - private ServerSettings(String instance) { - - screen = (PreferenceScreen) fragment.findPreference("server" + instance); - serverName = (EditTextPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SERVER_NAME + instance); - serverUrl = (EditTextPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SERVER_URL + instance); - serverLocalNetworkSSID = (EditTextPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance); - serverInternalUrl = (EditTextPreference) fragment.findPreference(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance); - username = (EditTextPreference) fragment.findPreference(Constants.PREFERENCES_KEY_USERNAME + instance); - - serverUrl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object value) { - try { - String url = (String) value; - new URL(url); - if (url.contains(" ") || url.contains("@") || url.contains("_")) { - throw new Exception(); - } - } catch (Exception x) { - new ErrorDialog(SettingsActivity.this, R.string.settings_invalid_url, false); - return false; - } - return true; - } - }); - serverInternalUrl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object value) { - try { - String url = (String) value; - // Allow blank internal IP address - if("".equals(url) || url == null) { - return true; - } - - new URL(url); - if (url.contains(" ") || url.contains("@") || url.contains("_")) { - throw new Exception(); - } - } catch (Exception x) { - new ErrorDialog(SettingsActivity.this, R.string.settings_invalid_url, false); - return false; - } - return true; - } - }); - - username.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object value) { - String username = (String) value; - if (username == null || !username.equals(username.trim())) { - new ErrorDialog(SettingsActivity.this, R.string.settings_invalid_username, false); - return false; - } - return true; - } - }); - } - - public void update() { - serverName.setSummary(serverName.getText()); - serverUrl.setSummary(serverUrl.getText()); - serverLocalNetworkSSID.setSummary(serverLocalNetworkSSID.getText()); - serverInternalUrl.setSummary(serverInternalUrl.getText()); - username.setSummary(username.getText()); - screen.setSummary(serverUrl.getText()); - screen.setTitle(serverName.getText()); - } } } diff --git a/src/github/daneren2005/dsub/fragments/SettingsFragment.java b/src/github/daneren2005/dsub/fragments/SettingsFragment.java new file mode 100644 index 00000000..e040e59e --- /dev/null +++ b/src/github/daneren2005/dsub/fragments/SettingsFragment.java @@ -0,0 +1,700 @@ +/* + 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 . + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.fragments; + +import android.accounts.Account; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; +import android.text.InputType; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.URL; +import java.text.DecimalFormat; +import java.util.LinkedHashMap; +import java.util.Map; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.service.MusicServiceFactory; +import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.FileUtil; +import github.daneren2005.dsub.util.LoadingTask; +import github.daneren2005.dsub.util.SyncUtil; +import github.daneren2005.dsub.util.Util; +import github.daneren2005.dsub.view.ErrorDialog; + +public class SettingsFragment extends PreferenceCompatFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + private final static String TAG = SettingsFragment.class.getSimpleName(); + private final Map serverSettings = new LinkedHashMap(); + private boolean testingConnection; + private ListPreference theme; + private ListPreference maxBitrateWifi; + private ListPreference maxBitrateMobile; + private ListPreference maxVideoBitrateWifi; + private ListPreference maxVideoBitrateMobile; + private ListPreference networkTimeout; + private EditTextPreference cacheLocation; + private ListPreference preloadCountWifi; + private ListPreference preloadCountMobile; + private ListPreference tempLoss; + private ListPreference pauseDisconnect; + private Preference addServerPreference; + private PreferenceCategory serversCategory; + private ListPreference videoPlayer; + private ListPreference syncInterval; + private CheckBoxPreference syncEnabled; + private CheckBoxPreference syncWifi; + private CheckBoxPreference syncNotification; + private CheckBoxPreference syncStarred; + private CheckBoxPreference syncMostRecent; + private CheckBoxPreference replayGain; + private ListPreference replayGainType; + private Preference replayGainBump; + private Preference replayGainUntagged; + private String internalSSID; + private String internalSSIDDisplay; + private EditTextPreference cacheSize; + + private int serverCount = 3; + private SharedPreferences settings; + private DecimalFormat megabyteFromat; + + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + this.setTitle(getResources().getString(R.string.settings_title)); + initSettings(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + SharedPreferences prefs = Util.getPreferences(context); + prefs.unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + // Random error I have no idea how to reproduce + if(sharedPreferences == null) { + return; + } + + update(); + + if (Constants.PREFERENCES_KEY_HIDE_MEDIA.equals(key)) { + setHideMedia(sharedPreferences.getBoolean(key, false)); + } + else if (Constants.PREFERENCES_KEY_MEDIA_BUTTONS.equals(key)) { + setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true)); + } + else if (Constants.PREFERENCES_KEY_CACHE_LOCATION.equals(key)) { + setCacheLocation(sharedPreferences.getString(key, "")); + } + else if (Constants.PREFERENCES_KEY_SLEEP_TIMER_DURATION.equals(key)){ + DownloadService downloadService = DownloadService.getInstance(); + downloadService.setSleepTimerDuration(Integer.parseInt(sharedPreferences.getString(key, "60"))); + } + else if(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT.equals(key)) { + SyncUtil.removeMostRecentSyncFiles(context); + } else if(Constants.PREFERENCES_KEY_REPLAY_GAIN.equals(key) || Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP.equals(key) || Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED.equals(key)) { + DownloadService downloadService = DownloadService.getInstance(); + if(downloadService != null) { + downloadService.reapplyVolume(); + } + } + + scheduleBackup(); + } + + private void initSettings() { + internalSSID = Util.getSSID(context); + if(internalSSID == null) { + internalSSID = ""; + } + internalSSIDDisplay = context.getResources().getString(R.string.settings_server_local_network_ssid_hint, internalSSID); + + theme = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_THEME); + maxBitrateWifi = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); + maxBitrateMobile = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); + maxVideoBitrateWifi = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI); + maxVideoBitrateMobile = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE); + networkTimeout = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT); + cacheLocation = (EditTextPreference) this.findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION); + preloadCountWifi = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI); + preloadCountMobile = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE); + tempLoss = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_TEMP_LOSS); + pauseDisconnect = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PAUSE_DISCONNECT); + serversCategory = (PreferenceCategory) this.findPreference(Constants.PREFERENCES_KEY_SERVER_KEY); + addServerPreference = this.findPreference(Constants.PREFERENCES_KEY_SERVER_ADD); + videoPlayer = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER); + syncInterval = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_INTERVAL); + syncEnabled = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_ENABLED); + syncWifi = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_WIFI); + syncNotification = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_NOTIFICATION); + syncStarred = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_STARRED); + syncMostRecent = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT); + replayGain = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN); + replayGainType = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_TYPE); + replayGainBump = this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP); + replayGainUntagged = this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED); + cacheSize = (EditTextPreference) this.findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); + + settings = Util.getPreferences(context); + serverCount = settings.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1); + + this.findPreference("clearCache").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Util.confirmDialog(context, R.string.common_delete, R.string.common_confirm_message_cache, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + new LoadingTask(context, false) { + @Override + protected Void doInBackground() throws Throwable { + FileUtil.deleteMusicDirectory(context); + FileUtil.deleteSerializedCache(context); + return null; + } + + @Override + protected void done(Void result) { + Util.toast(context, R.string.settings_cache_clear_complete); + } + + @Override + protected void error(Throwable error) { + Util.toast(context, getErrorMessage(error), false); + } + }.execute(); + } + }); + return false; + } + }); + + addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + serverCount++; + String instance = String.valueOf(serverCount); + serversCategory.addPreference(addServer(serverCount)); + + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount); + // Reset set folder ID + editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null); + editor.commit(); + + serverSettings.put(instance, new ServerSettings(instance)); + + return true; + } + }); + + this.findPreference(Constants.PREFERENCES_KEY_SYNC_ENABLED).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Boolean syncEnabled = (Boolean) newValue; + + Account account = new Account(Constants.SYNC_ACCOUNT_NAME, Constants.SYNC_ACCOUNT_TYPE); + ContentResolver.setSyncAutomatically(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, syncEnabled); + ContentResolver.setSyncAutomatically(account, Constants.SYNC_ACCOUNT_PODCAST_AUTHORITY, syncEnabled); + + return true; + } + }); + syncInterval.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Integer syncInterval = Integer.parseInt(((String) newValue)); + + Account account = new Account(Constants.SYNC_ACCOUNT_NAME, Constants.SYNC_ACCOUNT_TYPE); + ContentResolver.addPeriodicSync(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, new Bundle(), 60L * syncInterval); + ContentResolver.addPeriodicSync(account, Constants.SYNC_ACCOUNT_PODCAST_AUTHORITY, new Bundle(), 60L * syncInterval); + + return true; + } + }); + + serversCategory.setOrderingAsAdded(false); + for (int i = 1; i <= serverCount; i++) { + String instance = String.valueOf(i); + serversCategory.addPreference(addServer(i)); + serverSettings.put(instance, new ServerSettings(instance)); + } + + SharedPreferences prefs = Util.getPreferences(context); + prefs.registerOnSharedPreferenceChangeListener(this); + + update(); + } + + private void scheduleBackup() { + try { + Class managerClass = Class.forName("android.app.backup.BackupManager"); + Constructor managerConstructor = managerClass.getConstructor(Context.class); + Object manager = managerConstructor.newInstance(context); + Method m = managerClass.getMethod("dataChanged"); + m.invoke(manager); + } catch(ClassNotFoundException e) { + Log.e(TAG, "No backup manager found"); + } catch(Throwable t) { + Log.e(TAG, "Scheduling backup failed " + t); + t.printStackTrace(); + } + } + + private void update() { + if (testingConnection) { + return; + } + + theme.setSummary(theme.getEntry()); + maxBitrateWifi.setSummary(maxBitrateWifi.getEntry()); + maxBitrateMobile.setSummary(maxBitrateMobile.getEntry()); + maxVideoBitrateWifi.setSummary(maxVideoBitrateWifi.getEntry()); + maxVideoBitrateMobile.setSummary(maxVideoBitrateMobile.getEntry()); + networkTimeout.setSummary(networkTimeout.getEntry()); + cacheLocation.setSummary(cacheLocation.getText()); + preloadCountWifi.setSummary(preloadCountWifi.getEntry()); + preloadCountMobile.setSummary(preloadCountMobile.getEntry()); + tempLoss.setSummary(tempLoss.getEntry()); + pauseDisconnect.setSummary(pauseDisconnect.getEntry()); + videoPlayer.setSummary(videoPlayer.getEntry()); + syncInterval.setSummary(syncInterval.getEntry()); + try { + if(megabyteFromat == null) { + megabyteFromat = new DecimalFormat(getResources().getString(R.string.util_bytes_format_megabyte)); + } + + cacheSize.setSummary(megabyteFromat.format((double) Integer.parseInt(cacheSize.getText())).replace(".00", "")); + } catch(Exception e) { + Log.e(TAG, "Failed to format cache size", e); + cacheSize.setSummary(cacheSize.getText()); + } + if(syncEnabled.isChecked()) { + if(!syncInterval.isEnabled()) { + syncInterval.setEnabled(true); + syncWifi.setEnabled(true); + syncNotification.setEnabled(true); + syncStarred.setEnabled(true); + syncMostRecent.setEnabled(true); + } + } else { + if(syncInterval.isEnabled()) { + syncInterval.setEnabled(false); + syncWifi.setEnabled(false); + syncNotification.setEnabled(false); + syncStarred.setEnabled(false); + syncMostRecent.setEnabled(false); + } + } + if(replayGain.isChecked()) { + replayGainType.setEnabled(true); + replayGainBump.setEnabled(true); + replayGainUntagged.setEnabled(true); + } else { + replayGainType.setEnabled(false); + replayGainBump.setEnabled(false); + replayGainUntagged.setEnabled(false); + } + replayGainType.setSummary(replayGainType.getEntry()); + + for (ServerSettings ss : serverSettings.values()) { + ss.update(); + } + } + + private PreferenceScreen addServer(final int instance) { + final PreferenceScreen screen = this.getPreferenceManager().createPreferenceScreen(context); + screen.setTitle(R.string.settings_server_unused); + screen.setKey(Constants.PREFERENCES_KEY_SERVER_KEY + instance); + + final EditTextPreference serverNamePreference = new EditTextPreference(context); + serverNamePreference.setKey(Constants.PREFERENCES_KEY_SERVER_NAME + instance); + serverNamePreference.setDefaultValue(getResources().getString(R.string.settings_server_unused)); + serverNamePreference.setTitle(R.string.settings_server_name); + serverNamePreference.setDialogTitle(R.string.settings_server_name); + + if (serverNamePreference.getText() == null) { + serverNamePreference.setText(getResources().getString(R.string.settings_server_unused)); + } + + serverNamePreference.setSummary(serverNamePreference.getText()); + + final EditTextPreference serverUrlPreference = new EditTextPreference(context); + serverUrlPreference.setKey(Constants.PREFERENCES_KEY_SERVER_URL + instance); + serverUrlPreference.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI); + serverUrlPreference.setDefaultValue("http://yourhost"); + serverUrlPreference.setTitle(R.string.settings_server_address); + serverUrlPreference.setDialogTitle(R.string.settings_server_address); + + if (serverUrlPreference.getText() == null) { + serverUrlPreference.setText("http://yourhost"); + } + + serverUrlPreference.setSummary(serverUrlPreference.getText()); + screen.setSummary(serverUrlPreference.getText()); + + final EditTextPreference serverLocalNetworkSSIDPreference = new EditTextPreference(context) { + @Override + protected void onAddEditTextToDialogView(View dialogView, final EditText editText) { + super.onAddEditTextToDialogView(dialogView, editText); + ViewGroup root = (ViewGroup) ((ViewGroup) dialogView).getChildAt(0); + + Button defaultButton = new Button(getContext()); + defaultButton.setText(internalSSIDDisplay); + defaultButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + editText.setText(internalSSID); + } + }); + root.addView(defaultButton); + } + }; + serverLocalNetworkSSIDPreference.setKey(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance); + serverLocalNetworkSSIDPreference.setTitle(R.string.settings_server_local_network_ssid); + serverLocalNetworkSSIDPreference.setDialogTitle(R.string.settings_server_local_network_ssid); + + final EditTextPreference serverInternalUrlPreference = new EditTextPreference(context); + serverInternalUrlPreference.setKey(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance); + serverInternalUrlPreference.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI); + serverInternalUrlPreference.setDefaultValue(""); + serverInternalUrlPreference.setTitle(R.string.settings_server_internal_address); + serverInternalUrlPreference.setDialogTitle(R.string.settings_server_internal_address); + serverInternalUrlPreference.setSummary(serverInternalUrlPreference.getText()); + + final EditTextPreference serverUsernamePreference = new EditTextPreference(context); + serverUsernamePreference.setKey(Constants.PREFERENCES_KEY_USERNAME + instance); + serverUsernamePreference.setTitle(R.string.settings_server_username); + serverUsernamePreference.setDialogTitle(R.string.settings_server_username); + + final EditTextPreference serverPasswordPreference = new EditTextPreference(context); + serverPasswordPreference.setKey(Constants.PREFERENCES_KEY_PASSWORD + instance); + serverPasswordPreference.getEditText().setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + serverPasswordPreference.setSummary("***"); + serverPasswordPreference.setTitle(R.string.settings_server_password); + + final CheckBoxPreference serverTagPreference = new CheckBoxPreference(context); + serverTagPreference.setKey(Constants.PREFERENCES_KEY_BROWSE_TAGS + instance); + serverTagPreference.setChecked(Util.isTagBrowsing(context, instance)); + serverTagPreference.setSummary(R.string.settings_browse_by_tags_summary); + serverTagPreference.setTitle(R.string.settings_browse_by_tags); + serverPasswordPreference.setDialogTitle(R.string.settings_server_password); + + final CheckBoxPreference serverSyncPreference = new CheckBoxPreference(context); + serverSyncPreference.setKey(Constants.PREFERENCES_KEY_SERVER_SYNC + instance); + serverSyncPreference.setChecked(Util.isSyncEnabled(context, instance)); + serverSyncPreference.setSummary(R.string.settings_server_sync_summary); + serverSyncPreference.setTitle(R.string.settings_server_sync); + + final Preference serverOpenBrowser = new Preference(context); + serverOpenBrowser.setKey(Constants.PREFERENCES_KEY_OPEN_BROWSER); + serverOpenBrowser.setPersistent(false); + serverOpenBrowser.setTitle(R.string.settings_server_open_browser); + serverOpenBrowser.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + openInBrowser(instance); + return true; + } + }); + + Preference serverRemoveServerPreference = new Preference(context); + serverRemoveServerPreference.setKey(Constants.PREFERENCES_KEY_SERVER_REMOVE + instance); + serverRemoveServerPreference.setPersistent(false); + serverRemoveServerPreference.setTitle(R.string.settings_servers_remove); + + serverRemoveServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Util.confirmDialog(context, R.string.common_delete, screen.getTitle().toString(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Reset values to null so when we ask for them again they are new + serverNamePreference.setText(null); + serverUrlPreference.setText(null); + serverUsernamePreference.setText(null); + serverPasswordPreference.setText(null); + + int activeServer = Util.getActiveServer(context); + for (int i = instance; i <= serverCount; i++) { + Util.removeInstanceName(context, i, activeServer); + } + + serverCount--; + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount); + editor.commit(); + + serversCategory.removePreference(screen); + screen.getDialog().dismiss(); + } + }); + + return true; + } + }); + + Preference serverTestConnectionPreference = new Preference(context); + serverTestConnectionPreference.setKey(Constants.PREFERENCES_KEY_TEST_CONNECTION + instance); + serverTestConnectionPreference.setPersistent(false); + serverTestConnectionPreference.setTitle(R.string.settings_test_connection_title); + serverTestConnectionPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + testConnection(instance); + return false; + } + }); + + screen.addPreference(serverNamePreference); + screen.addPreference(serverUrlPreference); + screen.addPreference(serverInternalUrlPreference); + screen.addPreference(serverLocalNetworkSSIDPreference); + screen.addPreference(serverUsernamePreference); + screen.addPreference(serverPasswordPreference); + screen.addPreference(serverTagPreference); + screen.addPreference(serverSyncPreference); + screen.addPreference(serverTestConnectionPreference); + screen.addPreference(serverOpenBrowser); + screen.addPreference(serverRemoveServerPreference); + + screen.setOrder(instance); + + return screen; + } + + private void setHideMedia(boolean hide) { + File nomediaDir = new File(FileUtil.getSubsonicDirectory(context), ".nomedia"); + File musicNoMedia = new File(FileUtil.getMusicDirectory(context), ".nomedia"); + if (hide && !nomediaDir.exists()) { + try { + if (!nomediaDir.createNewFile()) { + Log.w(TAG, "Failed to create " + nomediaDir); + } + } catch(Exception e) { + Log.w(TAG, "Failed to create " + nomediaDir, e); + } + + try { + if(!musicNoMedia.createNewFile()) { + Log.w(TAG, "Failed to create " + musicNoMedia); + } + } catch(Exception e) { + Log.w(TAG, "Failed to create " + musicNoMedia, e); + } + } else if (nomediaDir.exists()) { + if (!nomediaDir.delete()) { + Log.w(TAG, "Failed to delete " + nomediaDir); + } + if(!musicNoMedia.delete()) { + Log.w(TAG, "Failed to delete " + musicNoMedia); + } + } + Util.toast(context, R.string.settings_hide_media_toast, false); + } + + private void setMediaButtonsEnabled(boolean enabled) { + if (enabled) { + Util.registerMediaButtonEventReceiver(context); + } else { + Util.unregisterMediaButtonEventReceiver(context); + } + } + + private void setCacheLocation(String path) { + File dir = new File(path); + if (!FileUtil.verifyCanWrite(dir)) { + Util.toast(context, R.string.settings_cache_location_error, false); + + // Reset it to the default. + String defaultPath = FileUtil.getDefaultMusicDirectory(context).getPath(); + if (!defaultPath.equals(path)) { + SharedPreferences prefs = Util.getPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultPath); + editor.commit(); + cacheLocation.setSummary(defaultPath); + cacheLocation.setText(defaultPath); + } + + // Clear download queue. + DownloadService downloadService = DownloadService.getInstance(); + downloadService.clear(); + } + } + + private void testConnection(final int instance) { + LoadingTask task = new LoadingTask(context) { + private int previousInstance; + + @Override + protected Boolean doInBackground() throws Throwable { + updateProgress(R.string.settings_testing_connection); + + previousInstance = Util.getActiveServer(context); + testingConnection = true; + MusicService musicService = MusicServiceFactory.getMusicService(context); + try { + musicService.setInstance(instance); + musicService.ping(context, this); + return musicService.isLicenseValid(context, null); + } finally { + musicService.setInstance(null); + testingConnection = false; + } + } + + @Override + protected void done(Boolean licenseValid) { + if (licenseValid) { + Util.toast(context, R.string.settings_testing_ok); + } else { + Util.toast(context, R.string.settings_testing_unlicensed); + } + } + + @Override + public void cancel() { + super.cancel(); + Util.setActiveServer(context, previousInstance); + } + + @Override + protected void error(Throwable error) { + Log.w(TAG, error.toString(), error); + new ErrorDialog(context, getResources().getString(R.string.settings_connection_failure) + + " " + getErrorMessage(error), false); + } + }; + task.execute(); + } + + private void openInBrowser(final int instance) { + SharedPreferences prefs = Util.getPreferences(context); + String url = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null); + if(url == null) { + new ErrorDialog(context, R.string.settings_invalid_url, false); + return; + } + Uri uriServer = Uri.parse(url); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, uriServer); + startActivity(browserIntent); + } + + private class ServerSettings { + private EditTextPreference serverName; + private EditTextPreference serverUrl; + private EditTextPreference serverLocalNetworkSSID; + private EditTextPreference serverInternalUrl; + private EditTextPreference username; + private PreferenceScreen screen; + + private ServerSettings(String instance) { + screen = (PreferenceScreen) SettingsFragment.this.findPreference("server" + instance); + serverName = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_NAME + instance); + serverUrl = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_URL + instance); + serverLocalNetworkSSID = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance); + serverInternalUrl = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance); + username = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_USERNAME + instance); + + serverUrl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + try { + String url = (String) value; + new URL(url); + if (url.contains(" ") || url.contains("@") || url.contains("_")) { + throw new Exception(); + } + } catch (Exception x) { + new ErrorDialog(context, R.string.settings_invalid_url, false); + return false; + } + return true; + } + }); + serverInternalUrl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + try { + String url = (String) value; + // Allow blank internal IP address + if("".equals(url) || url == null) { + return true; + } + + new URL(url); + if (url.contains(" ") || url.contains("@") || url.contains("_")) { + throw new Exception(); + } + } catch (Exception x) { + new ErrorDialog(context, R.string.settings_invalid_url, false); + return false; + } + return true; + } + }); + + username.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + String username = (String) value; + if (username == null || !username.equals(username.trim())) { + new ErrorDialog(context, R.string.settings_invalid_username, false); + return false; + } + return true; + } + }); + } + + public void update() { + serverName.setSummary(serverName.getText()); + serverUrl.setSummary(serverUrl.getText()); + serverLocalNetworkSSID.setSummary(serverLocalNetworkSSID.getText()); + serverInternalUrl.setSummary(serverInternalUrl.getText()); + username.setSummary(username.getText()); + screen.setSummary(serverUrl.getText()); + screen.setTitle(serverName.getText()); + } + } +} -- cgit v1.2.3 From 3035c5b9a4c0d0edbd03c959b1956635d470be94 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 29 Dec 2014 16:48:12 -0800 Subject: Fix another crash when recreating fragment later --- src/github/daneren2005/dsub/fragments/SettingsFragment.java | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/SettingsFragment.java b/src/github/daneren2005/dsub/fragments/SettingsFragment.java index e040e59e..1d08796d 100644 --- a/src/github/daneren2005/dsub/fragments/SettingsFragment.java +++ b/src/github/daneren2005/dsub/fragments/SettingsFragment.java @@ -31,6 +31,7 @@ import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; import android.text.InputType; import android.util.Log; +import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -95,8 +96,16 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { + View root = super.onCreateView(inflater, container, bundle); + this.setTitle(getResources().getString(R.string.settings_title)); initSettings(); + + return root; } @Override -- cgit v1.2.3 From 4a63563b47710fb1cdc214bbc8ad71fd961a1db6 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Tue, 30 Dec 2014 08:31:04 -0800 Subject: Move settings fragment to be placed like standard so restore logic works --- src/github/daneren2005/dsub/activity/SettingsActivity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/activity/SettingsActivity.java b/src/github/daneren2005/dsub/activity/SettingsActivity.java index 24188c39..d5ac60d3 100644 --- a/src/github/daneren2005/dsub/activity/SettingsActivity.java +++ b/src/github/daneren2005/dsub/activity/SettingsActivity.java @@ -73,7 +73,7 @@ public class SettingsActivity extends SubsonicActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(0); + setContentView(R.layout.download_activity); if (savedInstanceState == null) { fragment = new SettingsFragment(); @@ -82,8 +82,10 @@ public class SettingsActivity extends SubsonicActivity { fragment.setArguments(args); fragment.setRetainInstance(true); - this.getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, fragment, null).commit(); + currentFragment = fragment; + currentFragment.setPrimaryFragment(true); + getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, currentFragment, currentFragment.getSupportTag() + "").commit(); } } } -- cgit v1.2.3 From a8d2fcf7aa8466007965dd64aa4b567723cd89d0 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 3 Jan 2015 12:24:50 -0800 Subject: Delete artwork/avatars from cache on clear cache as well --- .../daneren2005/dsub/fragments/SettingsFragment.java | 2 ++ src/github/daneren2005/dsub/util/FileUtil.java | 8 ++++++++ src/github/daneren2005/dsub/util/Util.java | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/SettingsFragment.java b/src/github/daneren2005/dsub/fragments/SettingsFragment.java index 1d08796d..8dfca3b7 100644 --- a/src/github/daneren2005/dsub/fragments/SettingsFragment.java +++ b/src/github/daneren2005/dsub/fragments/SettingsFragment.java @@ -197,6 +197,8 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared protected Void doInBackground() throws Throwable { FileUtil.deleteMusicDirectory(context); FileUtil.deleteSerializedCache(context); + FileUtil.deleteArtworkCache(context); + FileUtil.deleteAvatarCache(context); return null; } diff --git a/src/github/daneren2005/dsub/util/FileUtil.java b/src/github/daneren2005/dsub/util/FileUtil.java index ac2c080b..f59f9363 100644 --- a/src/github/daneren2005/dsub/util/FileUtil.java +++ b/src/github/daneren2005/dsub/util/FileUtil.java @@ -442,6 +442,14 @@ public class FileUtil { } } } + public static boolean deleteArtworkCache(Context context) { + File artDirectory = FileUtil.getAlbumArtDirectory(context); + return Util.recursiveDelete(artDirectory); + } + public static boolean deleteAvatarCache(Context context) { + File artDirectory = FileUtil.getAvatarDirectory(context); + return Util.recursiveDelete(artDirectory); + } public static void unpinSong(Context context, File saveFile) { // Don't try to unpin a song which isn't actually pinned diff --git a/src/github/daneren2005/dsub/util/Util.java b/src/github/daneren2005/dsub/util/Util.java index 7b905d11..ae0d668c 100644 --- a/src/github/daneren2005/dsub/util/Util.java +++ b/src/github/daneren2005/dsub/util/Util.java @@ -585,6 +585,26 @@ public final class Util { } return true; } + public static boolean recursiveDelete(File dir) { + if (dir != null && dir.exists()) { + File[] list = dir.listFiles(); + if(list != null) { + for(File file: list) { + if(file.isDirectory()) { + if(!recursiveDelete(file)) { + return false; + } + } else if(file.exists()) { + if(!file.delete()) { + return false; + } + } + } + } + return dir.delete(); + } + return false; + } public static boolean recursiveDelete(File dir, MediaStoreService mediaStore) { if (dir != null && dir.exists()) { File[] list = dir.listFiles(); -- cgit v1.2.3 From ec63a0fc0c28c5b9d8af6c972e8ef6ec5ab4e0cf Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 3 Jan 2015 12:32:09 -0800 Subject: Put delete in a background loader --- .../dsub/fragments/SubsonicFragment.java | 38 ++++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/SubsonicFragment.java b/src/github/daneren2005/dsub/fragments/SubsonicFragment.java index 6e588ec4..5a190439 100644 --- a/src/github/daneren2005/dsub/fragments/SubsonicFragment.java +++ b/src/github/daneren2005/dsub/fragments/SubsonicFragment.java @@ -1343,25 +1343,35 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR } public void deleteRecursively(Artist artist) { - File dir = FileUtil.getArtistDirectory(context, artist); - if(dir == null) return; - - MediaStoreService mediaStore = new MediaStoreService(context); - Util.recursiveDelete(dir, mediaStore); - if(Util.isOffline(context)) { - refresh(); - } + deleteRecursively(FileUtil.getArtistDirectory(context, artist)); } public void deleteRecursively(Entry album) { - File dir = FileUtil.getAlbumDirectory(context, album); - if(dir == null) return; + deleteRecursively(FileUtil.getAlbumDirectory(context, album)); - MediaStoreService mediaStore = new MediaStoreService(context); - Util.recursiveDelete(dir, mediaStore); - if(Util.isOffline(context)) { - refresh(); + } + public void deleteRecursively(final File dir) { + if(dir == null) { + return; } + + new LoadingTask(context) { + @Override + protected Void doInBackground() throws Throwable { + MediaStoreService mediaStore = new MediaStoreService(context); + Util.recursiveDelete(dir, mediaStore); + return null; + } + + @Override + protected void done(Void result) { + if(Util.isOffline(context)) { + refresh(); + } else { + UpdateView.triggerUpdate(); + } + } + }.execute(); } public void showAlbumArtist(Entry entry) { -- cgit v1.2.3 From 1023f1350dedf2d9f6b5a40c0acb156b3602cd58 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 3 Jan 2015 20:09:11 -0800 Subject: Fix crash if title is null for any reason --- src/github/daneren2005/dsub/activity/SubsonicActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/activity/SubsonicActivity.java b/src/github/daneren2005/dsub/activity/SubsonicActivity.java index 09a1995e..f73bccb8 100644 --- a/src/github/daneren2005/dsub/activity/SubsonicActivity.java +++ b/src/github/daneren2005/dsub/activity/SubsonicActivity.java @@ -379,7 +379,7 @@ public class SubsonicActivity extends ActionBarActivity implements OnItemSelecte @Override public void setTitle(CharSequence title) { - if(!title.equals(getSupportActionBar().getTitle())) { + if(title != null && !title.equals(getSupportActionBar().getTitle())) { getSupportActionBar().setTitle(title); recreateSpinner(); } -- cgit v1.2.3 From f34bfb4d4408ba29de1b84367cba538a69bfedcb Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 3 Jan 2015 20:11:59 -0800 Subject: Don't recycle cached unknown images --- src/github/daneren2005/dsub/util/ImageLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/util/ImageLoader.java b/src/github/daneren2005/dsub/util/ImageLoader.java index 48ff39ca..be3f7d1b 100644 --- a/src/github/daneren2005/dsub/util/ImageLoader.java +++ b/src/github/daneren2005/dsub/util/ImageLoader.java @@ -79,7 +79,7 @@ public class ImageLoader { @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldBitmap, Bitmap newBitmap) { if(evicted) { - if(oldBitmap != nowPlaying) { + if(oldBitmap != nowPlaying && key.indexOf("unknown") == -1) { if(sizeOf("", oldBitmap) > 500) { oldBitmap.recycle(); } -- cgit v1.2.3 From 9bc4925ff0a07415b58513044ac4ce1d64caeb6c Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 3 Jan 2015 20:52:18 -0800 Subject: Add Similar Artists button under artist's options --- res/menu/select_album.xml | 4 + res/values/strings.xml | 1 + src/github/daneren2005/dsub/domain/ArtistInfo.java | 67 ++++++++++++ .../dsub/fragments/SelectDirectoryFragment.java | 12 ++ .../dsub/fragments/SimilarArtistFragment.java | 121 +++++++++++++++++++++ .../dsub/service/CachedMusicService.java | 6 + .../daneren2005/dsub/service/MusicService.java | 3 + .../dsub/service/OfflineMusicService.java | 6 + .../daneren2005/dsub/service/RESTMusicService.java | 11 ++ .../dsub/service/parser/ArtistInfoParser.java | 69 ++++++++++++ 10 files changed, 300 insertions(+) create mode 100644 src/github/daneren2005/dsub/domain/ArtistInfo.java create mode 100644 src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java create mode 100644 src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java (limited to 'src') diff --git a/res/menu/select_album.xml b/res/menu/select_album.xml index fa887c28..39eb2206 100644 --- a/res/menu/select_album.xml +++ b/res/menu/select_album.xml @@ -17,6 +17,10 @@ android:id="@+id/menu_top_tracks" android:title="@string/menu.top_tracks"/> + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 3bcdec17..ebffb19b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -104,6 +104,7 @@ Rescan Server Set Rating Last.FM Top Tracks + Similar Artists Playlists Update Information diff --git a/src/github/daneren2005/dsub/domain/ArtistInfo.java b/src/github/daneren2005/dsub/domain/ArtistInfo.java new file mode 100644 index 00000000..325f1b6f --- /dev/null +++ b/src/github/daneren2005/dsub/domain/ArtistInfo.java @@ -0,0 +1,67 @@ +/* + 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 . + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.domain; + +import java.io.Serializable; +import java.util.List; + +public class ArtistInfo implements Serializable { + private String biography; + private String musicBrainzId; + private String lastFMUrl; + private String imageUrl; + private List similarArtists; + + public String getBiography() { + return biography; + } + + public void setBiography(String biography) { + this.biography = biography; + } + + public String getMusicBrainzId() { + return musicBrainzId; + } + + public void setMusicBrainzId(String musicBrainzId) { + this.musicBrainzId = musicBrainzId; + } + + public String getLastFMUrl() { + return lastFMUrl; + } + + public void setLastFMUrl(String lastFMUrl) { + this.lastFMUrl = lastFMUrl; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public List getSimilarArtists() { + return similarArtists; + } + + public void setSimilarArtists(List similarArtists) { + this.similarArtists = similarArtists; + } +} diff --git a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 3fbb90cf..fa9eea36 100644 --- a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -298,6 +298,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter case R.id.menu_top_tracks: showTopTracks(); return true; + case R.id.menu_similar_artists: + showSimilarArtists(id); + return true; } return super.onOptionsItemSelected(item); @@ -1211,6 +1214,15 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter replaceFragment(fragment, true); } + private void showSimilarArtists(String artistId) { + SubsonicFragment fragment = new SimilarArtistFragment(); + Bundle args = new Bundle(); + args.putString(Constants.INTENT_EXTRA_NAME_ARTIST, artistId); + fragment.setArguments(args); + + replaceFragment(fragment, true); + } + private View createHeader(List entries) { View header = entryList.findViewById(R.id.select_album_header); boolean add = false; diff --git a/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java b/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java new file mode 100644 index 00000000..8fdd6994 --- /dev/null +++ b/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java @@ -0,0 +1,121 @@ +/* + 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 . + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.fragments; + +import android.os.Build; +import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.ArtistInfo; +import github.daneren2005.dsub.domain.Indexes; +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.MusicFolder; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.service.MusicServiceFactory; +import github.daneren2005.dsub.util.BackgroundTask; +import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.ProgressListener; +import github.daneren2005.dsub.util.TabBackgroundTask; +import github.daneren2005.dsub.util.Util; +import github.daneren2005.dsub.view.ArtistAdapter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class SimilarArtistFragment extends SelectListFragment { + private static final String TAG = SimilarArtistFragment.class.getSimpleName(); + private String artistId; + + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + artist = true; + + artistId = getArguments().getString(Constants.INTENT_EXTRA_NAME_ARTIST); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Object entry = listView.getItemAtPosition(info.position); + onCreateContextMenu(menu, view, menuInfo, entry); + + recreateContextMenu(menu); + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + if(menuItem.getGroupId() != getSupportTag()) { + return false; + } + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + Artist artist = (Artist) listView.getItemAtPosition(info.position); + return onContextItemSelected(menuItem, artist); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Artist artist = (Artist) parent.getItemAtPosition(position); + SubsonicFragment fragment = new SelectDirectoryFragment(); + Bundle args = new Bundle(); + args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); + args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); + args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true); + fragment.setArguments(args); + + replaceFragment(fragment); + } + + @Override + public int getOptionsMenu() { + return R.menu.empty; + } + + @Override + public ArrayAdapter getAdapter(List objects) { + return new ArtistAdapter(context, objects); + } + + @Override + public List getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception { + ArtistInfo info = musicService.getArtistInfo(artistId, refresh, context, listener); + return info.getSimilarArtists(); + } + + @Override + public int getTitleResource() { + return R.string.menu_similar_artists; + } +} diff --git a/src/github/daneren2005/dsub/service/CachedMusicService.java b/src/github/daneren2005/dsub/service/CachedMusicService.java index 8e8e120d..51f96ffb 100644 --- a/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -32,6 +32,7 @@ import android.content.Context; import android.graphics.Bitmap; import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.ChatMessage; import github.daneren2005.dsub.domain.Genre; @@ -890,6 +891,11 @@ public class CachedMusicService implements MusicService { return musicService.getAvatar(username, size, context, progressListener, task); } + @Override + public ArtistInfo getArtistInfo(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { + return musicService.getArtistInfo(id, refresh, context, progressListener); + } + @Override public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{ return musicService.processOfflineSyncs(context, progressListener); diff --git a/src/github/daneren2005/dsub/service/MusicService.java b/src/github/daneren2005/dsub/service/MusicService.java index 765f498a..64061191 100644 --- a/src/github/daneren2005/dsub/service/MusicService.java +++ b/src/github/daneren2005/dsub/service/MusicService.java @@ -25,6 +25,7 @@ import org.apache.http.HttpResponse; import android.content.Context; import android.graphics.Bitmap; +import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.ChatMessage; import github.daneren2005.dsub.domain.Genre; @@ -177,6 +178,8 @@ public interface MusicService { void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception; Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception; + + ArtistInfo getArtistInfo(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception; int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception; diff --git a/src/github/daneren2005/dsub/service/OfflineMusicService.java b/src/github/daneren2005/dsub/service/OfflineMusicService.java index 887ad84f..dc4651d2 100644 --- a/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -37,6 +37,7 @@ import android.util.Log; import org.apache.http.HttpResponse; import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.ChatMessage; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; @@ -782,6 +783,11 @@ public class OfflineMusicService implements MusicService { throw new OfflineException(ERRORMSG); } + @Override + public ArtistInfo getArtistInfo(String id, 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/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index c7ca2708..53d797b5 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -69,6 +69,7 @@ import android.util.Log; import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.*; import github.daneren2005.dsub.service.parser.AlbumListParser; +import github.daneren2005.dsub.service.parser.ArtistInfoParser; import github.daneren2005.dsub.service.parser.BookmarkParser; import github.daneren2005.dsub.service.parser.ChatMessageParser; import github.daneren2005.dsub.service.parser.ErrorParser; @@ -1378,6 +1379,16 @@ public class RESTMusicService implements MusicService { } } + @Override + public ArtistInfo getArtistInfo(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { + Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtistInfo2" : "getArtistInfo", null, Arrays.asList("id"), Arrays.asList(id)); + try { + return new ArtistInfoParser(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/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java b/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java new file mode 100644 index 00000000..9ffb354c --- /dev/null +++ b/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java @@ -0,0 +1,69 @@ +/* + 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 . + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.service.parser; + +import android.content.Context; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.ArtistInfo; +import github.daneren2005.dsub.util.ProgressListener; + +public class ArtistInfoParser extends AbstractParser { + + public ArtistInfoParser(Context context, int instance) { + super(context, instance); + } + + public ArtistInfo parse(Reader reader, ProgressListener progressListener) throws Exception { + init(reader); + + ArtistInfo info = new ArtistInfo(); + List artists = new ArrayList(); + + int eventType; + do { + eventType = nextParseEvent(); + if (eventType == XmlPullParser.START_TAG) { + String name = getElementName(); + if ("biography".equals(name)) { + info.setBiography(getText()); + } else if ("musicBrainzId".equals(name)) { + info.setMusicBrainzId(getText()); + } else if ("lastFmUrl".equals(name)) { + info.setLastFMUrl(getText()); + } else if ("largeImageUrl".equals(name)) { + info.setImageUrl(getText()); + } else if ("similarArtist".equals(name)) { + Artist artist = new Artist(); + artist.setId(get("id")); + artist.setName(get("name")); + artists.add(artist); + } else if ("error".equals(name)) { + handleError(); + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + + info.setSimilarArtists(artists); + return info; + } +} -- cgit v1.2.3 From 8a412ca3ace26f543dffa8ae88644b86682dd42c Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sun, 4 Jan 2015 12:58:03 -0800 Subject: Cache artist info --- src/github/daneren2005/dsub/service/CachedMusicService.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/service/CachedMusicService.java b/src/github/daneren2005/dsub/service/CachedMusicService.java index 51f96ffb..f3e91b38 100644 --- a/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -893,7 +893,18 @@ public class CachedMusicService implements MusicService { @Override public ArtistInfo getArtistInfo(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - return musicService.getArtistInfo(id, refresh, context, progressListener); + String cacheName = getCacheName(context, "artistInfo", id); + ArtistInfo info = null; + if(!refresh) { + info = FileUtil.deserialize(context, cacheName, ArtistInfo.class); + } + + if(info == null) { + info = musicService.getArtistInfo(id, refresh, context, progressListener); + FileUtil.serialize(context, info, cacheName); + } + + return info; } @Override -- cgit v1.2.3 From 6a62b5d859c202f48ceff89612538702210e2b9a Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sun, 4 Jan 2015 13:13:14 -0800 Subject: Only show similar artists for 5.1+ --- src/github/daneren2005/dsub/domain/Version.java | 2 ++ src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java | 3 +++ src/github/daneren2005/dsub/service/RESTMusicService.java | 2 ++ 3 files changed, 7 insertions(+) (limited to 'src') diff --git a/src/github/daneren2005/dsub/domain/Version.java b/src/github/daneren2005/dsub/domain/Version.java index f3566644..6b82ea99 100644 --- a/src/github/daneren2005/dsub/domain/Version.java +++ b/src/github/daneren2005/dsub/domain/Version.java @@ -88,6 +88,8 @@ public class Version implements Comparable, Serializable { return "4.8"; case 10: return "4.9"; + case 11: + return "5.1"; } } return ""; diff --git a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index fa9eea36..68857d41 100644 --- a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -210,6 +210,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter if(!ServerInfo.isMadsonic(context)) { menu.removeItem(R.id.menu_top_tracks); } + if(!ServerInfo.checkServerVersion(context, "1.11")) { + menu.removeItem(R.id.menu_similar_artists); + } } } else { if(podcastId == null) { diff --git a/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index 53d797b5..084c0f52 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -1381,6 +1381,8 @@ public class RESTMusicService implements MusicService { @Override public ArtistInfo getArtistInfo(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.11", "Getting artist info is not supported"); + Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtistInfo2" : "getArtistInfo", null, Arrays.asList("id"), Arrays.asList(id)); try { return new ArtistInfoParser(context, getInstance(context)).parse(reader, progressListener); -- cgit v1.2.3 From 323d1d98f373d3cb15f829675333c4dfec61bf2e Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sun, 4 Jan 2015 13:23:50 -0800 Subject: #438Added missing artists, starts to data pulled --- src/github/daneren2005/dsub/domain/ArtistInfo.java | 9 +++++++++ .../daneren2005/dsub/service/RESTMusicService.java | 2 +- .../dsub/service/parser/ArtistInfoParser.java | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/domain/ArtistInfo.java b/src/github/daneren2005/dsub/domain/ArtistInfo.java index 325f1b6f..2205d561 100644 --- a/src/github/daneren2005/dsub/domain/ArtistInfo.java +++ b/src/github/daneren2005/dsub/domain/ArtistInfo.java @@ -24,6 +24,7 @@ public class ArtistInfo implements Serializable { private String lastFMUrl; private String imageUrl; private List similarArtists; + private List missingArtists; public String getBiography() { return biography; @@ -64,4 +65,12 @@ public class ArtistInfo implements Serializable { public void setSimilarArtists(List similarArtists) { this.similarArtists = similarArtists; } + + public List getMissingArtists() { + return missingArtists; + } + + public void setMissingArtists(List missingArtists) { + this.missingArtists = missingArtists; + } } diff --git a/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index 084c0f52..239b2b43 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -1383,7 +1383,7 @@ public class RESTMusicService implements MusicService { public ArtistInfo getArtistInfo(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.11", "Getting artist info is not supported"); - Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtistInfo2" : "getArtistInfo", null, Arrays.asList("id"), Arrays.asList(id)); + Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtistInfo2" : "getArtistInfo", null, Arrays.asList("id", "includeNotPresent"), Arrays.asList(id, "true")); try { return new ArtistInfoParser(context, getInstance(context)).parse(reader, progressListener); } finally { diff --git a/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java b/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java index 9ffb354c..5b8dc796 100644 --- a/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java +++ b/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java @@ -38,6 +38,7 @@ public class ArtistInfoParser extends AbstractParser { ArtistInfo info = new ArtistInfo(); List artists = new ArrayList(); + List missingArtists = new ArrayList(); int eventType; do { @@ -53,10 +54,16 @@ public class ArtistInfoParser extends AbstractParser { } else if ("largeImageUrl".equals(name)) { info.setImageUrl(getText()); } else if ("similarArtist".equals(name)) { - Artist artist = new Artist(); - artist.setId(get("id")); - artist.setName(get("name")); - artists.add(artist); + String id = get("id"); + if(id.equals("-1")) { + missingArtists.add(get("name")); + } else { + Artist artist = new Artist(); + artist.setId(id); + artist.setName(get("name")); + artist.setStarred(get("starred") != null); + artists.add(artist); + } } else if ("error".equals(name)) { handleError(); } @@ -64,6 +71,7 @@ public class ArtistInfoParser extends AbstractParser { } while (eventType != XmlPullParser.END_DOCUMENT); info.setSimilarArtists(artists); + info.setMissingArtists(missingArtists); return info; } } -- cgit v1.2.3 From f7d19f2cf94263aedd9e173e19bf52816725fd17 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sun, 4 Jan 2015 13:56:47 -0800 Subject: #438 Add dialog to lookup missing artists --- res/menu/similar_artists.xml | 8 ++++ res/values/strings.xml | 1 + .../dsub/fragments/SimilarArtistFragment.java | 52 ++++++++++++++-------- src/github/daneren2005/dsub/util/Util.java | 7 ++- 4 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 res/menu/similar_artists.xml (limited to 'src') diff --git a/res/menu/similar_artists.xml b/res/menu/similar_artists.xml new file mode 100644 index 00000000..bffa1837 --- /dev/null +++ b/res/menu/similar_artists.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index ebffb19b..314fc9bf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -105,6 +105,7 @@ Set Rating Last.FM Top Tracks Similar Artists + Show missing Playlists Update Information diff --git a/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java b/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java index 8fdd6994..26d7fb5e 100644 --- a/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java +++ b/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java @@ -15,44 +15,33 @@ package github.daneren2005.dsub.fragments; -import android.os.Build; +import android.app.AlertDialog; import android.os.Bundle; -import android.support.v4.widget.SwipeRefreshLayout; -import android.util.Log; +import android.text.method.LinkMovementMethod; import android.view.ContextMenu; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.TextView; + import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.ArtistInfo; -import github.daneren2005.dsub.domain.Indexes; -import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.domain.MusicFolder; import github.daneren2005.dsub.service.MusicService; -import github.daneren2005.dsub.service.MusicServiceFactory; -import github.daneren2005.dsub.util.BackgroundTask; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.ProgressListener; -import github.daneren2005.dsub.util.TabBackgroundTask; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.view.ArtistAdapter; -import java.io.Serializable; -import java.util.ArrayList; +import java.net.URLEncoder; import java.util.List; public class SimilarArtistFragment extends SelectListFragment { private static final String TAG = SimilarArtistFragment.class.getSimpleName(); + private ArtistInfo info; private String artistId; @Override @@ -63,6 +52,21 @@ public class SimilarArtistFragment extends SelectListFragment { artistId = getArguments().getString(Constants.INTENT_EXTRA_NAME_ARTIST); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(super.onOptionsItemSelected(item)) { + return true; + } + + switch (item.getItemId()) { + case R.id.menu_show_missing: + showMissingArtists(); + break; + } + + return false; + } + @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); @@ -100,7 +104,7 @@ public class SimilarArtistFragment extends SelectListFragment { @Override public int getOptionsMenu() { - return R.menu.empty; + return R.menu.similar_artists; } @Override @@ -110,7 +114,7 @@ public class SimilarArtistFragment extends SelectListFragment { @Override public List getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception { - ArtistInfo info = musicService.getArtistInfo(artistId, refresh, context, listener); + info = musicService.getArtistInfo(artistId, refresh, context, listener); return info.getSimilarArtists(); } @@ -118,4 +122,16 @@ public class SimilarArtistFragment extends SelectListFragment { public int getTitleResource() { return R.string.menu_similar_artists; } + + private void showMissingArtists() { + StringBuilder b = new StringBuilder(); + + for(String name: info.getMissingArtists()) { + b.append("

" + name + "

"); + } + + Util.showHTMLDialog(context, R.string.menu_similar_artists, b.toString()); + + // Util.info(context, R.string.menu_similar_artists, b.toString()); + } } diff --git a/src/github/daneren2005/dsub/util/Util.java b/src/github/daneren2005/dsub/util/Util.java index ae0d668c..f899b9e4 100644 --- a/src/github/daneren2005/dsub/util/Util.java +++ b/src/github/daneren2005/dsub/util/Util.java @@ -1040,10 +1040,13 @@ public final class Util { ((TextView)dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } public static void showHTMLDialog(Context context, int title, int message) { + showHTMLDialog(context, title, context.getResources().getString(message)); + } + public static void showHTMLDialog(Context context, int title, String message) { AlertDialog dialog = new AlertDialog.Builder(context) .setIcon(android.R.drawable.ic_dialog_info) .setTitle(title) - .setMessage(Html.fromHtml(context.getResources().getString(message))) + .setMessage(Html.fromHtml(message)) .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int i) { @@ -1051,6 +1054,8 @@ public final class Util { } }) .show(); + + ((TextView)dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } public static void sleepQuietly(long millis) { -- cgit v1.2.3 From 97cb4a3bda6c28cc161f548beb86d45ecf33c93a Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 5 Jan 2015 08:39:41 -0800 Subject: Remove accidently commited comment --- src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java b/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java index 26d7fb5e..c029581b 100644 --- a/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java +++ b/src/github/daneren2005/dsub/fragments/SimilarArtistFragment.java @@ -131,7 +131,5 @@ public class SimilarArtistFragment extends SelectListFragment { } Util.showHTMLDialog(context, R.string.menu_similar_artists, b.toString()); - - // Util.info(context, R.string.menu_similar_artists, b.toString()); } } -- cgit v1.2.3 From 7f63410ff21dd2f7655365b899260fcbb76fbcee Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 5 Jan 2015 12:00:01 -0800 Subject: Simplify header creation/album menu logic --- .../dsub/fragments/SelectDirectoryFragment.java | 53 ++++++++++------------ 1 file changed, 23 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 68857d41..ae02c40f 100644 --- a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -1,10 +1,12 @@ package github.daneren2005.dsub.fragments; +import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.util.Log; @@ -60,9 +62,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private GridView albumList; private ListView entryList; - private boolean hideButtons = false; private Boolean licenseValid; - private boolean showHeader = true; private EntryAdapter entryAdapter; private List albums; private List entries; @@ -200,19 +200,16 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { if(licenseValid == null) { menuInflater.inflate(R.menu.empty, menu); - } - else if(hideButtons && !showAll) { - if(albumListType != null) { - menuInflater.inflate(R.menu.select_album_list, menu); - } else { - menuInflater.inflate(R.menu.select_album, menu); + } else if(albumListType != null) { + menuInflater.inflate(R.menu.select_album_list, menu); + } else if(artist && !showAll) { + menuInflater.inflate(R.menu.select_album, menu); - if(!ServerInfo.isMadsonic(context)) { - menu.removeItem(R.id.menu_top_tracks); - } - if(!ServerInfo.checkServerVersion(context, "1.11")) { - menu.removeItem(R.id.menu_similar_artists); - } + if(!ServerInfo.isMadsonic(context)) { + menu.removeItem(R.id.menu_top_tracks); + } + if(!ServerInfo.checkServerVersion(context, "1.11")) { + menu.removeItem(R.id.menu_similar_artists); } } else { if(podcastId == null) { @@ -599,8 +596,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } private void getAlbumList(final String albumListType, final int size) { - showHeader = false; - if ("newest".equals(albumListType)) { setTitle(R.string.main_albums_newest); } else if ("random".equals(albumListType)) { @@ -661,6 +656,11 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } else { entries = dir.getChildren(); } + + // This isn't really an artist if no albums on it! + if(albums.size() == 0) { + artist = false; + } return new Pair(dir, licenseValid); } @@ -673,19 +673,13 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } private void finishLoading() { - if (entries.size() > 0 && albums.size() == 0 && !"root".equals(id)) { - if(showHeader) { - View header = createHeader(entries); - if(header != null && entryList != null) { - entryList.addHeaderView(header, null, false); - } - } - } else { - showHeader = false; - if(!"root".equals(id) && (entries.size() == 0 || !largeAlbums && albums.size() == entries.size())) { - hideButtons = true; + // Show header if not album list type and not root and not artist + if(albumListType == null && !"root".equals(id) && !artist) { + View header = createHeader(entries); + if(header != null && entryList != null) { + entryList.addHeaderView(header, null, false); } - } + } // Needs to be added here, GB crashes if you to try to remove the header view before adapter is set if(addAlbumHeader) { @@ -1297,8 +1291,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } } if(songCount == 0) { - showHeader = false; - hideButtons = true; return null; } @@ -1310,6 +1302,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter artistView.setTextAppearance(context, android.R.style.TextAppearance_Small); artistView.setOnClickListener(new View.OnClickListener() { + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onClick(View v) { if(artistView.getMaxLines() == 5) { -- cgit v1.2.3 From 230c5764c69b2f2242d67559aed1336955d3db5a Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 9 Jan 2015 10:49:36 -0800 Subject: Fix incrementing randomly --- src/github/daneren2005/dsub/fragments/NowPlayingFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java b/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java index 942b0613..b3d0c845 100644 --- a/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java +++ b/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java @@ -1012,6 +1012,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis if(fromUser) { int length = getMinutes(progress); lengthBox.setText(Util.formatDuration(length)); + seekBar.setProgress(progress + 1); } } @@ -1031,7 +1032,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { - int length = getMinutes(lengthBar.getProgress()); + int length = getMinutes(lengthBar.getProgress() - 1); SharedPreferences.Editor editor = prefs.edit(); editor.putString(Constants.PREFERENCES_KEY_SLEEP_TIMER_DURATION, Integer.toString(length)); -- cgit v1.2.3 From eaa34272cc62237cfd3639e6a17eddd5741e8ce0 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 9 Jan 2015 10:52:56 -0800 Subject: Better fix for the issue now that I understand why --- src/github/daneren2005/dsub/fragments/NowPlayingFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java b/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java index b3d0c845..06c88863 100644 --- a/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java +++ b/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java @@ -1012,7 +1012,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis if(fromUser) { int length = getMinutes(progress); lengthBox.setText(Util.formatDuration(length)); - seekBar.setProgress(progress + 1); + seekBar.setProgress(progress); } } @@ -1024,7 +1024,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis public void onStopTrackingTouch(SeekBar seekBar) { } }); - lengthBar.setProgress(length); + lengthBar.setProgress(length - 1); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.menu_set_timer) @@ -1032,7 +1032,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { - int length = getMinutes(lengthBar.getProgress() - 1); + int length = getMinutes(lengthBar.getProgress()); SharedPreferences.Editor editor = prefs.edit(); editor.putString(Constants.PREFERENCES_KEY_SLEEP_TIMER_DURATION, Integer.toString(length)); -- cgit v1.2.3 From 2602d6405f53e9f3923dcb941b9f68e0df1f593a Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 12 Jan 2015 14:39:52 -0800 Subject: Fix bio, etc not being parsed correctly --- .../dsub/service/parser/ArtistInfoParser.java | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java b/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java index 5b8dc796..5c3d2412 100644 --- a/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java +++ b/src/github/daneren2005/dsub/service/parser/ArtistInfoParser.java @@ -16,6 +16,7 @@ package github.daneren2005.dsub.service.parser; import android.content.Context; +import android.util.Log; import org.xmlpull.v1.XmlPullParser; @@ -28,6 +29,7 @@ import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.util.ProgressListener; public class ArtistInfoParser extends AbstractParser { + private static final String TAG = ArtistInfo.class.getSimpleName(); public ArtistInfoParser(Context context, int instance) { super(context, instance); @@ -40,20 +42,13 @@ public class ArtistInfoParser extends AbstractParser { List artists = new ArrayList(); List missingArtists = new ArrayList(); + String tagName = null; int eventType; do { eventType = nextParseEvent(); if (eventType == XmlPullParser.START_TAG) { - String name = getElementName(); - if ("biography".equals(name)) { - info.setBiography(getText()); - } else if ("musicBrainzId".equals(name)) { - info.setMusicBrainzId(getText()); - } else if ("lastFmUrl".equals(name)) { - info.setLastFMUrl(getText()); - } else if ("largeImageUrl".equals(name)) { - info.setImageUrl(getText()); - } else if ("similarArtist".equals(name)) { + tagName = getElementName(); + if ("similarArtist".equals(tagName)) { String id = get("id"); if(id.equals("-1")) { missingArtists.add(get("name")); @@ -64,9 +59,19 @@ public class ArtistInfoParser extends AbstractParser { artist.setStarred(get("starred") != null); artists.add(artist); } - } else if ("error".equals(name)) { + } else if ("error".equals(tagName)) { handleError(); } + } else if(eventType == XmlPullParser.TEXT) { + if ("biography".equals(tagName) && info.getBiography() == null) { + info.setBiography(getText()); + } else if ("musicBrainzId".equals(tagName) && info.getMusicBrainzId() == null) { + info.setMusicBrainzId(getText()); + } else if ("lastFmUrl".equals(tagName) && info.getLastFMUrl() == null) { + info.setLastFMUrl(getText()); + } else if ("largeImageUrl".equals(tagName) && info.getImageUrl() == null) { + info.setImageUrl(getText()); + } } } while (eventType != XmlPullParser.END_DOCUMENT); -- cgit v1.2.3 From bd95555df0a465df1112a6178dcf213c6f73174b Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 12 Jan 2015 15:30:35 -0800 Subject: Start of displaying artist info as a header --- .../dsub/fragments/NowPlayingFragment.java | 4 +- .../dsub/fragments/SelectDirectoryFragment.java | 240 +++++++++++++-------- .../dsub/service/CachedMusicService.java | 5 + .../daneren2005/dsub/service/MusicService.java | 2 + .../dsub/service/OfflineMusicService.java | 5 + .../daneren2005/dsub/service/RESTMusicService.java | 63 +++++- src/github/daneren2005/dsub/util/FileUtil.java | 27 +++ src/github/daneren2005/dsub/util/ImageLoader.java | 89 +++++++- 8 files changed, 325 insertions(+), 110 deletions(-) (limited to 'src') diff --git a/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java b/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java index 06c88863..d3dea3bd 100644 --- a/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java +++ b/src/github/daneren2005/dsub/fragments/NowPlayingFragment.java @@ -869,7 +869,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis updateButtons(); if(currentPlaying == null && downloadService != null && currentPlaying == downloadService.getCurrentPlaying()) { - getImageLoader().loadImage(albumArtImageView, null, true, false); + getImageLoader().loadImage(albumArtImageView, (Entry) null, true, false); } if(downloadService != null) { downloadService.startRemoteScan(); @@ -1226,7 +1226,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis bookmarkButton.setImageResource(bookmark); } else { songTitleTextView.setText(null); - getImageLoader().loadImage(albumArtImageView, null, true, false); + getImageLoader().loadImage(albumArtImageView, (Entry) null, true, false); starButton.setImageResource(android.R.drawable.btn_star_big_off); setSubtitle(null); } diff --git a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index ae02c40f..9cb8c582 100644 --- a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -9,6 +9,9 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; +import android.text.Html; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -26,6 +29,7 @@ import android.widget.ListView; import android.widget.RatingBar; import android.widget.TextView; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.ServerInfo; import github.daneren2005.dsub.domain.Share; @@ -69,6 +73,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private boolean albumContext = false; private boolean addAlbumHeader = false; private LoadTask currentTask; + ArtistInfo artistInfo; String id; String name; @@ -89,7 +94,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter boolean largeAlbums = false; boolean topTracks = false; String lookupEntry; - + public SelectDirectoryFragment() { super(); } @@ -475,7 +480,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void getMusicDirectory(final String id, final String name, final boolean refresh) { setTitle(name); - new LoadTask() { + new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { MusicDirectory dir = getMusicDirectory(id, name, refresh, service, this); @@ -512,7 +517,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void getRecursiveMusicDirectory(final String id, final String name, final boolean refresh) { setTitle(name); - new LoadTask() { + new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { MusicDirectory root; @@ -554,7 +559,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void getPlaylist(final String playlistId, final String playlistName, final boolean refresh) { setTitle(playlistName); - new LoadTask() { + new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return service.getPlaylist(refresh, playlistId, playlistName, context, this); @@ -565,7 +570,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void getPodcast(final String podcastId, final String podcastName, final boolean refresh) { setTitle(podcastName); - new LoadTask() { + new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return service.getPodcastEpisodes(refresh, podcastId, context, this); @@ -576,7 +581,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void getShare(final Share share, final boolean refresh) { setTitle(share.getName()); - new LoadTask() { + new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return share.getMusicDirectory(); @@ -587,7 +592,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void getTopTracks(final String id, final String name, final boolean refresh) { setTitle(name); - new LoadTask() { + new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return service.getTopTrackSongs(name, 20, context, this); @@ -612,7 +617,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter setTitle(albumListExtra); } - new LoadTask() { + new LoadTask(true) { @Override protected MusicDirectory load(MusicService service) throws Exception { MusicDirectory result; @@ -635,9 +640,11 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } private abstract class LoadTask extends TabBackgroundTask> { + private boolean refresh; - public LoadTask() { + public LoadTask(boolean refresh) { super(SelectDirectoryFragment.this); + this.refresh = refresh; currentTask = this; } @@ -661,6 +668,11 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter if(albums.size() == 0) { artist = false; } + + // If artist, we want to load the artist info to use later + if(artist) { + artistInfo = musicService.getArtistInfo(id, refresh, context, this); + } return new Pair(dir, licenseValid); } @@ -674,8 +686,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void finishLoading() { // Show header if not album list type and not root and not artist - if(albumListType == null && !"root".equals(id) && !artist) { - View header = createHeader(entries); + // For Subsonic 5.1+ display a header for artists with getArtistInfo data if it exists + if(albumListType == null && !"root".equals(id) && (!artist || (ServerInfo.checkServerVersion(context, "1.11") && artistInfo != null))) { + View header = createHeader(); if(header != null && entryList != null) { entryList.addHeaderView(header, null, false); } @@ -1220,7 +1233,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter replaceFragment(fragment, true); } - private View createHeader(List entries) { + private View createHeader() { View header = entryList.findViewById(R.id.select_album_header); boolean add = false; if(header == null) { @@ -1228,36 +1241,75 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter add = true; } - final ImageLoader imageLoader = getImageLoader(); - - // Try a few times to get a random cover art - Entry coverArt = null; - for(int i = 0; (i < 3) && (coverArt == null || coverArt.getCoverArt() == null); i++) { - coverArt = entries.get(random.nextInt(entries.size())); + setupCoverArt(header); + setupTextDisplay(header); + + if(add) { + setupButtonEvents(header); } - - final Entry albumRep = coverArt; + + if(add) { + return header; + } else { + return null; + } + } + + private void setupCoverArt(View header) { + final ImageLoader imageLoader = getImageLoader(); View coverArtView = header.findViewById(R.id.select_album_art); - coverArtView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if(albumRep.getCoverArt() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(context); - ImageView fullScreenView = new ImageView(context); - imageLoader.loadImage(fullScreenView, albumRep, true, true); - builder.setCancelable(true); - - AlertDialog imageDialog = builder.create(); - // Set view here with unecessary 0's to remove top/bottom border - imageDialog.setView(fullScreenView, 0, 0, 0, 0); - imageDialog.show(); + // Try a few times to get a random cover art + if(artistInfo != null) { + final String url = artistInfo.getImageUrl(); + coverArtView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (url == null) { + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + ImageView fullScreenView = new ImageView(context); + imageLoader.loadImage(fullScreenView, url, true); + builder.setCancelable(true); + + AlertDialog imageDialog = builder.create(); + // Set view here with unecessary 0's to remove top/bottom border + imageDialog.setView(fullScreenView, 0, 0, 0, 0); + imageDialog.show(); + } + }); + imageLoader.loadImage(coverArtView, url, false); + } else if(entries.size() > 0) { + Entry coverArt = null; + for (int i = 0; (i < 3) && (coverArt == null || coverArt.getCoverArt() == null); i++) { + coverArt = entries.get(random.nextInt(entries.size())); } - }); - imageLoader.loadImage(coverArtView, albumRep, false, true); + final Entry albumRep = coverArt; + coverArtView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (albumRep.getCoverArt() == null) { + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + ImageView fullScreenView = new ImageView(context); + imageLoader.loadImage(fullScreenView, albumRep, true, true); + builder.setCancelable(true); + + AlertDialog imageDialog = builder.create(); + // Set view here with unecessary 0's to remove top/bottom border + imageDialog.setView(fullScreenView, 0, 0, 0, 0); + imageDialog.show(); + } + }); + imageLoader.loadImage(coverArtView, albumRep, false, true); + } + } + private void setupTextDisplay(View header) { TextView titleView = (TextView) header.findViewById(R.id.select_album_title); if(playlistName != null) { titleView.setText(playlistName); @@ -1266,6 +1318,10 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter titleView.setPadding(0, 6, 4, 8); } else if(name != null) { titleView.setText(name); + + if(artistInfo != null) { + titleView.setPadding(0, 6, 4, 8); + } } else if(share != null) { titleView.setVisibility(View.GONE); } @@ -1290,13 +1346,15 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } } } - if(songCount == 0) { - return null; - } final TextView artistView = (TextView) header.findViewById(R.id.select_album_artist); - if(podcastDescription != null) { - artistView.setText(podcastDescription); + if(podcastDescription != null || artistInfo != null) { + String text = podcastDescription != null ? podcastDescription : artistInfo.getBiography(); + Spanned spanned = null; + if(text != null) { + spanned = Html.fromHtml(text); + } + artistView.setText(spanned); artistView.setSingleLine(false); artistView.setLines(5); artistView.setTextAppearance(context, android.R.style.TextAppearance_Small); @@ -1312,6 +1370,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } } }); + artistView.setMovementMethod(LinkMovementMethod.getInstance()); } else if(topTracks) { artistView.setText(R.string.menu_top_tracks); artistView.setVisibility(View.VISIBLE); @@ -1339,62 +1398,55 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter songCountView.setVisibility(View.GONE); songLengthView.setVisibility(View.GONE); } + } + private void setupButtonEvents(View header) { + ImageView shareButton = (ImageView) header.findViewById(R.id.select_album_share); + if(share != null || podcastId != null || !Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_SHARED, true) || Util.isOffline(context) || !UserUtil.canShare()) { + shareButton.setVisibility(View.GONE); + } else { + shareButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createShare(SelectDirectoryFragment.this.entries); + } + }); + } - if(add) { - ImageView shareButton = (ImageView) header.findViewById(R.id.select_album_share); - if(share != null || podcastId != null || !Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_SHARED, true) || Util.isOffline(context) || !UserUtil.canShare()) { - shareButton.setVisibility(View.GONE); - } else { - shareButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - createShare(SelectDirectoryFragment.this.entries); - } - }); - } - - final ImageButton starButton = (ImageButton) header.findViewById(R.id.select_album_star); - if(directory != null && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_STAR, true)) { - starButton.setImageResource(directory.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off); - starButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - toggleStarred(directory, new OnStarChange() { - @Override - void starChange(boolean starred) { - starButton.setImageResource(directory.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off); - } - }); - } - }); - } else { - starButton.setVisibility(View.GONE); - } - - View ratingBarWrapper = header.findViewById(R.id.select_album_rate_wrapper); - final RatingBar ratingBar = (RatingBar) header.findViewById(R.id.select_album_rate); - if(directory != null && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_RATING, true) && !Util.isOffline(context)) { - ratingBar.setRating(directory.getRating()); - ratingBarWrapper.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setRating(directory, new OnRatingChange() { - @Override - void ratingChange(int rating) { - ratingBar.setRating(directory.getRating()); - } - }); - } - }); - } else { - ratingBar.setVisibility(View.GONE); - } + final ImageButton starButton = (ImageButton) header.findViewById(R.id.select_album_star); + if(directory != null && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_STAR, true)) { + starButton.setImageResource(directory.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off); + starButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + toggleStarred(directory, new OnStarChange() { + @Override + void starChange(boolean starred) { + starButton.setImageResource(directory.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off); + } + }); + } + }); + } else { + starButton.setVisibility(View.GONE); } - if(add) { - return header; + View ratingBarWrapper = header.findViewById(R.id.select_album_rate_wrapper); + final RatingBar ratingBar = (RatingBar) header.findViewById(R.id.select_album_rate); + if(directory != null && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_RATING, true) && !Util.isOffline(context)) { + ratingBar.setRating(directory.getRating()); + ratingBarWrapper.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setRating(directory, new OnRatingChange() { + @Override + void ratingChange(int rating) { + ratingBar.setRating(directory.getRating()); + } + }); + } + }); } else { - return null; + ratingBar.setVisibility(View.GONE); } } } diff --git a/src/github/daneren2005/dsub/service/CachedMusicService.java b/src/github/daneren2005/dsub/service/CachedMusicService.java index f3e91b38..232d0acf 100644 --- a/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -907,6 +907,11 @@ public class CachedMusicService implements MusicService { return info; } + @Override + public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { + return musicService.getBitmap(url, size, context, progressListener, task); + } + @Override public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{ return musicService.processOfflineSyncs(context, progressListener); diff --git a/src/github/daneren2005/dsub/service/MusicService.java b/src/github/daneren2005/dsub/service/MusicService.java index 64061191..854a0aa4 100644 --- a/src/github/daneren2005/dsub/service/MusicService.java +++ b/src/github/daneren2005/dsub/service/MusicService.java @@ -180,6 +180,8 @@ public interface MusicService { Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception; ArtistInfo getArtistInfo(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception; + + Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception; int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception; diff --git a/src/github/daneren2005/dsub/service/OfflineMusicService.java b/src/github/daneren2005/dsub/service/OfflineMusicService.java index dc4651d2..4bd90d09 100644 --- a/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -788,6 +788,11 @@ public class OfflineMusicService implements MusicService { throw new OfflineException(ERRORMSG); } + @Override + public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) 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/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index 239b2b43..cd0ae376 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -1391,6 +1391,54 @@ public class RESTMusicService implements MusicService { } } + @Override + public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { + // Synchronize on the url so that we don't download concurrently + synchronized (url) { + // Use cached file, if existing. + Bitmap bitmap = FileUtil.getMiscBitmap(context, url, size); + if(bitmap != null) { + return bitmap; + } + + InputStream in = null; + try { + HttpEntity entity = getEntityForURL(context, url, null, null, null, progressListener, task); + in = entity.getContent(); + Header contentEncoding = entity.getContentEncoding(); + if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) { + in = new GZIPInputStream(in); + } + + // If content type is XML, an error occurred. Get it. + String contentType = Util.getContentType(entity); + if (contentType != null && contentType.startsWith("text/xml")) { + new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8)); + return null; // Never reached. + } + + byte[] bytes = Util.toByteArray(in); + if(task != null && task.isCancelled()) { + // Handle case where partial is downloaded and cancelled + return null; + } + + OutputStream out = null; + try { + out = new FileOutputStream(FileUtil.getMiscFile(context, url)); + out.write(bytes); + } finally { + Util.close(out); + } + + return FileUtil.getSampledBitmap(bytes, size, false); + } + finally { + Util.close(in); + } + } + } + @Override public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{ return processOfflineScrobbles(context, progressListener) + processOfflineStars(context, progressListener); @@ -1683,14 +1731,17 @@ public class RESTMusicService implements MusicService { redirectedUrl = request.getURI().toString(); } - redirectFrom = originalUrl.substring(0, originalUrl.indexOf("/rest/")); - redirectTo = redirectedUrl.substring(0, redirectedUrl.indexOf("/rest/")); + int index = originalUrl.indexOf("/rest/"); + if(index != -1) { + redirectFrom = originalUrl.substring(0, index); + redirectTo = redirectedUrl.substring(0, redirectedUrl.indexOf("/rest/")); - if(redirectFrom.compareTo(redirectTo) != 0) { - Log.i(TAG, redirectFrom + " redirects to " + redirectTo); + if (redirectFrom.compareTo(redirectTo) != 0) { + Log.i(TAG, redirectFrom + " redirects to " + redirectTo); + } + redirectionLastChecked = System.currentTimeMillis(); + redirectionNetworkType = getCurrentNetworkType(context); } - redirectionLastChecked = System.currentTimeMillis(); - redirectionNetworkType = getCurrentNetworkType(context); } private String rewriteUrlWithRedirect(Context context, String url) { diff --git a/src/github/daneren2005/dsub/util/FileUtil.java b/src/github/daneren2005/dsub/util/FileUtil.java index f59f9363..34838f33 100644 --- a/src/github/daneren2005/dsub/util/FileUtil.java +++ b/src/github/daneren2005/dsub/util/FileUtil.java @@ -250,6 +250,33 @@ public class FileUtil { return null; } + public static File getMiscDirectory(Context context) { + File dir = new File(getSubsonicDirectory(context), "misc"); + ensureDirectoryExistsAndIsReadWritable(dir); + ensureDirectoryExistsAndIsReadWritable(new File(dir, ".nomedia")); + return dir; + } + + public static File getMiscFile(Context context, String url) { + return new File(getMiscDirectory(context), Util.md5Hex(url) + ".jpeg"); + } + + public static Bitmap getMiscBitmap(Context context, String url, int size) { + File avatarFile = getMiscFile(context, url); + if (avatarFile.exists()) { + final BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(avatarFile.getPath(), opt); + opt.inPurgeable = true; + opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); + opt.inJustDecodeBounds = false; + + Bitmap bitmap = BitmapFactory.decodeFile(avatarFile.getPath(), opt); + return bitmap == null ? null : getScaledBitmap(bitmap, size, false); + } + return null; + } + public static Bitmap getSampledBitmap(byte[] bytes, int size) { return getSampledBitmap(bytes, size, true); } diff --git a/src/github/daneren2005/dsub/util/ImageLoader.java b/src/github/daneren2005/dsub/util/ImageLoader.java index be3f7d1b..5adf5e34 100644 --- a/src/github/daneren2005/dsub/util/ImageLoader.java +++ b/src/github/daneren2005/dsub/util/ImageLoader.java @@ -108,9 +108,11 @@ public class ImageLoader { if(entry == null) { key = getKey("unknown", size); color = COLORS[0]; + + return getUnknownImage(key, size, color, null, null); } else { key = getKey(entry.getId() + "unknown", size); - + String hash; if(entry.getAlbum() != null) { hash = entry.getAlbum(); @@ -120,16 +122,20 @@ public class ImageLoader { hash = entry.getId(); } color = COLORS[Math.abs(hash.hashCode()) % COLORS.length]; + + return getUnknownImage(key, size, color, entry.getAlbum(), entry.getArtist()); } + } + private Bitmap getUnknownImage(String key, int size, int color, String topText, String bottomText) { Bitmap bitmap = cache.get(key); if(bitmap == null) { - bitmap = createUnknownImage(entry, size, color); + bitmap = createUnknownImage(size, color, topText, bottomText); cache.put(key, bitmap); } return bitmap; } - private Bitmap createUnknownImage(MusicDirectory.Entry entry, int size, int primaryColor) { + private Bitmap createUnknownImage(int size, int primaryColor, String topText, String bottomText) { Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); @@ -140,18 +146,18 @@ public class ImageLoader { color.setShader(new LinearGradient(0, 0, 0, size / 3.0f, Color.rgb(82, 82, 82), Color.BLACK, Shader.TileMode.MIRROR)); canvas.drawRect(0, size * 2.0f / 3.0f, size, size, color); - if(entry != null) { + if(topText != null || bottomText != null) { Paint font = new Paint(); font.setFlags(Paint.ANTI_ALIAS_FLAG); font.setColor(Color.WHITE); font.setTextSize(3.0f + size * 0.07f); - if(entry.getAlbum() != null) { - canvas.drawText(entry.getAlbum(), size * 0.05f, size * 0.6f, font); + if(topText != null) { + canvas.drawText(topText, size * 0.05f, size * 0.6f, font); } - if(entry.getArtist() != null) { - canvas.drawText(entry.getArtist(), size * 0.05f, size * 0.8f, font); + if(bottomText != null) { + canvas.drawText(bottomText, size * 0.05f, size * 0.8f, font); } } @@ -210,6 +216,29 @@ public class ImageLoader { return task; } + public SilentBackgroundTask loadImage(View view, String url, boolean large) { + Bitmap bitmap; + int size = large ? imageSizeLarge : imageSizeDefault; + if (url == null) { + String key = getKey(url + "unknown", size); + int color = COLORS[Math.abs(key.hashCode()) % COLORS.length]; + bitmap = getUnknownImage(key, size, color, null, null); + setImage(view, Util.createDrawableFromBitmap(context, bitmap), true); + return null; + } + + bitmap = cache.get(getKey(url, size)); + if (bitmap != null && !bitmap.isRecycled()) { + final Drawable drawable = Util.createDrawableFromBitmap(this.context, bitmap); + setImage(view, drawable, true); + return null; + } + + SilentBackgroundTask task = new ViewUrlTask(view.getContext(), view, url, size); + task.execute(); + return task; + } + public SilentBackgroundTask loadImage(Context context, RemoteControlClient remoteControl, MusicDirectory.Entry entry) { Bitmap bitmap; if (entry == null || entry.getCoverArt() == null) { @@ -382,6 +411,50 @@ public class ImageLoader { } } + private class ViewUrlTask extends SilentBackgroundTask { + private final Context mContext; + private final String mUrl; + private final ImageView mView; + private Drawable mDrawable; + private int mSize; + + public ViewUrlTask(Context context, View view, String url, int size) { + super(context); + mContext = context; + mView = (ImageView) view; + mUrl = url; + mSize = size; + } + + @Override + protected Void doInBackground() throws Throwable { + try { + MusicService musicService = MusicServiceFactory.getMusicService(mContext); + Bitmap bitmap = musicService.getBitmap(mUrl, mSize, mContext, null, this); + if(bitmap != null) { + String key = getKey(mUrl, mSize); + cache.put(key, bitmap); + // Make sure key is the most recently "used" + cache.get(key); + + mDrawable = Util.createDrawableFromBitmap(mContext, bitmap); + } + } catch (Throwable x) { + Log.e(TAG, "Failed to download from url " + mUrl, x); + cancelled.set(true); + } + + return null; + } + + @Override + protected void done(Void result) { + if(mDrawable != null) { + mView.setImageDrawable(mDrawable); + } + } + } + private class AvatarTask extends SilentBackgroundTask { private final Context mContext; private final String mUsername; -- cgit v1.2.3 From f014c1d35b1d7b3d38266f07e7c825a3cc4f8a5b Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Tue, 13 Jan 2015 09:24:02 -0800 Subject: Add artist headers to grids as well --- res/layout/grid_view.xml | 2 +- .../dsub/fragments/SelectDirectoryFragment.java | 23 +- .../daneren2005/dsub/view/HeaderGridView.java | 785 +++++++++++++++++++++ 3 files changed, 803 insertions(+), 7 deletions(-) create mode 100644 src/github/daneren2005/dsub/view/HeaderGridView.java (limited to 'src') diff --git a/res/layout/grid_view.xml b/res/layout/grid_view.xml index 40674c8d..7690d975 100644 --- a/res/layout/grid_view.xml +++ b/res/layout/grid_view.xml @@ -1,5 +1,5 @@ - 0)) { entryList.addHeaderView(header, null, false); + header = null; } } @@ -706,6 +711,12 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter setupScrollList(albumList); setupAlbumList(); + + // This should only not be null for a artist with only albums + if(header != null) { + HeaderGridView headerGridView = (HeaderGridView) albumList; + headerGridView.addHeaderView(header); + } } addAlbumHeader = false; } @@ -1390,13 +1401,13 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter TextView songCountView = (TextView) header.findViewById(R.id.select_album_song_count); TextView songLengthView = (TextView) header.findViewById(R.id.select_album_song_length); - if(podcastDescription == null) { + if(podcastDescription != null || artistInfo != null) { + songCountView.setVisibility(View.GONE); + songLengthView.setVisibility(View.GONE); + } else { String s = context.getResources().getQuantityString(R.plurals.select_album_n_songs, songCount, songCount); songCountView.setText(s.toUpperCase()); songLengthView.setText(Util.formatDuration(totalDuration)); - } else { - songCountView.setVisibility(View.GONE); - songLengthView.setVisibility(View.GONE); } } private void setupButtonEvents(View header) { diff --git a/src/github/daneren2005/dsub/view/HeaderGridView.java b/src/github/daneren2005/dsub/view/HeaderGridView.java new file mode 100644 index 00000000..cd57dc0e --- /dev/null +++ b/src/github/daneren2005/dsub/view/HeaderGridView.java @@ -0,0 +1,785 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package github.daneren2005.dsub.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.database.DataSetObservable; +import android.database.DataSetObserver; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.*; + +import java.lang.reflect.Field; +import java.util.ArrayList; + +/** + * A {@link GridView} that supports adding header rows in a + * very similar way to {@link android.widget.ListView}. + * See {@link HeaderGridView#addHeaderView(View, Object, boolean)} + * See {@link HeaderGridView#addFooterView(View, Object, boolean)} + */ +public class HeaderGridView extends GridView { + + public static boolean DEBUG = false; + + /** + * A class that represents a fixed view in a list, for example a header at the top + * or a footer at the bottom. + */ + private static class FixedViewInfo { + /** + * The view to add to the grid + */ + public View view; + public ViewGroup viewContainer; + /** + * The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. + */ + public Object data; + /** + * true if the fixed view should be selectable in the grid + */ + public boolean isSelectable; + } + + private int mNumColumns = AUTO_FIT; + private View mViewForMeasureRowHeight = null; + private int mRowHeight = -1; + private static final String LOG_TAG = "grid-view-with-header-and-footer"; + + private ArrayList mHeaderViewInfos = new ArrayList(); + private ArrayList mFooterViewInfos = new ArrayList(); + + private void initHeaderGridView() { + } + + public HeaderGridView(Context context) { + super(context); + initHeaderGridView(); + } + + public HeaderGridView(Context context, AttributeSet attrs) { + super(context, attrs); + initHeaderGridView(); + } + + public HeaderGridView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initHeaderGridView(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + ListAdapter adapter = getAdapter(); + if (adapter != null && adapter instanceof HeaderViewGridAdapter) { + ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumnsCompatible()); + ((HeaderViewGridAdapter) adapter).setRowHeight(getRowHeight()); + } + } + + @Override + public void setClipChildren(boolean clipChildren) { + // Ignore, since the header rows depend on not being clipped + } + + /** + * Do not call this method unless you know how it works. + * + * @param clipChildren + */ + public void setClipChildrenSupper(boolean clipChildren) { + super.setClipChildren(false); + } + + /** + * Add a fixed view to appear at the top of the grid. If addHeaderView is + * called more than once, the views will appear in the order they were + * added. Views added using this call can take focus if they want. + *

+ * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap + * the supplied cursor with one that will also account for header views. + * + * @param v The view to add. + */ + public void addHeaderView(View v) { + addHeaderView(v, null, true); + } + + /** + * Add a fixed view to appear at the top of the grid. If addHeaderView is + * called more than once, the views will appear in the order they were + * added. Views added using this call can take focus if they want. + *

+ * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap + * the supplied cursor with one that will also account for header views. + * + * @param v The view to add. + * @param data Data to associate with this view + * @param isSelectable whether the item is selectable + */ + public void addHeaderView(View v, Object data, boolean isSelectable) { + ListAdapter adapter = getAdapter(); + if (adapter != null && !(adapter instanceof HeaderViewGridAdapter)) { + throw new IllegalStateException( + "Cannot add header view to grid -- setAdapter has already been called."); + } + + ViewGroup.LayoutParams lyp = v.getLayoutParams(); + + FixedViewInfo info = new FixedViewInfo(); + FrameLayout fl = new FullWidthFixedViewLayout(getContext()); + + if (lyp != null) { + v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height)); + fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height)); + } + fl.addView(v); + info.view = v; + info.viewContainer = fl; + info.data = data; + info.isSelectable = isSelectable; + mHeaderViewInfos.add(info); + // in the case of re-adding a header view, or adding one later on, + // we need to notify the observer + if (adapter != null) { + ((HeaderViewGridAdapter) adapter).notifyDataSetChanged(); + } + } + + public void addFooterView(View v) { + addFooterView(v, null, true); + } + + public void addFooterView(View v, Object data, boolean isSelectable) { + ListAdapter mAdapter = getAdapter(); + if (mAdapter != null && !(mAdapter instanceof HeaderViewGridAdapter)) { + throw new IllegalStateException( + "Cannot add header view to grid -- setAdapter has already been called."); + } + + ViewGroup.LayoutParams lyp = v.getLayoutParams(); + + FixedViewInfo info = new FixedViewInfo(); + FrameLayout fl = new FullWidthFixedViewLayout(getContext()); + + if (lyp != null) { + v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height)); + fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height)); + } + fl.addView(v); + info.view = v; + info.viewContainer = fl; + info.data = data; + info.isSelectable = isSelectable; + mFooterViewInfos.add(info); + + if (mAdapter != null) { + ((HeaderViewGridAdapter) mAdapter).notifyDataSetChanged(); + } + } + + public int getHeaderViewCount() { + return mHeaderViewInfos.size(); + } + + public int getFooterViewCount() { + return mFooterViewInfos.size(); + } + + /** + * Removes a previously-added header view. + * + * @param v The view to remove + * @return true if the view was removed, false if the view was not a header + * view + */ + public boolean removeHeaderView(View v) { + if (mHeaderViewInfos.size() > 0) { + boolean result = false; + ListAdapter adapter = getAdapter(); + if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) { + result = true; + } + removeFixedViewInfo(v, mHeaderViewInfos); + return result; + } + return false; + } + + /** + * Removes a previously-added footer view. + * + * @param v The view to remove + * @return true if the view was removed, false if the view was not a header + * view + */ + public boolean removeFooterView(View v) { + if (mFooterViewInfos.size() > 0) { + boolean result = false; + ListAdapter adapter = getAdapter(); + if (adapter != null && ((HeaderViewGridAdapter) adapter).removeFooter(v)) { + result = true; + } + removeFixedViewInfo(v, mFooterViewInfos); + return result; + } + return false; + } + + private void removeFixedViewInfo(View v, ArrayList where) { + int len = where.size(); + for (int i = 0; i < len; ++i) { + FixedViewInfo info = where.get(i); + if (info.view == v) { + where.remove(i); + break; + } + } + } + + @TargetApi(11) + private int getNumColumnsCompatible() { + if (Build.VERSION.SDK_INT >= 11) { + return super.getNumColumns(); + } else { + try { + Field numColumns = getClass().getSuperclass().getDeclaredField("mNumColumns"); + numColumns.setAccessible(true); + return numColumns.getInt(this); + } catch (Exception e) { + if (mNumColumns != -1) { + return mNumColumns; + } + throw new RuntimeException("Can not determine the mNumColumns for this API platform, please call setNumColumns to set it."); + } + } + } + + @TargetApi(16) + private int getColumnWidthCompatible() { + if (Build.VERSION.SDK_INT >= 16) { + return super.getColumnWidth(); + } else { + try { + Field numColumns = getClass().getSuperclass().getDeclaredField("mColumnWidth"); + numColumns.setAccessible(true); + return numColumns.getInt(this); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mViewForMeasureRowHeight = null; + } + + public void invalidateRowHeight() { + mRowHeight = -1; + } + + public int getRowHeight() { + if (mRowHeight > 0) { + return mRowHeight; + } + ListAdapter adapter = getAdapter(); + int numColumns = getNumColumnsCompatible(); + + // adapter has not been set or has no views in it; + if (adapter == null || adapter.getCount() <= numColumns * (mHeaderViewInfos.size() + mFooterViewInfos.size())) { + return -1; + } + int mColumnWidth = getColumnWidthCompatible(); + View view = getAdapter().getView(numColumns * mHeaderViewInfos.size(), mViewForMeasureRowHeight, this); + AbsListView.LayoutParams p = (AbsListView.LayoutParams) view.getLayoutParams(); + if (p == null) { + p = new AbsListView.LayoutParams(-1, -2, 0); + view.setLayoutParams(p); + } + int childHeightSpec = getChildMeasureSpec( + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height); + int childWidthSpec = getChildMeasureSpec( + MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width); + view.measure(childWidthSpec, childHeightSpec); + mViewForMeasureRowHeight = view; + mRowHeight = view.getMeasuredHeight(); + return mRowHeight; + } + + @TargetApi(11) + public void tryToScrollToBottomSmoothly() { + int lastPos = getAdapter().getCount() - 1; + if (Build.VERSION.SDK_INT >= 11) { + smoothScrollToPositionFromTop(lastPos, 0); + } else { + setSelection(lastPos); + } + } + + @TargetApi(11) + public void tryToScrollToBottomSmoothly(int duration) { + int lastPos = getAdapter().getCount() - 1; + if (Build.VERSION.SDK_INT >= 11) { + smoothScrollToPositionFromTop(lastPos, 0, duration); + } else { + setSelection(lastPos); + } + } + + @Override + public void setAdapter(ListAdapter adapter) { + if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) { + HeaderViewGridAdapter headerViewGridAdapter = new HeaderViewGridAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); + int numColumns = getNumColumnsCompatible(); + if (numColumns > 1) { + headerViewGridAdapter.setNumColumns(numColumns); + } + headerViewGridAdapter.setRowHeight(getRowHeight()); + super.setAdapter(headerViewGridAdapter); + } else { + super.setAdapter(adapter); + } + } + + /** + * full width + */ + private class FullWidthFixedViewLayout extends FrameLayout { + + public FullWidthFixedViewLayout(Context context) { + super(context); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int realLeft = HeaderGridView.this.getPaddingLeft() + getPaddingLeft(); + // Try to make where it should be, from left, full width + if (realLeft != left) { + offsetLeftAndRight(realLeft - left); + } + super.onLayout(changed, left, top, right, bottom); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int targetWidth = HeaderGridView.this.getMeasuredWidth() + - HeaderGridView.this.getPaddingLeft() + - HeaderGridView.this.getPaddingRight(); + widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth, + MeasureSpec.getMode(widthMeasureSpec)); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override + public void setNumColumns(int numColumns) { + super.setNumColumns(numColumns); + mNumColumns = numColumns; + ListAdapter adapter = getAdapter(); + if (adapter != null && adapter instanceof HeaderViewGridAdapter) { + ((HeaderViewGridAdapter) adapter).setNumColumns(numColumns); + } + } + + /** + * ListAdapter used when a HeaderGridView has header views. This ListAdapter + * wraps another one and also keeps track of the header views and their + * associated data objects. + *

This is intended as a base class; you will probably not need to + * use this class directly in your own code. + */ + private static class HeaderViewGridAdapter implements WrapperListAdapter, Filterable { + // This is used to notify the container of updates relating to number of columns + // or headers changing, which changes the number of placeholders needed + private final DataSetObservable mDataSetObservable = new DataSetObservable(); + private final ListAdapter mAdapter; + static final ArrayList EMPTY_INFO_LIST = + new ArrayList(); + + // This ArrayList is assumed to NOT be null. + ArrayList mHeaderViewInfos; + ArrayList mFooterViewInfos; + private int mNumColumns = 1; + private int mRowHeight = -1; + boolean mAreAllFixedViewsSelectable; + private final boolean mIsFilterable; + private boolean mCachePlaceHoldView = true; + // From Recycle Bin or calling getView, this a question... + private boolean mCacheFirstHeaderView = false; + + public HeaderViewGridAdapter(ArrayList headerViewInfos, ArrayList footViewInfos, ListAdapter adapter) { + mAdapter = adapter; + mIsFilterable = adapter instanceof Filterable; + if (headerViewInfos == null) { + mHeaderViewInfos = EMPTY_INFO_LIST; + } else { + mHeaderViewInfos = headerViewInfos; + } + + if (footViewInfos == null) { + mFooterViewInfos = EMPTY_INFO_LIST; + } else { + mFooterViewInfos = footViewInfos; + } + mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos) + && areAllListInfosSelectable(mFooterViewInfos); + } + + public void setNumColumns(int numColumns) { + if (numColumns < 1) { + return; + } + if (mNumColumns != numColumns) { + mNumColumns = numColumns; + notifyDataSetChanged(); + } + } + + public void setRowHeight(int height) { + mRowHeight = height; + } + + public int getHeadersCount() { + return mHeaderViewInfos.size(); + } + + public int getFootersCount() { + return mFooterViewInfos.size(); + } + + @Override + public boolean isEmpty() { + return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0 && getFootersCount() == 0; + } + + private boolean areAllListInfosSelectable(ArrayList infos) { + if (infos != null) { + for (FixedViewInfo info : infos) { + if (!info.isSelectable) { + return false; + } + } + } + return true; + } + + public boolean removeHeader(View v) { + for (int i = 0; i < mHeaderViewInfos.size(); i++) { + FixedViewInfo info = mHeaderViewInfos.get(i); + if (info.view == v) { + mHeaderViewInfos.remove(i); + mAreAllFixedViewsSelectable = + areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos); + mDataSetObservable.notifyChanged(); + return true; + } + } + return false; + } + + public boolean removeFooter(View v) { + for (int i = 0; i < mFooterViewInfos.size(); i++) { + FixedViewInfo info = mFooterViewInfos.get(i); + if (info.view == v) { + mFooterViewInfos.remove(i); + mAreAllFixedViewsSelectable = + areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos); + mDataSetObservable.notifyChanged(); + return true; + } + } + return false; + } + + @Override + public int getCount() { + if (mAdapter != null) { + return (getFootersCount() + getHeadersCount()) * mNumColumns + getAdapterAndPlaceHolderCount(); + } else { + return (getFootersCount() + getHeadersCount()) * mNumColumns; + } + } + + @Override + public boolean areAllItemsEnabled() { + if (mAdapter != null) { + return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled(); + } else { + return true; + } + } + + private int getAdapterAndPlaceHolderCount() { + final int adapterCount = (int) (Math.ceil(1f * mAdapter.getCount() / mNumColumns) * mNumColumns); + return adapterCount; + } + + @Override + public boolean isEnabled(int position) { + // Header (negative positions will throw an IndexOutOfBoundsException) + int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; + if (position < numHeadersAndPlaceholders) { + return position % mNumColumns == 0 + && mHeaderViewInfos.get(position / mNumColumns).isSelectable; + } + + // Adapter + final int adjPosition = position - numHeadersAndPlaceholders; + int adapterCount = 0; + if (mAdapter != null) { + adapterCount = getAdapterAndPlaceHolderCount(); + if (adjPosition < adapterCount) { + return adjPosition < mAdapter.getCount() && mAdapter.isEnabled(adjPosition); + } + } + + // Footer (off-limits positions will throw an IndexOutOfBoundsException) + final int footerPosition = adjPosition - adapterCount; + return footerPosition % mNumColumns == 0 + && mFooterViewInfos.get(footerPosition / mNumColumns).isSelectable; + } + + @Override + public Object getItem(int position) { + // Header (negative positions will throw an ArrayIndexOutOfBoundsException) + int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; + if (position < numHeadersAndPlaceholders) { + if (position % mNumColumns == 0) { + return mHeaderViewInfos.get(position / mNumColumns).data; + } + return null; + } + + // Adapter + final int adjPosition = position - numHeadersAndPlaceholders; + int adapterCount = 0; + if (mAdapter != null) { + adapterCount = getAdapterAndPlaceHolderCount(); + if (adjPosition < adapterCount) { + if (adjPosition < mAdapter.getCount()) { + return mAdapter.getItem(adjPosition); + } else { + return null; + } + } + } + + // Footer (off-limits positions will throw an IndexOutOfBoundsException) + final int footerPosition = adjPosition - adapterCount; + if (footerPosition % mNumColumns == 0) { + return mFooterViewInfos.get(footerPosition).data; + } else { + return null; + } + } + + @Override + public long getItemId(int position) { + int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; + if (mAdapter != null && position >= numHeadersAndPlaceholders) { + int adjPosition = position - numHeadersAndPlaceholders; + int adapterCount = mAdapter.getCount(); + if (adjPosition < adapterCount) { + return mAdapter.getItemId(adjPosition); + } + } + return -1; + } + + @Override + public boolean hasStableIds() { + if (mAdapter != null) { + return mAdapter.hasStableIds(); + } + return false; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (DEBUG) { + Log.d(LOG_TAG, String.format("getView: %s, reused: %s", position, convertView == null)); + } + // Header (negative positions will throw an ArrayIndexOutOfBoundsException) + int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; + if (position < numHeadersAndPlaceholders) { + View headerViewContainer = mHeaderViewInfos + .get(position / mNumColumns).viewContainer; + if (position % mNumColumns == 0) { + return headerViewContainer; + } else { + if (convertView == null) { + convertView = new View(parent.getContext()); + } + // We need to do this because GridView uses the height of the last item + // in a row to determine the height for the entire row. + convertView.setVisibility(View.INVISIBLE); + convertView.setMinimumHeight(headerViewContainer.getHeight()); + return convertView; + } + } + // Adapter + final int adjPosition = position - numHeadersAndPlaceholders; + int adapterCount = 0; + if (mAdapter != null) { + adapterCount = getAdapterAndPlaceHolderCount(); + if (adjPosition < adapterCount) { + if (adjPosition < mAdapter.getCount()) { + View view = mAdapter.getView(adjPosition, convertView, parent); + return view; + } else { + if (convertView == null) { + convertView = new View(parent.getContext()); + } + convertView.setVisibility(View.INVISIBLE); + convertView.setMinimumHeight(mRowHeight); + return convertView; + } + } + } + // Footer + final int footerPosition = adjPosition - adapterCount; + if (footerPosition < getCount()) { + View footViewContainer = mFooterViewInfos + .get(footerPosition / mNumColumns).viewContainer; + if (position % mNumColumns == 0) { + return footViewContainer; + } else { + if (convertView == null) { + convertView = new View(parent.getContext()); + } + // We need to do this because GridView uses the height of the last item + // in a row to determine the height for the entire row. + convertView.setVisibility(View.INVISIBLE); + convertView.setMinimumHeight(footViewContainer.getHeight()); + return convertView; + } + } + throw new ArrayIndexOutOfBoundsException(position); + } + + @Override + public int getItemViewType(int position) { + + final int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; + final int adapterViewTypeStart = mAdapter == null ? 0 : mAdapter.getViewTypeCount() - 1; + int type = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; + if (mCachePlaceHoldView) { + // Header + if (position < numHeadersAndPlaceholders) { + if (position == 0) { + if (mCacheFirstHeaderView) { + type = adapterViewTypeStart + mHeaderViewInfos.size() + mFooterViewInfos.size() + 1 + 1; + } + } + if (position % mNumColumns != 0) { + type = adapterViewTypeStart + (position / mNumColumns + 1); + } + } + } + + // Adapter + final int adjPosition = position - numHeadersAndPlaceholders; + int adapterCount = 0; + if (mAdapter != null) { + adapterCount = getAdapterAndPlaceHolderCount(); + if (adjPosition >= 0 && adjPosition < adapterCount) { + if (adjPosition < mAdapter.getCount()) { + type = mAdapter.getItemViewType(adjPosition); + } else { + if (mCachePlaceHoldView) { + type = adapterViewTypeStart + mHeaderViewInfos.size() + 1; + } + } + } + } + + if (mCachePlaceHoldView) { + // Footer + final int footerPosition = adjPosition - adapterCount; + if (footerPosition >= 0 && footerPosition < getCount() && (footerPosition % mNumColumns) != 0) { + type = adapterViewTypeStart + mHeaderViewInfos.size() + 1 + (footerPosition / mNumColumns + 1); + } + } + if (DEBUG) { + Log.d(LOG_TAG, String.format("getItemViewType: pos: %s, result: %s", position, type, mCachePlaceHoldView, mCacheFirstHeaderView)); + } + return type; + } + + /** + * content view, content view holder, header[0], header and footer placeholder(s) + * + * @return + */ + @Override + public int getViewTypeCount() { + int count = mAdapter == null ? 1 : mAdapter.getViewTypeCount(); + if (mCachePlaceHoldView) { + int offset = mHeaderViewInfos.size() + 1 + mFooterViewInfos.size(); + if (mCacheFirstHeaderView) { + offset += 1; + } + count += offset; + } + if (DEBUG) { + Log.d(LOG_TAG, String.format("getViewTypeCount: %s", count)); + } + return count; + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + mDataSetObservable.registerObserver(observer); + if (mAdapter != null) { + mAdapter.registerDataSetObserver(observer); + } + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + mDataSetObservable.unregisterObserver(observer); + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(observer); + } + } + + @Override + public Filter getFilter() { + if (mIsFilterable) { + return ((Filterable) mAdapter).getFilter(); + } + return null; + } + + @Override + public ListAdapter getWrappedAdapter() { + return mAdapter; + } + + public void notifyDataSetChanged() { + mDataSetObservable.notifyChanged(); + } + } +} -- cgit v1.2.3 From 719811985eee20c0f98f4740347e8e66b490a4f9 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Thu, 15 Jan 2015 09:17:42 -0800 Subject: Make text wrap from around image in header --- res/layout/select_album_header.xml | 1 + .../dsub/fragments/SelectDirectoryFragment.java | 36 ++++++++++++++++++++-- .../dsub/view/MyLeadingMarginSpan2.java | 34 ++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 src/github/daneren2005/dsub/view/MyLeadingMarginSpan2.java (limited to 'src') diff --git a/res/layout/select_album_header.xml b/res/layout/select_album_header.xml index 8b13ee68..abc16e58 100644 --- a/res/layout/select_album_header.xml +++ b/res/layout/select_album_header.xml @@ -15,6 +15,7 @@ android:contentDescription="@null"/>