From 95773c7994b624459c58981ff0f461aca27cb4eb Mon Sep 17 00:00:00 2001 From: Nite Date: Thu, 4 Feb 2021 20:15:58 +0100 Subject: [PATCH] Started refactoring to NavigationUI Main menu items are refactored, except Now Playing --- build.gradle | 1 + dependencies.gradle | 38 +- ultrasonic/build.gradle | 12 + ultrasonic/src/main/AndroidManifest.xml | 61 +- .../ultrasonic/activity/BookmarkActivity.java | 485 ------ .../ultrasonic/activity/ChatActivity.java | 310 ---- .../ultrasonic/activity/DownloadActivity.java | 4 +- .../ultrasonic/activity/HelpActivity.java | 325 ---- .../ultrasonic/activity/MainActivity.java | 272 ---- .../ultrasonic/activity/PodcastsActivity.java | 106 -- .../ultrasonic/activity/SearchActivity.java | 13 +- .../activity/SelectAlbumActivity.java | 1170 +-------------- .../activity/SelectGenreActivity.java | 191 --- .../activity/SelectPlaylistActivity.java | 391 ----- .../ultrasonic/activity/SettingsActivity.java | 43 - .../ultrasonic/activity/ShareActivity.java | 385 ----- .../activity/SubsonicTabActivity.java | 141 +- .../ultrasonic/fragment/AboutFragment.java | 147 ++ .../fragment/BookmarksFragment.java | 475 ++++++ .../ultrasonic/fragment/ChatFragment.java | 317 ++++ .../ultrasonic/fragment/MainFragment.java | 278 ++++ .../fragment/PlaylistsFragment.java | 393 +++++ .../ultrasonic/fragment/PodcastFragment.java | 93 ++ .../ultrasonic/fragment/SearchFragment.java | 586 ++++++++ .../fragment/SelectAlbumFragment.java | 1321 +++++++++++++++++ .../fragment/SelectGenreFragment.java | 174 +++ .../ultrasonic/fragment/SettingsFragment.java | 145 +- .../ultrasonic/fragment/SharesFragment.java | 388 +++++ .../org/moire/ultrasonic/util/Constants.java | 1 + .../org/moire/ultrasonic/util/FileUtil.java | 11 +- .../moire/ultrasonic/util/LoadingTask.java | 29 +- .../util/TabActivityBackgroundTask.java | 39 +- .../ultrasonic/util/TimeSpanPreference.java | 61 +- ...imeSpanPreferenceDialogFragmentCompat.java | 88 ++ .../java/org/moire/ultrasonic/util/Util.java | 14 - .../ultrasonic/util/VideoPlayerType.java | 31 +- .../moire/ultrasonic/view/ChatAdapter.java | 23 +- .../moire/ultrasonic/view/EntryAdapter.java | 15 +- .../ultrasonic/view/PlaylistAdapter.java | 12 +- .../moire/ultrasonic/view/ShareAdapter.java | 12 +- .../ultrasonic/activity/NavigationActivity.kt | 161 ++ .../di/AppPermanentStorageModule.kt | 3 + .../moire/ultrasonic/di/MusicServiceModule.kt | 9 + .../ultrasonic/fragment/DownloadFragment.kt | 6 + .../ultrasonic/fragment/FragmentTitle.kt | 32 + .../SelectArtistFragment.kt} | 129 +- .../ultrasonic/subsonic/DownloadHandler.kt | 210 +++ .../subsonic/ImageLoaderProvider.kt | 45 + .../subsonic/NetworkAndStorageChecker.kt | 16 + .../moire/ultrasonic/subsonic/ShareHandler.kt | 139 ++ .../moire/ultrasonic/subsonic/VideoPlayer.kt | 21 + .../ultrasonic/util/CancellationToken.kt | 33 + ultrasonic/src/main/res/layout/help.xml | 16 +- .../main/res/layout/navigation_activity.xml | 39 + .../src/main/res/layout/navigation_header.xml | 41 + ultrasonic/src/main/res/layout/search.xml | 8 +- .../src/main/res/layout/select_album.xml | 2 - ultrasonic/src/main/res/menu/navigation.xml | 73 + ultrasonic/src/main/res/menu/search.xml | 9 + .../main/res/navigation/navigation_graph.xml | 85 ++ ultrasonic/src/main/res/values/styles.xml | 10 + ultrasonic/src/main/res/xml/settings.xml | 217 ++- 62 files changed, 5679 insertions(+), 4226 deletions(-) delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectGenreActivity.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/SettingsActivity.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/ShareActivity.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/BookmarksFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ChatFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/MainFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlaylistsFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PodcastFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SearchFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectAlbumFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectGenreFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SharesFragment.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreferenceDialogFragmentCompat.java create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadFragment.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/FragmentTitle.kt rename ultrasonic/src/main/kotlin/org/moire/ultrasonic/{activity/SelectArtistActivity.kt => fragment/SelectArtistFragment.kt} (58%) create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CancellationToken.kt create mode 100644 ultrasonic/src/main/res/layout/navigation_activity.xml create mode 100644 ultrasonic/src/main/res/layout/navigation_header.xml create mode 100644 ultrasonic/src/main/res/menu/navigation.xml create mode 100644 ultrasonic/src/main/res/menu/search.xml create mode 100644 ultrasonic/src/main/res/navigation/navigation_graph.xml diff --git a/build.gradle b/build.gradle index 8f012662..a66ae852 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ buildscript { classpath gradlePlugins.ktlintGradle classpath gradlePlugins.detekt classpath gradlePlugins.jacoco + classpath gradlePlugins.navigationSafeArgs } } diff --git a/dependencies.gradle b/dependencies.gradle index 01b87e01..e9c785e4 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -4,11 +4,14 @@ ext.versions = [ compileSdk : 29, gradle : '6.5', + navigation : "2.3.2", androidTools : "4.0.0", ktlint : "0.37.1", ktlintGradle : "9.2.1", detekt : "1.0.0.RC6-4", jacoco : "0.8.5", + navigationSafeArgs : "2.3.2", + preferences : "1.1.1", androidSupport : "28.0.0", androidLegacySupport : "1.0.0", @@ -42,23 +45,30 @@ ext.versions = [ ] ext.gradlePlugins = [ - androidTools : "com.android.tools.build:gradle:$versions.androidTools", - kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin", - ktlintGradle : "org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle", - detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt", - jacoco : "org.jacoco:org.jacoco.core:$versions.jacoco" + androidTools : "com.android.tools.build:gradle:$versions.androidTools", + kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin", + ktlintGradle : "org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle", + detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt", + jacoco : "org.jacoco:org.jacoco.core:$versions.jacoco", + navigationSafeArgs: "androidx.navigation:navigation-safe-args-gradle-plugin:$versions.navigationSafeArgs" ] ext.androidSupport = [ - support : "androidx.legacy:legacy-support-v4:$versions.androidLegacySupport", - design : "com.google.android.material:material:$versions.androidSupportDesign", - annotations : "com.android.support:support-annotations:$versions.androidSupport", - multidex : "androidx.multidex:multidex:$versions.multidex", - constraintLayout : "androidx.constraintlayout:constraintlayout:$versions.constraintLayout", - room : "androidx.room:room-compiler:$versions.room", - roomRuntime : "androidx.room:room-runtime:$versions.room", - roomKtx : "androidx.room:room-ktx:$versions.room", - viewModelKtx : "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.viewModelKtx" + support : "androidx.legacy:legacy-support-v4:$versions.androidLegacySupport", + design : "com.google.android.material:material:$versions.androidSupportDesign", + annotations : "com.android.support:support-annotations:$versions.androidSupport", + multidex : "androidx.multidex:multidex:$versions.multidex", + constraintLayout : "androidx.constraintlayout:constraintlayout:$versions.constraintLayout", + room : "androidx.room:room-compiler:$versions.room", + roomRuntime : "androidx.room:room-runtime:$versions.room", + roomKtx : "androidx.room:room-ktx:$versions.room", + viewModelKtx : "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.viewModelKtx", + navigationFragment : "androidx.navigation:navigation-fragment:$versions.navigation", + navigationUi : "androidx.navigation:navigation-ui:$versions.navigation", + navigationFragmentKtx : "androidx.navigation:navigation-fragment-ktx:$versions.navigation", + navigationUiKtx : "androidx.navigation:navigation-ui-ktx:$versions.navigation", + navigationFeature : "androidx.navigation:navigation-dynamic-features-fragment:$versions.navigation", + preferences : "androidx.preference:preference:$versions.preferences", ] ext.other = [ diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 4701f7df..119b08b4 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'jacoco' +apply plugin: 'androidx.navigation.safeargs' apply from: "../gradle_scripts/code_quality.gradle" android { @@ -50,6 +51,10 @@ android { warning 'MissingTranslation' abortOnError true } + + kotlinOptions { + jvmTarget = "1.8" + } } tasks.withType(Test) { @@ -71,6 +76,13 @@ dependencies { implementation androidSupport.roomKtx implementation androidSupport.viewModelKtx implementation androidSupport.constraintLayout + implementation androidSupport.preferences + + implementation androidSupport.navigationFragment + implementation androidSupport.navigationUi + implementation androidSupport.navigationFragmentKtx + implementation androidSupport.navigationUiKtx + implementation androidSupport.navigationFeature implementation other.kotlinStdlib implementation other.kotlinxCoroutines diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml index 515a6f3d..9ec6b2eb 100644 --- a/ultrasonic/src/main/AndroidManifest.xml +++ b/ultrasonic/src/main/AndroidManifest.xml @@ -23,24 +23,36 @@ android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" - android:theme="@style/Theme.AppCompat" + android:theme="@style/NoActionBar" android:name=".app.UApp" android:label="@string/common.appname" android:usesCleartextTraffic="true"> + + + + + + + + + + + - + - @@ -49,38 +61,11 @@ android:configChanges="orientation|keyboardHidden" android:label="@string/search.label" android:launchMode="singleTask"/> - - - - - - - - @@ -102,7 +83,7 @@ - @@ -114,7 +95,7 @@ android:name="android.app.searchable" android:resource="@xml/searchable"/> - +--> diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java deleted file mode 100644 index 23301024..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - 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 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.activity; - -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.ListView; - -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.domain.MusicDirectory.Entry; -import org.moire.ultrasonic.service.DownloadFile; -import org.moire.ultrasonic.service.MediaPlayerController; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.Pair; -import org.moire.ultrasonic.util.TabActivityBackgroundTask; -import org.moire.ultrasonic.util.Util; -import org.moire.ultrasonic.view.EntryAdapter; - -import java.util.ArrayList; -import java.util.List; - -public class BookmarkActivity extends SubsonicTabActivity -{ - - private SwipeRefreshLayout refreshAlbumListView; - private ListView albumListView; - private View albumButtons; - private View emptyView; - private ImageView playNowButton; - private ImageView pinButton; - private ImageView unpinButton; - private ImageView downloadButton; - private ImageView deleteButton; - - /** - * Called when the activity is first created. - */ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.select_album); - - albumButtons = findViewById(R.id.menu_album); - - refreshAlbumListView = findViewById(R.id.select_album_entries_refresh); - albumListView = findViewById(R.id.select_album_entries_list); - - refreshAlbumListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() - { - @Override - public void onRefresh() - { - new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - - albumListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - - albumListView.setOnItemClickListener(new AdapterView.OnItemClickListener() - { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - if (position >= 0) - { - Entry entry = (Entry) parent.getItemAtPosition(position); - - if (entry != null) - { - if (entry.isVideo()) - { - playVideo(entry); - } - else - { - enableButtons(); - } - } - } - } - }); - - ImageView selectButton = (ImageView) findViewById(R.id.select_album_select); - playNowButton = (ImageView) findViewById(R.id.select_album_play_now); - ImageView playNextButton = (ImageView) findViewById(R.id.select_album_play_next); - ImageView playLastButton = (ImageView) findViewById(R.id.select_album_play_last); - pinButton = (ImageView) findViewById(R.id.select_album_pin); - unpinButton = (ImageView) findViewById(R.id.select_album_unpin); - downloadButton = (ImageView) findViewById(R.id.select_album_download); - deleteButton = (ImageView) findViewById(R.id.select_album_delete); - ImageView oreButton = (ImageView) findViewById(R.id.select_album_more); - emptyView = findViewById(R.id.select_album_empty); - - selectButton.setVisibility(View.GONE); - playNextButton.setVisibility(View.GONE); - playLastButton.setVisibility(View.GONE); - oreButton.setVisibility(View.GONE); - - playNowButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - playNow(getSelectedSongs(albumListView)); - } - }); - - selectButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - selectAllOrNone(); - } - }); - pinButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - downloadBackground(true); - selectAll(false, false); - } - }); - unpinButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - unpin(); - selectAll(false, false); - } - }); - downloadButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - downloadBackground(false); - selectAll(false, false); - } - }); - deleteButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - delete(); - selectAll(false, false); - } - }); - - registerForContextMenu(albumListView); - - enableButtons(); - - View browseMenuItem = findViewById(R.id.menu_bookmarks); - menuDrawer.setActiveView(browseMenuItem); - - getBookmarks(); - } - - private void getBookmarks() - { - setActionBarSubtitle(R.string.button_bar_bookmarks); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - return Util.getSongsFromBookmarks(service.getBookmarks(BookmarkActivity.this, this)); - } - }.execute(); - } - - private void playNow(List songs) - { - if (!getSelectedSongs(albumListView).isEmpty()) - { - int position = songs.get(0).getBookmarkPosition(); - if (getMediaPlayerController() == null) return; - getMediaPlayerController().restore(songs, 0, position, true, true); - selectAll(false, false); - } - } - - private static List getSelectedSongs(ListView albumListView) - { - List songs = new ArrayList(10); - - if (albumListView != null) - { - int count = albumListView.getCount(); - for (int i = 0; i < count; i++) - { - if (albumListView.isItemChecked(i)) - { - songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(i)); - } - } - } - - return songs; - } - - private void refresh() - { - finish(); - Intent intent = getIntent(); - intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); - startActivityForResultWithoutTransition(this, intent); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - menuDrawer.toggleMenu(); - return true; - } - - return false; - } - - private void selectAllOrNone() - { - boolean someUnselected = false; - int count = albumListView.getCount(); - - for (int i = 0; i < count; i++) - { - if (!albumListView.isItemChecked(i) && albumListView.getItemAtPosition(i) instanceof MusicDirectory.Entry) - { - someUnselected = true; - break; - } - } - - selectAll(someUnselected, true); - } - - private void selectAll(boolean selected, boolean toast) - { - int count = albumListView.getCount(); - int selectedCount = 0; - - for (int i = 0; i < count; i++) - { - MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i); - if (entry != null && !entry.isDirectory() && !entry.isVideo()) - { - albumListView.setItemChecked(i, selected); - selectedCount++; - } - } - - // Display toast: N tracks selected / N tracks unselected - if (toast) - { - int toastResId = selected ? R.string.select_album_n_selected : R.string.select_album_n_unselected; - Util.toast(this, getString(toastResId, selectedCount)); - } - - enableButtons(); - } - - private void enableButtons() - { - MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (mediaPlayerController == null) - { - return; - } - - List selection = getSelectedSongs(albumListView); - boolean enabled = !selection.isEmpty(); - boolean unpinEnabled = false; - boolean deleteEnabled = false; - - int pinnedCount = 0; - - for (MusicDirectory.Entry song : selection) - { - DownloadFile downloadFile = mediaPlayerController.getDownloadFileForSong(song); - if (downloadFile.isWorkDone()) - { - deleteEnabled = true; - } - - if (downloadFile.isSaved()) - { - pinnedCount++; - unpinEnabled = true; - } - } - - playNowButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE); - pinButton.setVisibility((enabled && !ActiveServerProvider.Companion.isOffline(this) && selection.size() > pinnedCount) ? View.VISIBLE : View.GONE); - unpinButton.setVisibility(enabled && unpinEnabled ? View.VISIBLE : View.GONE); - downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(this) ? View.VISIBLE : View.GONE); - deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE); - } - - private void downloadBackground(final boolean save) - { - List songs = getSelectedSongs(albumListView); - - if (songs.isEmpty()) - { - selectAll(true, false); - songs = getSelectedSongs(albumListView); - } - - downloadBackground(save, songs); - } - - private void downloadBackground(final boolean save, final List songs) - { - if (getMediaPlayerController() == null) - { - return; - } - - Runnable onValid = new Runnable() - { - @Override - public void run() - { - warnIfNetworkOrStorageUnavailable(); - getMediaPlayerController().downloadBackground(songs, save); - - if (save) - { - Util.toast(BookmarkActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size())); - } - else - { - Util.toast(BookmarkActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size())); - } - } - }; - - checkLicenseAndTrialPeriod(onValid); - } - - private void delete() - { - List songs = getSelectedSongs(albumListView); - - if (songs.isEmpty()) - { - selectAll(true, false); - songs = getSelectedSongs(albumListView); - } - - if (getMediaPlayerController() != null) - { - getMediaPlayerController().delete(songs); - } - } - - private void unpin() - { - if (getMediaPlayerController() != null) - { - List songs = getSelectedSongs(albumListView); - Util.toast(BookmarkActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); - getMediaPlayerController().unpin(songs); - } - } - - private abstract class LoadTask extends TabActivityBackgroundTask> - { - - public LoadTask() - { - super(BookmarkActivity.this, true); - } - - protected abstract MusicDirectory load(MusicService service) throws Exception; - - @Override - protected Pair doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(BookmarkActivity.this); - MusicDirectory dir = load(musicService); - boolean valid = musicService.isLicenseValid(BookmarkActivity.this, this); - return new Pair(dir, valid); - } - - @Override - protected void done(Pair result) - { - MusicDirectory musicDirectory = result.getFirst(); - List entries = musicDirectory.getChildren(); - - int songCount = 0; - for (MusicDirectory.Entry entry : entries) - { - if (!entry.isDirectory()) - { - songCount++; - } - } - - final int listSize = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); - - if (songCount > 0) - { - pinButton.setVisibility(View.VISIBLE); - unpinButton.setVisibility(View.VISIBLE); - downloadButton.setVisibility(View.VISIBLE); - deleteButton.setVisibility(View.VISIBLE); - playNowButton.setVisibility(View.VISIBLE); - } - else - { - pinButton.setVisibility(View.GONE); - unpinButton.setVisibility(View.GONE); - downloadButton.setVisibility(View.GONE); - deleteButton.setVisibility(View.GONE); - playNowButton.setVisibility(View.GONE); - - if (listSize == 0 || result.getFirst().getChildren().size() < listSize) - { - albumButtons.setVisibility(View.GONE); - } - } - - enableButtons(); - - emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE); - - albumListView.setAdapter(new EntryAdapter(BookmarkActivity.this, getImageLoader(), entries, true)); - licenseValid = result.getSecond(); - } - } - - private class GetDataTask extends AsyncTask - { - @Override - protected void onPostExecute(String[] result) - { - super.onPostExecute(result); - } - - @Override - protected String[] doInBackground(Void... params) - { - refresh(); - return null; - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java deleted file mode 100644 index 83a20677..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java +++ /dev/null @@ -1,310 +0,0 @@ -package org.moire.ultrasonic.activity; - -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.domain.ChatMessage; -import org.moire.ultrasonic.service.JukeboxMediaPlayer; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.util.BackgroundTask; -import org.moire.ultrasonic.util.TabActivityBackgroundTask; -import org.moire.ultrasonic.util.Util; -import org.moire.ultrasonic.view.ChatAdapter; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - -import kotlin.Lazy; - -import static org.koin.java.KoinJavaComponent.inject; - -/** - * @author Joshua Bahnsen - */ -public final class ChatActivity extends SubsonicTabActivity -{ - private ListView chatListView; - private EditText messageEditText; - private ImageButton sendButton; - private Timer timer; - private volatile static Long lastChatMessageTime = (long) 0; - private volatile static ArrayList messageList = new ArrayList(); - private Lazy activeServerProvider = inject(ActiveServerProvider.class); - - @Override - protected void onCreate(Bundle bundle) - { - super.onCreate(bundle); - setContentView(R.layout.chat); - - messageEditText = (EditText) findViewById(R.id.chat_edittext); - sendButton = (ImageButton) findViewById(R.id.chat_send); - - sendButton.setOnClickListener(new View.OnClickListener() - { - - @Override - public void onClick(View view) - { - sendMessage(); - } - }); - - chatListView = findViewById(R.id.chat_entries_list); - chatListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); - chatListView.setStackFromBottom(true); - - String serverName = activeServerProvider.getValue().getActiveServer().getName(); - String userName = activeServerProvider.getValue().getActiveServer().getUserName(); - String title = String.format("%s [%s@%s]", getResources().getString(R.string.button_bar_chat), userName, serverName); - - setActionBarSubtitle(title); - - messageEditText.setImeActionLabel("Send", KeyEvent.KEYCODE_ENTER); - - messageEditText.addTextChangedListener(new TextWatcher() - { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) - { - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) - { - } - - @Override - public void afterTextChanged(Editable editable) - { - sendButton.setEnabled(!Util.isNullOrWhiteSpace(editable.toString())); - } - }); - - messageEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() - { - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) - { - if (actionId == EditorInfo.IME_ACTION_DONE || (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN)) - { - sendMessage(); - return true; - } - - return false; - } - }); - - View chatMenuItem = findViewById(R.id.menu_chat); - menuDrawer.setActiveView(chatMenuItem); - - load(); - } - - @Override - protected void onPostCreate(Bundle bundle) - { - super.onPostCreate(bundle); - - timerMethod(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.chat, menu); - super.onCreateOptionsMenu(menu); - - return true; - } - - /* - * Listen for option item selections so that we receive a notification - * when the user requests a refresh by selecting the refresh action bar item. - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - // Check if user triggered a refresh: - case R.id.menu_refresh: - // Start the refresh background task. - new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - return true; - } - // User didn't trigger a refresh, let the superclass handle this action - return super.onOptionsItemSelected(item); - } - - @Override - protected void onResume() - { - super.onResume(); - - if (!messageList.isEmpty()) - { - ListAdapter chatAdapter = new ChatAdapter(ChatActivity.this, messageList); - chatListView.setAdapter(chatAdapter); - } - - if (timer == null) - { - timerMethod(); - } - } - - @Override - protected void onPause() - { - super.onPause(); - - if (timer != null) - { - timer.cancel(); - timer = null; - } - } - - private void timerMethod() - { - int refreshInterval = Util.getChatRefreshInterval(this); - - if (refreshInterval > 0) - { - timer = new Timer(); - - timer.schedule(new TimerTask() - { - @Override - public void run() - { - ChatActivity.this.runOnUiThread(new Runnable() - { - @Override - public void run() - { - load(); - } - }); - } - }, refreshInterval, refreshInterval); - } - } - - private void sendMessage() - { - if (messageEditText != null) - { - final String message; - Editable text = messageEditText.getText(); - - if (text == null) - { - return; - } - - message = text.toString(); - - if (!Util.isNullOrWhiteSpace(message)) - { - messageEditText.setText(""); - - BackgroundTask task = new TabActivityBackgroundTask(ChatActivity.this, false) - { - @Override - protected Void doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(ChatActivity.this); - musicService.addChatMessage(message, ChatActivity.this, this); - return null; - } - - @Override - protected void done(Void result) - { - load(); - } - }; - - task.execute(); - } - } - } - - private synchronized void load() - { - BackgroundTask> task = new TabActivityBackgroundTask>(this, false) - { - @Override - protected List doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(ChatActivity.this); - return musicService.getChatMessages(lastChatMessageTime, ChatActivity.this, this); - } - - @Override - protected void done(List result) - { - if (result != null && !result.isEmpty()) - { - // Reset lastChatMessageTime if we have a newer message - for (ChatMessage message : result) - { - if (message.getTime() > lastChatMessageTime) - { - lastChatMessageTime = message.getTime(); - } - } - - // Reverse results to show them on the bottom - Collections.reverse(result); - messageList.addAll(result); - - ListAdapter chatAdapter = new ChatAdapter(ChatActivity.this, messageList); - chatListView.setAdapter(chatAdapter); - } - } - }; - - task.execute(); - } - - private class GetDataTask extends AsyncTask - { - @Override - protected void onPostExecute(String[] result) - { - load(); - super.onPostExecute(result); - } - - @Override - protected String[] doInBackground(Void... params) - { - return null; - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java index 7c9c8057..99f4e536 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java @@ -1404,7 +1404,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi artistTextView.setText(currentSong.getArtist()); downloadTrackTextView.setText(trackFormat); downloadTotalDurationTextView.setText(duration); - getImageLoader().loadImage(albumArtImageView, currentSong, true, 0, false, true); + imageLoader.getValue().getImageLoader().loadImage(albumArtImageView, currentSong, true, 0, false, true); displaySongRating(); } @@ -1416,7 +1416,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi artistTextView.setText(null); downloadTrackTextView.setText(null); downloadTotalDurationTextView.setText(null); - getImageLoader().loadImage(albumArtImageView, null, true, 0, false, true); + imageLoader.getValue().getImageLoader().loadImage(albumArtImageView, null, true, 0, false, true); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java deleted file mode 100644 index 86672bcc..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - 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 2009 (C) Sindre Mehus - */ - -package org.moire.ultrasonic.activity; - -import android.content.Intent; -import android.os.Bundle; -import androidx.appcompat.app.ActionBar; -import android.view.KeyEvent; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.Window; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.Button; -import android.widget.ImageView; - -import net.simonvt.menudrawer.MenuDrawer; -import net.simonvt.menudrawer.Position; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.Util; - -/** - * An HTML-based help screen with Back and Done buttons at the bottom. - * - * @author Sindre Mehus - */ -public final class HelpActivity extends ResultActivity implements OnClickListener -{ - private WebView webView; - private ImageView backButton; - private ImageView forwardButton; - - private static final String STATE_MENUDRAWER = "org.moire.ultrasonic.menuDrawer"; - private static final String STATE_ACTIVE_VIEW_ID = "org.moire.ultrasonic.activeViewId"; - private static final String STATE_ACTIVE_POSITION = "org.moire.ultrasonic.activePosition"; - - public MenuDrawer menuDrawer; - private int activePosition = 1; - private int menuActiveViewId; - View chatMenuItem; - View bookmarksMenuItem; - View sharesMenuItem; - - @Override - protected void onCreate(Bundle bundle) - { - Util.applyTheme(this); - getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - super.onCreate(bundle); - - setContentView(R.layout.help); - - if (bundle != null) - { - activePosition = bundle.getInt(STATE_ACTIVE_POSITION); - menuActiveViewId = bundle.getInt(STATE_ACTIVE_VIEW_ID); - } - - menuDrawer = MenuDrawer.attach(this, MenuDrawer.Type.BEHIND, Position.LEFT, MenuDrawer.MENU_DRAG_WINDOW); - menuDrawer.setMenuView(R.layout.menu_main); - - chatMenuItem = findViewById(R.id.menu_chat); - bookmarksMenuItem = findViewById(R.id.menu_bookmarks); - sharesMenuItem = findViewById(R.id.menu_shares); - View aboutMenuItem = findViewById(R.id.menu_about); - - findViewById(R.id.menu_home).setOnClickListener(this); - findViewById(R.id.menu_browse).setOnClickListener(this); - findViewById(R.id.menu_search).setOnClickListener(this); - findViewById(R.id.menu_playlists).setOnClickListener(this); - sharesMenuItem.setOnClickListener(this); - chatMenuItem.setOnClickListener(this); - bookmarksMenuItem.setOnClickListener(this); - findViewById(R.id.menu_now_playing).setOnClickListener(this); - findViewById(R.id.menu_settings).setOnClickListener(this); - aboutMenuItem.setOnClickListener(this); - findViewById(R.id.menu_exit).setOnClickListener(this); - - ActionBar actionBar = getSupportActionBar(); - - if (actionBar != null) - { - actionBar.setDisplayHomeAsUpEnabled(true); - } - - menuDrawer.setActiveView(aboutMenuItem); - - webView = (WebView) findViewById(R.id.help_contents); - webView.getSettings().setJavaScriptEnabled(true); - webView.setWebViewClient(new HelpClient()); - - if (bundle != null) - { - webView.restoreState(bundle); - } - else - { - webView.loadUrl(getResources().getString(R.string.help_url)); - } - - backButton = (ImageView) findViewById(R.id.help_back); - backButton.setOnClickListener(new Button.OnClickListener() - { - @Override - public void onClick(View view) - { - webView.goBack(); - } - }); - - ImageView stopButton = (ImageView) findViewById(R.id.help_stop); - stopButton.setOnClickListener(new Button.OnClickListener() - { - @Override - public void onClick(View view) - { - webView.stopLoading(); - setProgressBarIndeterminateVisibility(false); - } - }); - - forwardButton = (ImageView) findViewById(R.id.help_forward); - forwardButton.setOnClickListener(new Button.OnClickListener() - { - @Override - public void onClick(View view) - { - webView.goForward(); - } - }); - } - - @Override - protected void onPostCreate(Bundle bundle) - { - super.onPostCreate(bundle); - - int visibility = ActiveServerProvider.Companion.isOffline(this) ? View.GONE : View.VISIBLE; - chatMenuItem.setVisibility(visibility); - bookmarksMenuItem.setVisibility(visibility); - sharesMenuItem.setVisibility(visibility); - } - - @Override - public void onResume() - { - super.onResume(); - } - - @Override - protected void onSaveInstanceState(Bundle state) - { - webView.saveState(state); - super.onSaveInstanceState(state); - state.putParcelable(STATE_MENUDRAWER, menuDrawer.saveState()); - state.putInt(STATE_ACTIVE_VIEW_ID, menuActiveViewId); - state.putInt(STATE_ACTIVE_POSITION, activePosition); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) - { - if (keyCode == KeyEvent.KEYCODE_BACK) - { - if (webView.canGoBack()) - { - webView.goBack(); - return true; - } - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - menuDrawer.toggleMenu(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - protected void onRestoreInstanceState(Bundle state) - { - super.onRestoreInstanceState(state); - menuDrawer.restoreState(state.getParcelable(STATE_MENUDRAWER)); - } - - @Override - public void onBackPressed() - { - final int drawerState = menuDrawer.getDrawerState(); - - if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING) - { - menuDrawer.closeMenu(true); - return; - } - - finish(); - - super.onBackPressed(); - } - - @Override - public void onClick(View v) - { - menuActiveViewId = v.getId(); - menuDrawer.setActiveView(v); - - Intent intent; - - switch (menuActiveViewId) - { - case R.id.menu_home: - - intent = new Intent(this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(this, intent); - break; - case R.id.menu_browse: - intent = new Intent(this, SelectArtistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(this, intent); - break; - case R.id.menu_search: - intent = new Intent(this, SearchActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); - startActivityForResultWithoutTransition(this, intent); - break; - case R.id.menu_playlists: - intent = new Intent(this, SelectPlaylistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(this, intent); - break; - case R.id.menu_shares: - intent = new Intent(this, ShareActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(this, intent); - break; - case R.id.menu_chat: - startActivityForResultWithoutTransition(this, ChatActivity.class); - break; - case R.id.menu_bookmarks: - startActivityForResultWithoutTransition(this, BookmarkActivity.class); - break; - case R.id.menu_now_playing: - startActivityForResultWithoutTransition(this, DownloadActivity.class); - break; - case R.id.menu_settings: - startActivityForResultWithoutTransition(this, SettingsActivity.class); - break; - case R.id.menu_about: - startActivityForResultWithoutTransition(this, HelpActivity.class); - break; - case R.id.menu_exit: - intent = new Intent(this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); - startActivityForResultWithoutTransition(this, intent); - break; - } - - menuDrawer.closeMenu(true); - } - - private final class HelpClient extends WebViewClient - { - @Override - public void onLoadResource(WebView webView, String url) - { - setProgressBarIndeterminateVisibility(true); - super.onLoadResource(webView, url); - } - - @Override - public void onPageFinished(WebView view, String url) - { - setProgressBarIndeterminateVisibility(false); - String versionName = Util.getVersionName(HelpActivity.this); - String title = String.format("%s (%s)", view.getTitle(), versionName); - ActionBar actionBar = getSupportActionBar(); - - if (actionBar != null) - { - actionBar.setSubtitle(title); - } - - backButton.setEnabled(view.canGoBack()); - forwardButton.setEnabled(view.canGoForward()); - } - - @Override - public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) - { - Util.toast(HelpActivity.this, description); - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java index f467bde0..946bef64 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java @@ -67,161 +67,8 @@ public class MainActivity extends SubsonicTabActivity { super.onCreate(savedInstanceState); - // Determine first run and migrate server settings to DB as early as possible - boolean showWelcomeScreen = Util.isFirstRun(this); - boolean areServersMigrated = serverSettingsModel.getValue().migrateFromPreferences(); - - // If there are any servers in the DB, do not show the welcome screen - showWelcomeScreen &= !areServersMigrated; - - if (getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_EXIT)) - { - setResult(Constants.RESULT_CLOSE_ALL); - - if (getMediaPlayerController() != null) - { - getMediaPlayerController().stopJukeboxService(); - } - - if (getImageLoader() != null) - { - getImageLoader().stopImageLoader(); - } - - finish(); - exit(); - return; - } - setContentView(R.layout.main); - loadSettings(); - - final View buttons = LayoutInflater.from(this).inflate(R.layout.main_buttons, null); - final View serverButton = buttons.findViewById(R.id.main_select_server); - final TextView serverTextView = serverButton.findViewById(R.id.main_select_server_2); - final View musicTitle = buttons.findViewById(R.id.main_music); - final View artistsButton = buttons.findViewById(R.id.main_artists_button); - final View albumsButton = buttons.findViewById(R.id.main_albums_button); - final View genresButton = buttons.findViewById(R.id.main_genres_button); - final View videosTitle = buttons.findViewById(R.id.main_videos_title); - final View songsTitle = buttons.findViewById(R.id.main_songs); - final View randomSongsButton = buttons.findViewById(R.id.main_songs_button); - final View songsStarredButton = buttons.findViewById(R.id.main_songs_starred); - final View albumsTitle = buttons.findViewById(R.id.main_albums); - final View albumsNewestButton = buttons.findViewById(R.id.main_albums_newest); - final View albumsRandomButton = buttons.findViewById(R.id.main_albums_random); - final View albumsHighestButton = buttons.findViewById(R.id.main_albums_highest); - final View albumsStarredButton = buttons.findViewById(R.id.main_albums_starred); - final View albumsRecentButton = buttons.findViewById(R.id.main_albums_recent); - final View albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent); - final View albumsAlphaByNameButton = buttons.findViewById(R.id.main_albums_alphaByName); - final View albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist); - final View videosButton = buttons.findViewById(R.id.main_videos); - - lastActiveServerProperties = getActiveServerProperties(); - String name = activeServerProvider.getValue().getActiveServer().getName(); - - serverTextView.setText(name); - - final ListView list = findViewById(R.id.main_list); - - final MergeAdapter adapter = new MergeAdapter(); - adapter.addViews(Collections.singletonList(serverButton), true); - - if (!ActiveServerProvider.Companion.isOffline(this)) - { - adapter.addView(musicTitle, false); - adapter.addViews(asList(artistsButton, albumsButton, genresButton), true); - adapter.addView(songsTitle, false); - adapter.addViews(asList(randomSongsButton, songsStarredButton), true); - adapter.addView(albumsTitle, false); - - if (Util.getShouldUseId3Tags(MainActivity.this)) - { - shouldUseId3 = true; - adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true); - } - else - { - shouldUseId3 = false; - adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsHighestButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true); - } - - adapter.addView(videosTitle, false); - adapter.addViews(Collections.singletonList(videosButton), true); - } - - list.setAdapter(adapter); - - list.setOnItemClickListener(new AdapterView.OnItemClickListener() - { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - if (view == serverButton) - { - showServers(); - } - else if (view == albumsNewestButton) - { - showAlbumList("newest", R.string.main_albums_newest); - } - else if (view == albumsRandomButton) - { - showAlbumList("random", R.string.main_albums_random); - } - else if (view == albumsHighestButton) - { - showAlbumList("highest", R.string.main_albums_highest); - } - else if (view == albumsRecentButton) - { - showAlbumList("recent", R.string.main_albums_recent); - } - else if (view == albumsFrequentButton) - { - showAlbumList("frequent", R.string.main_albums_frequent); - } - else if (view == albumsStarredButton) - { - showAlbumList(Constants.STARRED, R.string.main_albums_starred); - } - else if (view == albumsAlphaByNameButton) - { - showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName); - } - else if (view == albumsAlphaByArtistButton) - { - showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist); - } - else if (view == songsStarredButton) - { - showStarredSongs(); - } - else if (view == artistsButton) - { - showArtists(); - } - else if (view == albumsButton) - { - showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title); - } - else if (view == randomSongsButton) - { - showRandomSongs(); - } - else if (view == genresButton) - { - showGenres(); - } - else if (view == videosButton) - { - showVideos(); - } - } - }); - final View homeMenuItem = findViewById(R.id.menu_home); menuDrawer.setActiveView(homeMenuItem); @@ -230,46 +77,8 @@ public class MainActivity extends SubsonicTabActivity // Remember the current theme. theme = Util.getTheme(this); - - showInfoDialog(showWelcomeScreen); } - private void loadSettings() - { - PreferenceManager.setDefaultValues(this, R.xml.settings, false); - final SharedPreferences preferences = Util.getPreferences(this); - - if (!preferences.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION)) - { - final SharedPreferences.Editor editor = preferences.edit(); - editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(this).getPath()); - editor.apply(); - } - } - - @Override - protected void onResume() - { - super.onResume(); - boolean shouldRestart = false; - - boolean id3 = Util.getShouldUseId3Tags(MainActivity.this); - String currentActiveServerProperties = getActiveServerProperties(); - - if (id3 != shouldUseId3) - { - shouldUseId3 = id3; - shouldRestart = true; - } - - if (!currentActiveServerProperties.equals(lastActiveServerProperties)) - { - lastActiveServerProperties = currentActiveServerProperties; - shouldRestart = true; - } - - if (shouldRestart) restart(); - } @Override public boolean onCreateOptionsMenu(final Menu menu) @@ -298,85 +107,4 @@ public class MainActivity extends SubsonicTabActivity return false; } - - private void exit() - { - lifecycleSupport.getValue().onDestroy(); - Util.unregisterMediaButtonEventReceiver(this, false); - finish(); - } - - private void showInfoDialog(final boolean show) - { - if (!infoDialogDisplayed) - { - infoDialogDisplayed = true; - - if (show) - { - Util.showWelcomeDialog(this, this, R.string.main_welcome_title, R.string.main_welcome_text); - } - } - } - - private void showAlbumList(final String type, final int title) - { - final Intent intent = new Intent(this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxAlbums(this)); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); - startActivityForResultWithoutTransition(this, intent); - } - - private void showStarredSongs() - { - final Intent intent = new Intent(this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_STARRED, 1); - startActivityForResultWithoutTransition(this, intent); - } - - private void showRandomSongs() - { - final Intent intent = new Intent(this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_RANDOM, 1); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(this)); - startActivityForResultWithoutTransition(this, intent); - } - - private void showArtists() - { - final Intent intent = new Intent(this, SelectArtistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, getResources().getString(R.string.main_artists_title)); - startActivityForResultWithoutTransition(this, intent); - } - - private void showGenres() - { - final Intent intent = new Intent(this, SelectGenreActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(this, intent); - } - - private void showVideos() - { - final Intent intent = new Intent(this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_VIDEOS, 1); - startActivityForResultWithoutTransition(this, intent); - } - - private void showServers() - { - final Intent intent = new Intent(this, ServerSelectorActivity.class); - startActivityForResult(intent, 0); - } - - private String getActiveServerProperties() - { - ServerSetting currentSetting = activeServerProvider.getValue().getActiveServer(); - return String.format("%s;%s;%s;%s;%s;%s", currentSetting.getUrl(), currentSetting.getUserName(), - currentSetting.getPassword(), currentSetting.getAllowSelfSignedCertificate(), - currentSetting.getLdapSupport(), currentSetting.getMinimumApiVersion()); - } } \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java deleted file mode 100644 index 2b7e3958..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - 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 2009 (C) Sindre Mehus - */ - -package org.moire.ultrasonic.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.domain.PodcastsChannel; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.util.BackgroundTask; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.TabActivityBackgroundTask; -import org.moire.ultrasonic.view.PodcastsChannelsAdapter; - -import java.util.List; - -public class PodcastsActivity extends SubsonicTabActivity { - - private View emptyTextView; - SubsonicTabActivity currentActivity = null; - ListView channelItemsListView = null; - - Context currentContext = (Context)this; - - @Override - public void onCreate(Bundle savedInstanceState) - { - this.currentActivity = this; - - super.onCreate(savedInstanceState); - setContentView(R.layout.podcasts); - - - emptyTextView = findViewById(R.id.select_podcasts_empty); - channelItemsListView = (ListView)findViewById(R.id.podcasts_channels_items_list); - channelItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - PodcastsChannel pc = (PodcastsChannel) parent.getItemAtPosition(position); - if (pc == null) { - return; - } - - Intent intent = new Intent(currentContext, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID, pc.getId()); - startActivityForResultWithoutTransition(PodcastsActivity.this, intent); - } - }); - - load(); - } - - - - private void load() - { - BackgroundTask> task = new TabActivityBackgroundTask>(this, true) - { - @Override - protected List doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(PodcastsActivity.this); - List channels = musicService.getPodcastsChannels(false,PodcastsActivity.this, this); - - /* TODO c'est quoi ce nettoyage de cache ? - if (!Util.isOffline(PodcastsActivity.this)) - new CacheCleaner(PodcastsActivity.this, getDownloadService()).cleanPlaylists(playlists); - */ - return channels; - } - - @Override - protected void done(List result) - { - channelItemsListView.setAdapter(new PodcastsChannelsAdapter(currentActivity, result)); - emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); - } - }; - task.execute(); - } - - -} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java index 11bf4078..ee4015dc 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java @@ -41,6 +41,7 @@ import org.moire.ultrasonic.domain.SearchResult; import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.subsonic.VideoPlayer; import org.moire.ultrasonic.util.BackgroundTask; import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.MergeAdapter; @@ -53,6 +54,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import kotlin.Lazy; + +import static org.koin.java.KoinJavaComponent.inject; + /** * Performs searches and displays the matching artists, albums and songs. * @@ -83,6 +88,8 @@ public class SearchActivity extends SubsonicTabActivity private ListAdapter moreSongsAdapter; private EntryAdapter songAdapter; + private final Lazy videoPlayer = inject(VideoPlayer.class); + @Override public void onCreate(Bundle savedInstanceState) { @@ -419,7 +426,7 @@ public class SearchActivity extends SubsonicTabActivity { mergeAdapter.addView(albumsHeading); List displayedAlbums = new ArrayList(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size()))); - albumAdapter = new EntryAdapter(this, getImageLoader(), displayedAlbums, false); + albumAdapter = new EntryAdapter(this, imageLoader.getValue().getImageLoader(), displayedAlbums, false); mergeAdapter.addAdapter(albumAdapter); if (albums.size() > DEFAULT_ALBUMS) { @@ -432,7 +439,7 @@ public class SearchActivity extends SubsonicTabActivity { mergeAdapter.addView(songsHeading); List displayedSongs = new ArrayList(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size()))); - songAdapter = new EntryAdapter(this, getImageLoader(), displayedSongs, false); + songAdapter = new EntryAdapter(this, imageLoader.getValue().getImageLoader(), displayedSongs, false); mergeAdapter.addAdapter(songAdapter); if (songs.size() > DEFAULT_SONGS) { @@ -530,7 +537,7 @@ public class SearchActivity extends SubsonicTabActivity private void onVideoSelected(MusicDirectory.Entry entry) { - playVideo(entry); + videoPlayer.getValue().playVideo(entry); } private void autoplay() diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java index aaaaeb96..59a35d22 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java @@ -92,229 +92,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity super.onCreate(savedInstanceState); setContentView(R.layout.select_album); - albumButtons = findViewById(R.id.menu_album); - refreshAlbumListView = findViewById(R.id.select_album_entries_refresh); - albumListView = findViewById(R.id.select_album_entries_list); - - refreshAlbumListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() - { - @Override - public void onRefresh() { - new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - - header = LayoutInflater.from(this).inflate(R.layout.select_album_header, albumListView, false); - - albumListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - albumListView.setOnItemClickListener(new AdapterView.OnItemClickListener() - { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - if (position >= 0) - { - MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position); - if (entry != null && entry.isDirectory()) - { - Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, entry.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_IS_ALBUM, entry.isDirectory()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, entry.getTitle()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.getParent()); - startActivityForResultWithoutTransition(SelectAlbumActivity.this, intent); - } - else if (entry != null && entry.isVideo()) - { - playVideo(entry); - } - else - { - enableButtons(); - } - } - } - }); - - albumListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener(){ - - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - if (view instanceof AlbumView) { - AlbumView albumView = (AlbumView) view; - if (!albumView.isMaximized()) { - albumView.maximizeOrMinimize(); - return true; - } else { - return false; - } - } - if (view instanceof SongView) { - SongView songView = (SongView) view; - songView.maximizeOrMinimize(); - return true; - } - return false; - } - }); - - - selectButton = (ImageView) findViewById(R.id.select_album_select); - playNowButton = (ImageView) findViewById(R.id.select_album_play_now); - playNextButton = (ImageView) findViewById(R.id.select_album_play_next); - playLastButton = (ImageView) findViewById(R.id.select_album_play_last); - pinButton = (ImageView) findViewById(R.id.select_album_pin); - unpinButton = (ImageView) findViewById(R.id.select_album_unpin); - downloadButton = (ImageView) findViewById(R.id.select_album_download); - deleteButton = (ImageView) findViewById(R.id.select_album_delete); - moreButton = (ImageView) findViewById(R.id.select_album_more); - emptyView = findViewById(R.id.select_album_empty); - - selectButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - selectAllOrNone(); - } - }); - playNowButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - playNow(false, false); - } - }); - playNextButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - download(true, false, false, true, false, getSelectedSongs(albumListView)); - selectAll(false, false); - } - }); - playLastButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - playNow(false, true); - } - }); - pinButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - downloadBackground(true); - selectAll(false, false); - } - }); - unpinButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - unpin(); - selectAll(false, false); - } - }); - downloadButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - downloadBackground(false); - selectAll(false, false); - } - }); - deleteButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - delete(); - selectAll(false, false); - } - }); - - registerForContextMenu(albumListView); - - enableButtons(); - - String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); - boolean isAlbum = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false); - String name = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME); - String parentId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID); - String playlistId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); - String podcastChannelId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID); - String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); - String shareId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_SHARE_ID); - String shareName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_SHARE_NAME); - String albumListType = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); - String genreName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_GENRE_NAME); - int albumListTitle = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0); - int getStarredTracks = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_STARRED, 0); - int getVideos = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_VIDEOS, 0); - int getRandomTracks = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_RANDOM, 0); - int albumListSize = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); - int albumListOffset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); - - View browseMenuItem = findViewById(R.id.menu_browse); - menuDrawer.setActiveView(browseMenuItem); - - if (playlistId != null) - { - getPlaylist(playlistId, playlistName); - } - else if (podcastChannelId != null) { - getPodcastEpisodes(podcastChannelId); - } - else if (shareId != null) - { - getShare(shareId, shareName); - } - else if (albumListType != null) - { - getAlbumList(albumListType, albumListTitle, albumListSize, albumListOffset); - } - else if (genreName != null) - { - getSongsForGenre(genreName, albumListSize, albumListOffset); - } - else if (getStarredTracks != 0) - { - getStarred(); - } - else if (getVideos != 0) - { - getVideos(); - } - else if (getRandomTracks != 0) - { - getRandom(albumListSize); - } - else - { - if (!ActiveServerProvider.Companion.isOffline(SelectAlbumActivity.this) && Util.getShouldUseId3Tags(SelectAlbumActivity.this)) - { - if (isAlbum) - { - getAlbum(id, name, parentId); - } - else - { - getArtist(id, name); - } - } - else - { - getMusicDirectory(id, name, parentId); - } - } } @Override @@ -348,163 +126,8 @@ public class SelectAlbumActivity extends SubsonicTabActivity return true; } - private void playNow(final boolean shuffle, final boolean append) - { - List selectedSongs = getSelectedSongs(albumListView); - if (!selectedSongs.isEmpty()) - { - download(append, false, !append, false, shuffle, selectedSongs); - selectAll(false, false); - } - else - { - playAll(shuffle, append); - } - } - private void playAll() - { - playAll(false, false); - } - - private void playAll(final boolean shuffle, final boolean append) - { - boolean hasSubFolders = false; - - for (int i = 0; i < albumListView.getCount(); i++) - { - MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i); - if (entry != null && entry.isDirectory()) - { - hasSubFolders = true; - break; - } - } - - boolean isArtist = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_ARTIST, false); - String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); - - if (hasSubFolders && id != null) - { - downloadRecursively(id, false, append, !append, shuffle, false, false, false, isArtist); - } - else - { - selectAll(true, false); - download(append, false, !append, false, shuffle, getSelectedSongs(albumListView)); - selectAll(false, false); - } - } - - private static List getSelectedSongs(ListView albumListView) - { - List songs = new ArrayList(10); - - if (albumListView != null) - { - int count = albumListView.getCount(); - for (int i = 0; i < count; i++) - { - if (albumListView.isItemChecked(i)) - { - songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(i)); - } - } - } - - return songs; - } - - private void refresh() - { - finish(); - Intent intent = getIntent(); - intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); - startActivityForResultWithoutTransition(this, intent); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) - { - super.onCreateContextMenu(menu, view, menuInfo); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - - MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(info.position); - - if (entry != null && entry.isDirectory()) - { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_album_context, menu); - } - - shareButton = menu.findItem(R.id.menu_item_share); - - if (shareButton != null) - { - shareButton.setVisible(!ActiveServerProvider.Companion.isOffline(this)); - } - - MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download); - - if (downloadMenuItem != null) - { - downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this)); - } - } - - @Override - public boolean onContextItemSelected(MenuItem menuItem) - { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - - if (info == null) - { - return true; - } - - MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(info.position); - - if (entry == null) - { - return true; - } - - String entryId = entry.getId(); - - switch (menuItem.getItemId()) - { - case R.id.album_menu_play_now: - downloadRecursively(entryId, false, false, true, false, false, false, false, false); - break; - case R.id.album_menu_play_next: - downloadRecursively(entryId, false, false, false, false, false, true, false, false); - break; - case R.id.album_menu_play_last: - downloadRecursively(entryId, false, true, false, false, false, false, false, false); - break; - case R.id.album_menu_pin: - downloadRecursively(entryId, true, true, false, false, false, false, false, false); - break; - case R.id.album_menu_unpin: - downloadRecursively(entryId, false, false, false, false, false, false, true, false); - break; - case R.id.album_menu_download: - downloadRecursively(entryId, false, false, false, false, true, false, false, false); - break; - case R.id.select_album_play_all: - playAll(); - break; - case R.id.menu_item_share: - List entries = new ArrayList(1); - entries.add(entry); - createShare(entries); - return true; - default: - return super.onContextItemSelected(menuItem); - } - return true; - } @Override public boolean onOptionsItemSelected(MenuItem item) @@ -520,802 +143,17 @@ public class SelectAlbumActivity extends SubsonicTabActivity startActivityForResultWithoutTransition(this, intent1); return true; case R.id.select_album_play_all: - playAll(); + // TODO + //playAll(); return true; case R.id.menu_item_share: - createShare(getSelectedSongs(albumListView)); + // TODO + //createShare(getSelectedSongs(albumListView)); return true; } return false; } - private void getMusicDirectory(final String id, final String name, final String parentId) - { - setActionBarSubtitle(name); - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - MusicDirectory root = new MusicDirectory(); - - if (allSongsId.equals(id)) - { - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - MusicDirectory musicDirectory = service.getMusicDirectory(parentId, name, refresh, SelectAlbumActivity.this, this); - - List songs = new LinkedList(); - getSongsRecursively(musicDirectory, songs); - - for (MusicDirectory.Entry song : songs) - { - if (!song.isDirectory()) - { - root.addChild(song); - } - } - } - else - { - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - MusicDirectory musicDirectory = service.getMusicDirectory(id, name, refresh, SelectAlbumActivity.this, this); - - if (Util.getShouldShowAllSongsByArtist(SelectAlbumActivity.this) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size() == musicDirectory.getChildren(true, true).size()) - { - MusicDirectory.Entry allSongs = new MusicDirectory.Entry(); - - allSongs.setDirectory(true); - allSongs.setArtist(name); - allSongs.setParent(id); - allSongs.setId(allSongsId); - allSongs.setTitle(String.format(getResources().getString(R.string.select_album_all_songs), name)); - - root.addChild(allSongs); - - List children = musicDirectory.getChildren(); - - if (children != null) - { - root.addAll(children); - } - } - else - { - root = musicDirectory; - } - } - - return root; - } - - private void getSongsRecursively(MusicDirectory parent, List songs) throws Exception - { - for (MusicDirectory.Entry song : parent.getChildren(false, true)) - { - if (!song.isVideo() && !song.isDirectory()) - { - songs.add(song); - } - } - - MusicService musicService = MusicServiceFactory.getMusicService(SelectAlbumActivity.this); - - for (MusicDirectory.Entry dir : parent.getChildren(true, false)) - { - MusicDirectory root; - - if (!allSongsId.equals(dir.getId())) - { - root = musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, SelectAlbumActivity.this, this); - - getSongsRecursively(root, songs); - } - } - } - }.execute(); - } - - private void getArtist(final String id, final String name) - { - setActionBarSubtitle(name); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - MusicDirectory root = new MusicDirectory(); - - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - MusicDirectory musicDirectory = service.getArtist(id, name, refresh, SelectAlbumActivity.this, this); - - if (Util.getShouldShowAllSongsByArtist(SelectAlbumActivity.this) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size() == musicDirectory.getChildren(true, true).size()) - { - MusicDirectory.Entry allSongs = new MusicDirectory.Entry(); - - allSongs.setDirectory(true); - allSongs.setArtist(name); - allSongs.setParent(id); - allSongs.setId(allSongsId); - allSongs.setTitle(String.format(getResources().getString(R.string.select_album_all_songs), name)); - - root.addFirst(allSongs); - - List children = musicDirectory.getChildren(); - - if (children != null) - { - root.addAll(children); - } - } - else - { - root = musicDirectory; - } - - return root; - } - }.execute(); - } - - private void getAlbum(final String id, final String name, final String parentId) - { - setActionBarSubtitle(name); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - MusicDirectory musicDirectory; - - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - - if (allSongsId.equals(id)) - { - MusicDirectory root = new MusicDirectory(); - - Collection songs = new LinkedList(); - getSongsForArtist(parentId, songs); - - for (MusicDirectory.Entry song : songs) - { - if (!song.isDirectory()) - { - root.addChild(song); - } - } - - musicDirectory = root; - } - else - { - musicDirectory = service.getAlbum(id, name, refresh, SelectAlbumActivity.this, this); - } - - return musicDirectory; - } - - private void getSongsForArtist(String id, Collection songs) throws Exception - { - MusicService musicService = MusicServiceFactory.getMusicService(SelectAlbumActivity.this); - MusicDirectory artist = musicService.getArtist(id, "", false, SelectAlbumActivity.this, this); - - for (MusicDirectory.Entry album : artist.getChildren()) - { - if (!allSongsId.equals(album.getId())) - { - MusicDirectory albumDirectory = musicService.getAlbum(album.getId(), "", false, SelectAlbumActivity.this, this); - - for (MusicDirectory.Entry song : albumDirectory.getChildren()) - { - if (!song.isVideo()) - { - songs.add(song); - } - } - } - } - } - }.execute(); - } - - private void getSongsForGenre(final String genre, final int count, final int offset) - { - setActionBarSubtitle(genre); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - return service.getSongsByGenre(genre, count, offset, SelectAlbumActivity.this, this); - } - - @Override - protected void done(Pair result) - { - // Hide more button when results are less than album list size - if (result.getFirst().getChildren().size() < getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)) - { - moreButton.setVisibility(View.GONE); - } - else - { - moreButton.setVisibility(View.VISIBLE); - } - - moreButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); - String genre = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_GENRE_NAME); - int size = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); - int offset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + size; - - intent.putExtra(Constants.INTENT_EXTRA_NAME_GENRE_NAME, genre); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); - startActivityForResultWithoutTransition(SelectAlbumActivity.this, intent); - } - }); - - super.done(result); - } - }.execute(); - } - - private void getStarred() - { - setActionBarSubtitle(R.string.main_songs_starred); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - return Util.getShouldUseId3Tags(SelectAlbumActivity.this) ? Util.getSongsFromSearchResult(service.getStarred2(SelectAlbumActivity.this, this)) : Util.getSongsFromSearchResult(service.getStarred(SelectAlbumActivity.this, this)); - } - }.execute(); - } - - private void getVideos() - { - showHeader = false; - - setActionBarSubtitle(R.string.main_videos); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - return service.getVideos(refresh, SelectAlbumActivity.this, this); - } - }.execute(); - } - - private void getRandom(final int size) - { - setActionBarSubtitle(R.string.main_songs_random); - - new LoadTask() - { - @Override - protected boolean sortableCollection() { - return false; - } - - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - return service.getRandomSongs(size, SelectAlbumActivity.this, this); - } - }.execute(); - } - - private void getPlaylist(final String playlistId, final String playlistName) - { - setActionBarSubtitle(playlistName); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - return service.getPlaylist(playlistId, playlistName, SelectAlbumActivity.this, this); - } - }.execute(); - } - - private void getPodcastEpisodes(final String podcastChannelId) - { - // TODO on fait quoi là ? - //setActionBarSubtitle(playlistName); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - return service.getPodcastEpisodes(podcastChannelId, SelectAlbumActivity.this, this); - } - }.execute(); - } - - private void getShare(final String shareId, final CharSequence shareName) - { - setActionBarSubtitle(shareName); - - new LoadTask() - { - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - List shares = service.getShares(true, SelectAlbumActivity.this, this); - - MusicDirectory md = new MusicDirectory(); - - for (Share share : shares) - { - if (share.getId().equals(shareId)) - { - for (MusicDirectory.Entry entry : share.getEntries()) - { - md.addChild(entry); - } - - break; - } - } - - return md; - } - }.execute(); - } - - private void getAlbumList(final String albumListType, final int albumListTitle, final int size, final int offset) - { - showHeader = false; - - setActionBarSubtitle(albumListTitle); - - new LoadTask() - { - @Override - protected boolean sortableCollection() { - if (albumListType.equals("newest") || albumListType.equals("random") || - albumListType.equals("highest") || albumListType.equals("recent") || - albumListType.equals("frequent")) { - return false; - } - return true; - } - - @Override - protected MusicDirectory load(MusicService service) throws Exception - { - return Util.getShouldUseId3Tags(SelectAlbumActivity.this) ? service.getAlbumList2(albumListType, size, offset, SelectAlbumActivity.this, this) : service.getAlbumList(albumListType, size, offset, SelectAlbumActivity.this, this); - } - - @Override - protected void done(Pair result) - { - if (!result.getFirst().getChildren().isEmpty()) - { - pinButton.setVisibility(View.GONE); - unpinButton.setVisibility(View.GONE); - downloadButton.setVisibility(View.GONE); - deleteButton.setVisibility(View.GONE); - - // Hide more button when results are less than album list size - if (result.getFirst().getChildren().size() < getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)) - { - moreButton.setVisibility(View.GONE); - } - else - { - moreButton.setVisibility(View.VISIBLE); - - moreButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); - int albumListTitle = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0); - String type = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); - int size = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); - int offset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + size; - - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, albumListTitle); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); - startActivityForResultWithoutTransition(SelectAlbumActivity.this, intent); - } - }); - } - } - else - { - moreButton.setVisibility(View.GONE); - } - - super.done(result); - } - }.execute(); - } - - private void selectAllOrNone() - { - boolean someUnselected = false; - int count = albumListView.getCount(); - - for (int i = 0; i < count; i++) - { - if (!albumListView.isItemChecked(i) && albumListView.getItemAtPosition(i) instanceof MusicDirectory.Entry) - { - someUnselected = true; - break; - } - } - - selectAll(someUnselected, true); - } - - private void selectAll(boolean selected, boolean toast) - { - int count = albumListView.getCount(); - int selectedCount = 0; - - for (int i = 0; i < count; i++) - { - MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i); - if (entry != null && !entry.isDirectory() && !entry.isVideo()) - { - albumListView.setItemChecked(i, selected); - selectedCount++; - } - } - - // Display toast: N tracks selected / N tracks unselected - if (toast) - { - int toastResId = selected ? R.string.select_album_n_selected : R.string.select_album_n_unselected; - Util.toast(this, getString(toastResId, selectedCount)); - } - - enableButtons(); - } - - private void enableButtons() - { - MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (mediaPlayerController == null) - { - return; - } - - List selection = getSelectedSongs(albumListView); - boolean enabled = !selection.isEmpty(); - boolean unpinEnabled = false; - boolean deleteEnabled = false; - - int pinnedCount = 0; - - for (MusicDirectory.Entry song : selection) - { - DownloadFile downloadFile = mediaPlayerController.getDownloadFileForSong(song); - if (downloadFile.isWorkDone()) - { - deleteEnabled = true; - } - - if (downloadFile.isSaved()) - { - pinnedCount++; - unpinEnabled = true; - } - } - - playNowButton.setVisibility(enabled ? View.VISIBLE : View.GONE); - playNextButton.setVisibility(enabled ? View.VISIBLE : View.GONE); - playLastButton.setVisibility(enabled ? View.VISIBLE : View.GONE); - pinButton.setVisibility((enabled && !ActiveServerProvider.Companion.isOffline(this) && selection.size() > pinnedCount) ? View.VISIBLE : View.GONE); - unpinButton.setVisibility(enabled && unpinEnabled ? View.VISIBLE : View.GONE); - downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(this) ? View.VISIBLE : View.GONE); - deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE); - } - - private void downloadBackground(final boolean save) - { - List songs = getSelectedSongs(albumListView); - - if (songs.isEmpty()) - { - selectAll(true, false); - songs = getSelectedSongs(albumListView); - } - - downloadBackground(save, songs); - } - - private void downloadBackground(final boolean save, final List songs) - { - if (getMediaPlayerController() == null) - { - return; - } - - Runnable onValid = new Runnable() - { - @Override - public void run() - { - warnIfNetworkOrStorageUnavailable(); - getMediaPlayerController().downloadBackground(songs, save); - - if (save) - { - Util.toast(SelectAlbumActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size())); - } - else - { - Util.toast(SelectAlbumActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size())); - } - } - }; - - checkLicenseAndTrialPeriod(onValid); - } - - private void delete() - { - List songs = getSelectedSongs(albumListView); - - if (songs.isEmpty()) - { - selectAll(true, false); - songs = getSelectedSongs(albumListView); - } - - if (getMediaPlayerController() != null) - { - getMediaPlayerController().delete(songs); - } - } - - private void unpin() - { - if (getMediaPlayerController() != null) - { - List songs = getSelectedSongs(albumListView); - Util.toast(SelectAlbumActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); - getMediaPlayerController().unpin(songs); - } - } - - private abstract class LoadTask extends TabActivityBackgroundTask> - { - - public LoadTask() - { - super(SelectAlbumActivity.this, true); - } - - protected abstract MusicDirectory load(MusicService service) throws Exception; - - protected boolean sortableCollection() { - return true; - } - - @Override - protected Pair doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(SelectAlbumActivity.this); - MusicDirectory dir = load(musicService); - boolean valid = musicService.isLicenseValid(SelectAlbumActivity.this, this); - return new Pair(dir, valid); - } - - @Override - protected void done(Pair result) - { - MusicDirectory musicDirectory = result.getFirst(); - List entries = musicDirectory.getChildren(); - - if (sortableCollection() && Util.getShouldSortByDisc(SelectAlbumActivity.this)) - { - Collections.sort(entries, new EntryByDiscAndTrackComparator()); - } - - boolean allVideos = true; - int songCount = 0; - - for (MusicDirectory.Entry entry : entries) - { - if (!entry.isVideo()) - { - allVideos = false; - } - - if (!entry.isDirectory()) - { - songCount++; - } - } - - final int listSize = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); - - if (songCount > 0) - { - if (showHeader) - { - String intentAlbumName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME); - String directoryName = musicDirectory.getName(); - View header = createHeader(entries, intentAlbumName != null ? intentAlbumName : directoryName, songCount); - - if (header != null) - { - albumListView.addHeaderView(header, null, false); - } - } - - pinButton.setVisibility(View.VISIBLE); - unpinButton.setVisibility(View.VISIBLE); - downloadButton.setVisibility(View.VISIBLE); - deleteButton.setVisibility(View.VISIBLE); - selectButton.setVisibility(allVideos ? View.GONE : View.VISIBLE); - playNowButton.setVisibility(View.VISIBLE); - playNextButton.setVisibility(View.VISIBLE); - playLastButton.setVisibility(View.VISIBLE); - - if (listSize == 0 || songCount < listSize) - { - moreButton.setVisibility(View.GONE); - } - else - { - moreButton.setVisibility(View.VISIBLE); - - if (getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_RANDOM, 0) > 0) - { - moreButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); - int offset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + listSize; - - intent.putExtra(Constants.INTENT_EXTRA_NAME_RANDOM, 1); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, listSize); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); - startActivityForResultWithoutTransition(SelectAlbumActivity.this, intent); - } - }); - } - } - } - else - { - pinButton.setVisibility(View.GONE); - unpinButton.setVisibility(View.GONE); - downloadButton.setVisibility(View.GONE); - deleteButton.setVisibility(View.GONE); - selectButton.setVisibility(View.GONE); - playNowButton.setVisibility(View.GONE); - playNextButton.setVisibility(View.GONE); - playLastButton.setVisibility(View.GONE); - - if (listSize == 0 || result.getFirst().getChildren().size() < listSize) - { - albumButtons.setVisibility(View.GONE); - } - else - { - moreButton.setVisibility(View.VISIBLE); - } - } - - enableButtons(); - - boolean isAlbumList = getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); - playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos; - shareButtonVisible = !ActiveServerProvider.Companion.isOffline(SelectAlbumActivity.this) && songCount > 0; - - emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE); - - if (playAllButton != null) - { - playAllButton.setVisible(playAllButtonVisible); - } - - if (shareButton != null) - { - shareButton.setVisible(shareButtonVisible); - } - - albumListView.setAdapter(new EntryAdapter(SelectAlbumActivity.this, getImageLoader(), entries, true)); - licenseValid = result.getSecond(); - - boolean playAll = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); - if (playAll && songCount > 0) - { - playAll(getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), false); - } - } - - protected View createHeader(List entries, CharSequence name, int songCount) - { - ImageView coverArtView = (ImageView) header.findViewById(R.id.select_album_art); - int artworkSelection = random.nextInt(entries.size()); - getImageLoader().loadImage(coverArtView, entries.get(artworkSelection), false, Util.getAlbumImageSize(SelectAlbumActivity.this), false, true); - - AlbumHeader albumHeader = AlbumHeader.processEntries(SelectAlbumActivity.this, entries); - - TextView titleView = (TextView) header.findViewById(R.id.select_album_title); - titleView.setText(name != null ? name : getActionBarSubtitle()); - - // Don't show a header if all entries are videos - if (albumHeader.getIsAllVideo()) - { - return null; - } - - TextView artistView = (TextView) header.findViewById(R.id.select_album_artist); - String artist; - - artist = albumHeader.getArtists().size() == 1 ? albumHeader.getArtists().iterator().next() : albumHeader.getGrandParents().size() == 1 ? albumHeader.getGrandParents().iterator().next() : getResources().getString(R.string.common_various_artists); - - artistView.setText(artist); - - TextView genreView = (TextView) header.findViewById(R.id.select_album_genre); - String genre; - - genre = albumHeader.getGenres().size() == 1 ? albumHeader.getGenres().iterator().next() : getResources().getString(R.string.common_multiple_genres); - - genreView.setText(genre); - - TextView yearView = (TextView) header.findViewById(R.id.select_album_year); - String year; - - year = albumHeader.getYears().size() == 1 ? albumHeader.getYears().iterator().next().toString() : getResources().getString(R.string.common_multiple_years); - - yearView.setText(year); - - TextView songCountView = (TextView) header.findViewById(R.id.select_album_song_count); - String songs = getResources().getQuantityString(R.plurals.select_album_n_songs, songCount, songCount); - songCountView.setText(songs); - - String duration = Util.formatTotalDuration(albumHeader.getTotalDuration()); - - TextView durationView = (TextView) header.findViewById(R.id.select_album_duration); - durationView.setText(duration); - - return header; - } - } - - private class GetDataTask extends AsyncTask - { - @Override - protected void onPostExecute(String[] result) - { - super.onPostExecute(result); - } - - @Override - protected String[] doInBackground(Void... params) - { - refresh(); - return null; - } - } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectGenreActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectGenreActivity.java deleted file mode 100644 index 20ae753c..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectGenreActivity.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - 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 2009 (C) Sindre Mehus - */ - -package org.moire.ultrasonic.activity; - -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import timber.log.Timber; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.domain.Genre; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.util.BackgroundTask; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.TabActivityBackgroundTask; -import org.moire.ultrasonic.util.Util; -import org.moire.ultrasonic.view.GenreAdapter; - -import java.util.ArrayList; -import java.util.List; - -public class SelectGenreActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener -{ - private SwipeRefreshLayout refreshGenreListView; - private ListView genreListView; - private View emptyView; - - /** - * Called when the activity is first created. - */ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.select_genre); - - refreshGenreListView = findViewById(R.id.select_genre_refresh); - genreListView = findViewById(R.id.select_genre_list); - - refreshGenreListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() - { - @Override - public void onRefresh() - { - new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - - genreListView.setOnItemClickListener(this); - - emptyView = findViewById(R.id.select_genre_empty); - - registerForContextMenu(genreListView); - - View browseMenuItem = findViewById(R.id.menu_browse); - menuDrawer.setActiveView(browseMenuItem); - - setActionBarSubtitle(R.string.main_genres_title); - - load(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - super.onCreateOptionsMenu(menu); - - return true; - } - - private void refresh() - { - finish(); - Intent intent = getIntent(); - intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); - startActivityForResultWithoutTransition(this, intent); - } - - private void load() - { - BackgroundTask> task = new TabActivityBackgroundTask>(this, true) - { - @Override - protected List doInBackground() throws Throwable - { - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - MusicService musicService = MusicServiceFactory.getMusicService(SelectGenreActivity.this); - - List genres = new ArrayList(); - - try - { - genres = musicService.getGenres(refresh, SelectGenreActivity.this, this); - } - catch (Exception x) - { - Timber.e(x, "Failed to load genres"); - } - - return genres; - } - - @Override - protected void done(List result) - { - emptyView.setVisibility(result == null || result.isEmpty() ? View.VISIBLE : View.GONE); - - if (result != null) - { - genreListView.setAdapter(new GenreAdapter(SelectGenreActivity.this, result)); - } - - } - }; - task.execute(); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - Genre genre = (Genre) parent.getItemAtPosition(position); - - if (genre != null) - { - Intent intent = new Intent(this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_GENRE_NAME, genre.getName()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(this)); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); - startActivityForResultWithoutTransition(this, intent); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - menuDrawer.toggleMenu(); - return true; - case R.id.main_shuffle: - Intent intent = new Intent(this, DownloadActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true); - startActivityForResultWithoutTransition(this, intent); - return true; - } - - return false; - } - - private class GetDataTask extends AsyncTask - { - @Override - protected void onPostExecute(String[] result) - { - super.onPostExecute(result); - } - - @Override - protected String[] doInBackground(Void... params) - { - refresh(); - return null; - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java deleted file mode 100644 index 3d1ada74..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - 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 2009 (C) Sindre Mehus - */ - -package org.moire.ultrasonic.activity; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Editable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.method.LinkMovementMethod; -import android.text.util.Linkify; -import android.view.ContextMenu; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException; -import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.domain.Playlist; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.service.OfflineException; -import org.moire.ultrasonic.util.BackgroundTask; -import org.moire.ultrasonic.util.CacheCleaner; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.LoadingTask; -import org.moire.ultrasonic.util.TabActivityBackgroundTask; -import org.moire.ultrasonic.util.Util; -import org.moire.ultrasonic.view.PlaylistAdapter; - -import java.util.List; - -public class SelectPlaylistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener -{ - - private SwipeRefreshLayout refreshPlaylistsListView; - private ListView playlistsListView; - private View emptyTextView; - private PlaylistAdapter playlistAdapter; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.select_playlist); - - refreshPlaylistsListView = findViewById(R.id.select_playlist_refresh); - playlistsListView = findViewById(R.id.select_playlist_list); - - refreshPlaylistsListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() - { - @Override - public void onRefresh() - { - new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - - emptyTextView = findViewById(R.id.select_playlist_empty); - playlistsListView.setOnItemClickListener(this); - registerForContextMenu(playlistsListView); - - View playlistsMenuItem = findViewById(R.id.menu_playlists); - menuDrawer.setActiveView(playlistsMenuItem); - - setActionBarTitle(R.string.common_appname); - setActionBarSubtitle(R.string.playlist_label); - - load(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - super.onCreateOptionsMenu(menu); - return true; - } - - private void refresh() - { - finish(); - Intent intent = new Intent(this, SelectPlaylistActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); - startActivityForResultWithoutTransition(this, intent); - } - - private void load() - { - BackgroundTask> task = new TabActivityBackgroundTask>(this, true) - { - @Override - protected List doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this); - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - List playlists = musicService.getPlaylists(refresh, SelectPlaylistActivity.this, this); - - if (!ActiveServerProvider.Companion.isOffline(SelectPlaylistActivity.this)) - new CacheCleaner(SelectPlaylistActivity.this).cleanPlaylists(playlists); - return playlists; - } - - @Override - protected void done(List result) - { - playlistsListView.setAdapter(playlistAdapter = new PlaylistAdapter(SelectPlaylistActivity.this, result)); - emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); - } - }; - task.execute(); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) - { - super.onCreateContextMenu(menu, view, menuInfo); - - MenuInflater inflater = getMenuInflater(); - if (ActiveServerProvider.Companion.isOffline(this)) inflater.inflate(R.menu.select_playlist_context_offline, menu); - else inflater.inflate(R.menu.select_playlist_context, menu); - - MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download); - - if (downloadMenuItem != null) - { - downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this)); - } - } - - @Override - public boolean onContextItemSelected(MenuItem menuItem) - { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - if (info == null) - { - return false; - } - - Playlist playlist = (Playlist) playlistsListView.getItemAtPosition(info.position); - if (playlist == null) - { - return false; - } - - Intent intent; - switch (menuItem.getItemId()) - { - case R.id.playlist_menu_pin: - downloadPlaylist(playlist.getId(), playlist.getName(), true, true, false, false, true, false, false); - break; - case R.id.playlist_menu_unpin: - downloadPlaylist(playlist.getId(), playlist.getName(), false, false, false, false, true, false, true); - break; - case R.id.playlist_menu_download: - downloadPlaylist(playlist.getId(), playlist.getName(), false, false, false, false, true, false, false); - break; - case R.id.playlist_menu_play_now: - intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true); - startActivityForResultWithoutTransition(SelectPlaylistActivity.this, intent); - break; - case R.id.playlist_menu_play_shuffled: - intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true); - intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true); - startActivityForResultWithoutTransition(SelectPlaylistActivity.this, intent); - break; - case R.id.playlist_menu_delete: - deletePlaylist(playlist); - break; - case R.id.playlist_info: - displayPlaylistInfo(playlist); - break; - case R.id.playlist_update_info: - updatePlaylistInfo(playlist); - break; - default: - return super.onContextItemSelected(menuItem); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - menuDrawer.toggleMenu(); - return true; - } - - return false; - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - Playlist playlist = (Playlist) parent.getItemAtPosition(position); - - if (playlist == null) - { - return; - } - - Intent intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, playlist.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); - startActivityForResultWithoutTransition(SelectPlaylistActivity.this, intent); - } - - private void deletePlaylist(final Playlist playlist) - { - new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, playlist.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - new LoadingTask(SelectPlaylistActivity.this, false) - { - @Override - protected Void doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this); - musicService.deletePlaylist(playlist.getId(), SelectPlaylistActivity.this, null); - return null; - } - - @Override - protected void done(Void result) - { - playlistAdapter.remove(playlist); - playlistAdapter.notifyDataSetChanged(); - Util.toast(SelectPlaylistActivity.this, getResources().getString(R.string.menu_deleted_playlist, playlist.getName())); - } - - @Override - protected void error(Throwable error) - { - String msg; - msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()), getErrorMessage(error)); - - Util.toast(SelectPlaylistActivity.this, msg, false); - } - }.execute(); - } - - }).setNegativeButton(R.string.common_cancel, null).show(); - } - - private void displayPlaylistInfo(final Playlist playlist) - { - final TextView textView = new TextView(this); - textView.setPadding(5, 5, 5, 5); - - final Spannable message = new SpannableString("Owner: " + playlist.getOwner() + "\nComments: " + - ((playlist.getComment() == null) ? "" : playlist.getComment()) + - "\nSong Count: " + playlist.getSongCount() + - ((playlist.getPublic() == null) ? "" : ("\nPublic: " + playlist.getPublic()) + ((playlist.getCreated() == null) ? "" : ("\nCreation Date: " + playlist.getCreated().replace('T', ' '))))); - - Linkify.addLinks(message, Linkify.WEB_URLS); - textView.setText(message); - textView.setMovementMethod(LinkMovementMethod.getInstance()); - - new AlertDialog.Builder(this).setTitle(playlist.getName()).setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show(); - } - - private void updatePlaylistInfo(final Playlist playlist) - { - View dialogView = getLayoutInflater().inflate(R.layout.update_playlist, null); - - if (dialogView == null) - { - return; - } - - final EditText nameBox = (EditText) dialogView.findViewById(R.id.get_playlist_name); - final EditText commentBox = (EditText) dialogView.findViewById(R.id.get_playlist_comment); - final CheckBox publicBox = (CheckBox) dialogView.findViewById(R.id.get_playlist_public); - - nameBox.setText(playlist.getName()); - commentBox.setText(playlist.getComment()); - Boolean pub = playlist.getPublic(); - - if (pub == null) - { - publicBox.setEnabled(false); - } - else - { - publicBox.setChecked(pub); - } - - AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); - - alertDialog.setIcon(android.R.drawable.ic_dialog_alert); - alertDialog.setTitle(R.string.playlist_update_info); - alertDialog.setView(dialogView); - alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - new LoadingTask(SelectPlaylistActivity.this, false) - { - @Override - protected Void doInBackground() throws Throwable - { - Editable nameBoxText = nameBox.getText(); - Editable commentBoxText = commentBox.getText(); - String name = nameBoxText != null ? nameBoxText.toString() : null; - String comment = commentBoxText != null ? commentBoxText.toString() : null; - - MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this); - musicService.updatePlaylist(playlist.getId(), name, comment, publicBox.isChecked(), SelectPlaylistActivity.this, null); - return null; - } - - @Override - protected void done(Void result) - { - refresh(); - Util.toast(SelectPlaylistActivity.this, getResources().getString(R.string.playlist_updated_info, playlist.getName())); - } - - @Override - protected void error(Throwable error) - { - String msg; - msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, playlist.getName()), getErrorMessage(error)); - - Util.toast(SelectPlaylistActivity.this, msg, false); - } - }.execute(); - } - - }); - alertDialog.setNegativeButton(R.string.common_cancel, null); - alertDialog.show(); - } - - private class GetDataTask extends AsyncTask - { - @Override - protected void onPostExecute(String[] result) - { - super.onPostExecute(result); - } - - @Override - protected String[] doInBackground(Void... params) - { - refresh(); - return null; - } - } -} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SettingsActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SettingsActivity.java deleted file mode 100644 index cb147e63..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SettingsActivity.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - 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 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.activity; - -import android.os.Bundle; -import androidx.appcompat.app.ActionBar; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.fragment.SettingsFragment; - -public class SettingsActivity extends SubsonicTabActivity { - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - getFragmentManager().beginTransaction() - .replace(android.R.id.content, new SettingsFragment()) - .commit(); - - - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) - { - actionBar.setSubtitle(R.string.menu_settings); - } - } -} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ShareActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ShareActivity.java deleted file mode 100644 index 9e012dd6..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ShareActivity.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - 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 2009 (C) Sindre Mehus - */ - -package org.moire.ultrasonic.activity; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Editable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.method.LinkMovementMethod; -import android.text.util.Linkify; -import android.view.ContextMenu; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException; -import org.moire.ultrasonic.domain.Share; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.service.OfflineException; -import org.moire.ultrasonic.util.BackgroundTask; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.LoadingTask; -import org.moire.ultrasonic.util.TabActivityBackgroundTask; -import org.moire.ultrasonic.util.TimeSpan; -import org.moire.ultrasonic.util.TimeSpanPicker; -import org.moire.ultrasonic.util.Util; -import org.moire.ultrasonic.view.ShareAdapter; - -import java.util.List; - -public class ShareActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener -{ - - private SwipeRefreshLayout refreshSharesListView; - private ListView sharesListView; - private View emptyTextView; - private ShareAdapter shareAdapter; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.select_share); - - refreshSharesListView = findViewById(R.id.select_share_refresh); - sharesListView = findViewById(R.id.select_share_list); - - refreshSharesListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() - { - @Override - public void onRefresh() - { - new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - - emptyTextView = findViewById(R.id.select_share_empty); - sharesListView.setOnItemClickListener(this); - registerForContextMenu(sharesListView); - - View sharesMenuItem = findViewById(R.id.menu_shares); - menuDrawer.setActiveView(sharesMenuItem); - - setActionBarTitle(R.string.common_appname); - setActionBarSubtitle(R.string.button_bar_shares); - - load(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - super.onCreateOptionsMenu(menu); - return true; - } - - private void refresh() - { - finish(); - Intent intent = new Intent(this, ShareActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); - startActivityForResultWithoutTransition(this, intent); - } - - private void load() - { - BackgroundTask> task = new TabActivityBackgroundTask>(this, true) - { - @Override - protected List doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(ShareActivity.this); - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - return musicService.getShares(refresh, ShareActivity.this, this); - } - - @Override - protected void done(List result) - { - sharesListView.setAdapter(shareAdapter = new ShareAdapter(ShareActivity.this, result)); - emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); - } - }; - task.execute(); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) - { - super.onCreateContextMenu(menu, view, menuInfo); - - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_share_context, menu); - } - - @Override - public boolean onContextItemSelected(MenuItem menuItem) - { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - if (info == null) - { - return false; - } - - Share share = (Share) sharesListView.getItemAtPosition(info.position); - if (share == null) - { - return false; - } - - switch (menuItem.getItemId()) - { - case R.id.share_menu_pin: - downloadShare(share.getId(), share.getName(), true, true, false, false, true, false, false); - break; - case R.id.share_menu_unpin: - downloadShare(share.getId(), share.getName(), false, false, false, false, true, false, true); - break; - case R.id.share_menu_download: - downloadShare(share.getId(), share.getName(), false, false, false, false, true, false, false); - break; - case R.id.share_menu_play_now: - downloadShare(share.getId(), share.getName(), false, false, true, false, false, false, false); - break; - case R.id.share_menu_play_shuffled: - downloadShare(share.getId(), share.getName(), false, false, true, true, false, false, false); - break; - case R.id.share_menu_delete: - deleteShare(share); - break; - case R.id.share_info: - displayShareInfo(share); - break; - case R.id.share_update_info: - updateShareInfo(share); - break; - default: - return super.onContextItemSelected(menuItem); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - menuDrawer.toggleMenu(); - return true; - } - - return false; - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - Share share = (Share) parent.getItemAtPosition(position); - - if (share == null) - { - return; - } - - Intent intent = new Intent(ShareActivity.this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_SHARE_ID, share.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_SHARE_NAME, share.getName()); - startActivityForResultWithoutTransition(ShareActivity.this, intent); - } - - private void deleteShare(final Share share) - { - new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, share.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - new LoadingTask(ShareActivity.this, false) - { - @Override - protected Void doInBackground() throws Throwable - { - MusicService musicService = MusicServiceFactory.getMusicService(ShareActivity.this); - musicService.deleteShare(share.getId(), ShareActivity.this, null); - return null; - } - - @Override - protected void done(Void result) - { - shareAdapter.remove(share); - shareAdapter.notifyDataSetChanged(); - Util.toast(ShareActivity.this, getResources().getString(R.string.menu_deleted_share, share.getName())); - } - - @Override - protected void error(Throwable error) - { - String msg; - msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_share_error, share.getName()), getErrorMessage(error)); - - Util.toast(ShareActivity.this, msg, false); - } - }.execute(); - } - - }).setNegativeButton(R.string.common_cancel, null).show(); - } - - private void displayShareInfo(final Share share) - { - final TextView textView = new TextView(this); - textView.setPadding(5, 5, 5, 5); - - final Spannable message = new SpannableString("Owner: " + share.getUsername() + - "\nComments: " + ((share.getDescription() == null) ? "" : share.getDescription()) + - "\nURL: " + share.getUrl() + - "\nEntry Count: " + share.getEntries().size() + - "\nVisit Count: " + share.getVisitCount() + - ((share.getCreated() == null) ? "" : ("\nCreation Date: " + share.getCreated().replace('T', ' '))) + - ((share.getLastVisited() == null) ? "" : ("\nLast Visited Date: " + share.getLastVisited().replace('T', ' '))) + - ((share.getExpires() == null) ? "" : ("\nExpiration Date: " + share.getExpires().replace('T', ' ')))); - - Linkify.addLinks(message, Linkify.WEB_URLS); - textView.setText(message); - textView.setMovementMethod(LinkMovementMethod.getInstance()); - - new AlertDialog.Builder(this).setTitle("Share Details").setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show(); - } - - private void updateShareInfo(final Share share) - { - View dialogView = getLayoutInflater().inflate(R.layout.share_details, null); - if (dialogView == null) - { - return; - } - - final EditText shareDescription = (EditText) dialogView.findViewById(R.id.share_description); - final TimeSpanPicker timeSpanPicker = (TimeSpanPicker) dialogView.findViewById(R.id.date_picker); - - shareDescription.setText(share.getDescription()); - - CheckBox hideDialogCheckBox = (CheckBox) dialogView.findViewById(R.id.hide_dialog); - CheckBox saveAsDefaultsCheckBox = (CheckBox) dialogView.findViewById(R.id.save_as_defaults); - CheckBox noExpirationCheckBox = (CheckBox) dialogView.findViewById(R.id.timeSpanDisableCheckBox); - - noExpirationCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() - { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) - { - timeSpanPicker.setEnabled(!b); - } - }); - - noExpirationCheckBox.setChecked(true); - - timeSpanPicker.setTimeSpanDisableText(getResources().getText(R.string.no_expiration)); - - hideDialogCheckBox.setVisibility(View.GONE); - saveAsDefaultsCheckBox.setVisibility(View.GONE); - - AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); - - alertDialog.setIcon(android.R.drawable.ic_dialog_alert); - alertDialog.setTitle(R.string.playlist_update_info); - alertDialog.setView(dialogView); - alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - new LoadingTask(ShareActivity.this, false) - { - @Override - protected Void doInBackground() throws Throwable - { - long millis = timeSpanPicker.getTimeSpan().getTotalMilliseconds(); - - if (millis > 0) - { - millis = TimeSpan.getCurrentTime().add(millis).getTotalMilliseconds(); - } - - Editable shareDescriptionText = shareDescription.getText(); - String description = shareDescriptionText != null ? shareDescriptionText.toString() : null; - - MusicService musicService = MusicServiceFactory.getMusicService(ShareActivity.this); - musicService.updateShare(share.getId(), description, millis, ShareActivity.this, null); - return null; - } - - @Override - protected void done(Void result) - { - refresh(); - Util.toast(ShareActivity.this, getResources().getString(R.string.playlist_updated_info, share.getName())); - } - - @Override - protected void error(Throwable error) - { - String msg; - msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, share.getName()), getErrorMessage(error)); - - Util.toast(ShareActivity.this, msg, false); - } - }.execute(); - } - }); - - alertDialog.setNegativeButton(R.string.common_cancel, null); - alertDialog.show(); - } - - private class GetDataTask extends AsyncTask - { - @Override - protected void onPostExecute(String[] result) - { - super.onPostExecute(result); - } - - @Override - protected String[] doInBackground(Void... params) - { - refresh(); - return null; - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java index e16c886e..d972d2a7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -49,6 +49,7 @@ import org.moire.ultrasonic.domain.Share; import org.moire.ultrasonic.featureflags.Feature; import org.moire.ultrasonic.featureflags.FeatureStorage; import org.moire.ultrasonic.service.*; +import org.moire.ultrasonic.subsonic.ImageLoaderProvider; import org.moire.ultrasonic.subsonic.SubsonicImageLoaderProxy; import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader; import org.moire.ultrasonic.util.*; @@ -63,10 +64,9 @@ import kotlin.Lazy; /** * @author Sindre Mehus */ -public class SubsonicTabActivity extends ResultActivity implements OnClickListener +public class SubsonicTabActivity extends ResultActivity { private static final Pattern COMPILE = Pattern.compile(":"); - protected static ImageLoader IMAGE_LOADER; protected static String theme; private static SubsonicTabActivity instance; @@ -79,6 +79,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); private Lazy lifecycleSupport = inject(MediaPlayerLifecycleSupport.class); + protected Lazy imageLoader = inject(ImageLoaderProvider.class); public MenuDrawer menuDrawer; private int activePosition = 1; @@ -118,18 +119,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen bookmarksMenuItem = findViewById(R.id.menu_bookmarks); sharesMenuItem = findViewById(R.id.menu_shares); - findViewById(R.id.menu_home).setOnClickListener(this); - findViewById(R.id.menu_browse).setOnClickListener(this); - findViewById(R.id.menu_search).setOnClickListener(this); - findViewById(R.id.menu_playlists).setOnClickListener(this); - findViewById(R.id.menu_podcasts).setOnClickListener(this); - sharesMenuItem.setOnClickListener(this); - chatMenuItem.setOnClickListener(this); - bookmarksMenuItem.setOnClickListener(this); - findViewById(R.id.menu_now_playing).setOnClickListener(this); - findViewById(R.id.menu_settings).setOnClickListener(this); - findViewById(R.id.menu_about).setOnClickListener(this); - findViewById(R.id.menu_exit).setOnClickListener(this); setActionBarDisplayHomeAsUp(true); TextView activeView = (TextView) findViewById(menuActiveViewId); @@ -203,7 +192,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen super.onDestroy(); destroyed = true; nowPlayingView = null; - clearImageLoader(); + imageLoader.getValue().clearImageLoader(); } @Override @@ -351,7 +340,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen @Override public void run() { - getImageLoader().loadImage(nowPlayingAlbumArtImage, song, false, Util.getNotificationImageSize(context), false, true); + imageLoader.getValue().getImageLoader().loadImage(nowPlayingAlbumArtImage, song, false, Util.getNotificationImageSize(context), false, true); } }); @@ -771,40 +760,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen } } - public synchronized void clearImageLoader() { - if (IMAGE_LOADER != null && - IMAGE_LOADER.isRunning()) { - IMAGE_LOADER.clear(); - } - - IMAGE_LOADER = null; - } - - public synchronized ImageLoader getImageLoader() { - if (IMAGE_LOADER == null || - !IMAGE_LOADER.isRunning()) { - LegacyImageLoader legacyImageLoader = new LegacyImageLoader( - this, - Util.getImageLoaderConcurrency(this) - ); - - boolean isNewImageLoaderEnabled = KoinJavaComponent.get(FeatureStorage.class) - .isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER); - if (isNewImageLoaderEnabled) { - IMAGE_LOADER = new SubsonicImageLoaderProxy( - legacyImageLoader, - KoinJavaComponent.get(SubsonicImageLoader.class) - ); - } else { - IMAGE_LOADER = legacyImageLoader; - } - - IMAGE_LOADER.startImageLoader(); - } - - return IMAGE_LOADER; - } - void download(final boolean append, final boolean save, final boolean autoPlay, final boolean playNext, final boolean shuffle, final List songs) { if (getMediaPlayerController() == null) @@ -1024,26 +979,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen task.execute(); } - protected void playVideo(Entry entry) - { - if (!Util.isNetworkConnected(this)) - { - Util.toast(this, R.string.select_album_no_network); - return; - } - - VideoPlayerType player = Util.getVideoPlayerType(this); - - try - { - player.playVideo(this, entry); - } - catch (Exception e) - { - Util.toast(this, e.getMessage(), false); - } - } - protected void checkLicenseAndTrialPeriod(Runnable onValid) { if (licenseValid) @@ -1247,72 +1182,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen } } - @Override - public void onClick(View v) - { - menuActiveViewId = v.getId(); - menuDrawer.setActiveView(v); - - Intent intent; - - switch (menuActiveViewId) - { - case R.id.menu_home: - intent = new Intent(SubsonicTabActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); - break; - case R.id.menu_browse: - intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); - break; - case R.id.menu_search: - intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); - startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); - break; - case R.id.menu_playlists: - intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); - break; - case R.id.menu_podcasts: - intent = new Intent(SubsonicTabActivity.this, PodcastsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); - break; - case R.id.menu_shares: - intent = new Intent(SubsonicTabActivity.this, ShareActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); - break; - case R.id.menu_chat: - startActivityForResultWithoutTransition(SubsonicTabActivity.this, ChatActivity.class); - break; - case R.id.menu_bookmarks: - startActivityForResultWithoutTransition(this, BookmarkActivity.class); - break; - case R.id.menu_now_playing: - startActivityForResultWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); - break; - case R.id.menu_settings: - startActivityForResultWithoutTransition(SubsonicTabActivity.this, SettingsActivity.class); - break; - case R.id.menu_about: - startActivityForResultWithoutTransition(SubsonicTabActivity.this, HelpActivity.class); - break; - case R.id.menu_exit: - intent = new Intent(SubsonicTabActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); - startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); - break; - } - - menuDrawer.closeMenu(true); - } - @Override protected void onRestoreInstanceState(Bundle inState) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java new file mode 100644 index 00000000..3c21969b --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java @@ -0,0 +1,147 @@ +package org.moire.ultrasonic.fragment; + +import android.graphics.Bitmap; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Button; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.util.Util; + +import timber.log.Timber; + +public class AboutFragment extends Fragment { + + private WebView webView; + private ImageView backButton; + private ImageView forwardButton; + private SwipeRefreshLayout swipeRefresh; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.help, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + + swipeRefresh = view.findViewById(R.id.help_refresh); + swipeRefresh.setEnabled(false); + + webView = view.findViewById(R.id.help_contents); + webView.getSettings().setJavaScriptEnabled(true); + webView.setWebViewClient(new HelpClient()); + + if (savedInstanceState != null) + { + webView.restoreState(savedInstanceState); + } + else + { + webView.loadUrl(getResources().getString(R.string.help_url)); + } + + backButton = view.findViewById(R.id.help_back); + backButton.setOnClickListener(new Button.OnClickListener() + { + @Override + public void onClick(View view) + { + webView.goBack(); + } + }); + + ImageView stopButton = view.findViewById(R.id.help_stop); + stopButton.setOnClickListener(new Button.OnClickListener() + { + @Override + public void onClick(View view) + { + webView.stopLoading(); + swipeRefresh.setRefreshing(false); + } + }); + + forwardButton = view.findViewById(R.id.help_forward); + forwardButton.setOnClickListener(new Button.OnClickListener() + { + @Override + public void onClick(View view) + { + webView.goForward(); + } + }); + + // TODO: Nicer Back key handling? + webView.setFocusableInTouchMode(true); + webView.requestFocus(); + webView.setOnKeyListener( new View.OnKeyListener() + { + @Override + public boolean onKey( View v, int keyCode, KeyEvent event ) + { + if (keyCode == KeyEvent.KEYCODE_BACK) + { + if (webView.canGoBack()) + { + webView.goBack(); + return true; + } + } + return false; + } + } ); + } + + @Override + public void onSaveInstanceState(Bundle state) + { + webView.saveState(state); + super.onSaveInstanceState(state); + } + + private final class HelpClient extends WebViewClient + { + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + swipeRefresh.setRefreshing(true); + super.onPageStarted(view, url, favicon); + } + + @Override + public void onPageFinished(WebView view, String url) + { + swipeRefresh.setRefreshing(false); + String versionName = Util.getVersionName(getContext()); + String title = String.format("%s (%s)", view.getTitle(), versionName); + + FragmentTitle.Companion.setTitle(AboutFragment.this, title); + + backButton.setEnabled(view.canGoBack()); + forwardButton.setEnabled(view.canGoForward()); + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) + { + Util.toast(getContext(), description); + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/BookmarksFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/BookmarksFragment.java new file mode 100644 index 00000000..fd505cbb --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/BookmarksFragment.java @@ -0,0 +1,475 @@ +package org.moire.ultrasonic.fragment; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.data.ActiveServerProvider; +import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.service.DownloadFile; +import org.moire.ultrasonic.service.MediaPlayerController; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.subsonic.ImageLoaderProvider; +import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker; +import org.moire.ultrasonic.subsonic.VideoPlayer; +import org.moire.ultrasonic.util.CancellationToken; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.Pair; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.EntryAdapter; + +import java.util.ArrayList; +import java.util.List; + +import kotlin.Lazy; +import timber.log.Timber; + +import static org.koin.java.KoinJavaComponent.inject; + +public class BookmarksFragment extends Fragment { + + private SwipeRefreshLayout refreshAlbumListView; + private ListView albumListView; + private View albumButtons; + private View emptyView; + private ImageView playNowButton; + private ImageView pinButton; + private ImageView unpinButton; + private ImageView downloadButton; + private ImageView deleteButton; + + private final Lazy videoPlayer = inject(VideoPlayer.class); + private final Lazy mediaPlayerController = inject(MediaPlayerController.class); + private final Lazy imageLoader = inject(ImageLoaderProvider.class); + private final Lazy networkAndStorageChecker = inject(NetworkAndStorageChecker.class); + private CancellationToken cancellationToken; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.select_album, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + cancellationToken = new CancellationToken(); + albumButtons = view.findViewById(R.id.menu_album); + + refreshAlbumListView = view.findViewById(R.id.select_album_entries_refresh); + albumListView = view.findViewById(R.id.select_album_entries_list); + + refreshAlbumListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() + { + @Override + public void onRefresh() + { + new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + albumListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + + albumListView.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if (position >= 0) + { + MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position); + + if (entry != null) + { + if (entry.isVideo()) + { + videoPlayer.getValue().playVideo(entry); + } + else + { + enableButtons(); + } + } + } + } + }); + + ImageView selectButton = view.findViewById(R.id.select_album_select); + playNowButton = view.findViewById(R.id.select_album_play_now); + ImageView playNextButton = view.findViewById(R.id.select_album_play_next); + ImageView playLastButton = view.findViewById(R.id.select_album_play_last); + pinButton = view.findViewById(R.id.select_album_pin); + unpinButton = view.findViewById(R.id.select_album_unpin); + downloadButton = view.findViewById(R.id.select_album_download); + deleteButton = view.findViewById(R.id.select_album_delete); + ImageView oreButton = view.findViewById(R.id.select_album_more); + emptyView = view.findViewById(R.id.select_album_empty); + + selectButton.setVisibility(View.GONE); + playNextButton.setVisibility(View.GONE); + playLastButton.setVisibility(View.GONE); + oreButton.setVisibility(View.GONE); + + playNowButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + playNow(getSelectedSongs(albumListView)); + } + }); + + selectButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + selectAllOrNone(); + } + }); + pinButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + downloadBackground(true); + selectAll(false, false); + } + }); + unpinButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + unpin(); + selectAll(false, false); + } + }); + downloadButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + downloadBackground(false); + selectAll(false, false); + } + }); + deleteButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + delete(); + selectAll(false, false); + } + }); + + registerForContextMenu(albumListView); + FragmentTitle.Companion.setTitle(this, R.string.button_bar_bookmarks); + + enableButtons(); + + getBookmarks(); + } + + @Override + public void onDestroyView() { + cancellationToken.cancel(); + super.onDestroyView(); + } + + private void getBookmarks() + { + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return Util.getSongsFromBookmarks(service.getBookmarks(getContext(), this)); + } + }.execute(); + } + + private void playNow(List songs) + { + if (!getSelectedSongs(albumListView).isEmpty()) + { + int position = songs.get(0).getBookmarkPosition(); + mediaPlayerController.getValue().restore(songs, 0, position, true, true); + selectAll(false, false); + } + } + + private static List getSelectedSongs(ListView albumListView) + { + List songs = new ArrayList(10); + + if (albumListView != null) + { + int count = albumListView.getCount(); + for (int i = 0; i < count; i++) + { + if (albumListView.isItemChecked(i)) + { + songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(i)); + } + } + } + + return songs; + } + + private void refresh() + { + // TODO: create better restart + getView().post(new Runnable() { + public void run() { + Timber.d("Refresh called..."); + if (getArguments() == null) { + Bundle bundle = new Bundle(); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + setArguments(bundle); + } else { + getArguments().putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + } + onViewCreated(getView(), null); + } + }); + +/* finish(); + Intent intent = getIntent(); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + startActivityForResultWithoutTransition(this, intent); + + */ + } + + private void selectAllOrNone() + { + boolean someUnselected = false; + int count = albumListView.getCount(); + + for (int i = 0; i < count; i++) + { + if (!albumListView.isItemChecked(i) && albumListView.getItemAtPosition(i) instanceof MusicDirectory.Entry) + { + someUnselected = true; + break; + } + } + + selectAll(someUnselected, true); + } + + private void selectAll(boolean selected, boolean toast) + { + int count = albumListView.getCount(); + int selectedCount = 0; + + for (int i = 0; i < count; i++) + { + MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i); + if (entry != null && !entry.isDirectory() && !entry.isVideo()) + { + albumListView.setItemChecked(i, selected); + selectedCount++; + } + } + + // Display toast: N tracks selected / N tracks unselected + if (toast) + { + int toastResId = selected ? R.string.select_album_n_selected : R.string.select_album_n_unselected; + Util.toast(getContext(), getString(toastResId, selectedCount)); + } + + enableButtons(); + } + + private void enableButtons() + { + List selection = getSelectedSongs(albumListView); + boolean enabled = !selection.isEmpty(); + boolean unpinEnabled = false; + boolean deleteEnabled = false; + + int pinnedCount = 0; + + for (MusicDirectory.Entry song : selection) + { + DownloadFile downloadFile = mediaPlayerController.getValue().getDownloadFileForSong(song); + if (downloadFile.isWorkDone()) + { + deleteEnabled = true; + } + + if (downloadFile.isSaved()) + { + pinnedCount++; + unpinEnabled = true; + } + } + + playNowButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE); + pinButton.setVisibility((enabled && !ActiveServerProvider.Companion.isOffline(getContext()) && selection.size() > pinnedCount) ? View.VISIBLE : View.GONE); + unpinButton.setVisibility(enabled && unpinEnabled ? View.VISIBLE : View.GONE); + downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(getContext()) ? View.VISIBLE : View.GONE); + deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE); + } + + private void downloadBackground(final boolean save) + { + List songs = getSelectedSongs(albumListView); + + if (songs.isEmpty()) + { + selectAll(true, false); + songs = getSelectedSongs(albumListView); + } + + downloadBackground(save, songs); + } + + private void downloadBackground(final boolean save, final List songs) + { + Runnable onValid = new Runnable() + { + @Override + public void run() + { + networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable(); + mediaPlayerController.getValue().downloadBackground(songs, save); + + if (save) + { + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size())); + } + else + { + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size())); + } + } + }; + + onValid.run(); + } + + private void delete() + { + List songs = getSelectedSongs(albumListView); + + if (songs.isEmpty()) + { + selectAll(true, false); + songs = getSelectedSongs(albumListView); + } + + mediaPlayerController.getValue().delete(songs); + } + + private void unpin() + { + List songs = getSelectedSongs(albumListView); + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); + mediaPlayerController.getValue().unpin(songs); + } + + private abstract class LoadTask extends TabActivityBackgroundTask> + { + public LoadTask() + { + super(BookmarksFragment.this.getActivity(), true, refreshAlbumListView, cancellationToken); + } + + protected abstract MusicDirectory load(MusicService service) throws Exception; + + @Override + protected Pair doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + MusicDirectory dir = load(musicService); + boolean valid = musicService.isLicenseValid(getContext(), this); + return new Pair<>(dir, valid); + } + + @Override + protected void done(Pair result) + { + MusicDirectory musicDirectory = result.getFirst(); + List entries = musicDirectory.getChildren(); + + int songCount = 0; + for (MusicDirectory.Entry entry : entries) + { + if (!entry.isDirectory()) + { + songCount++; + } + } + + final int listSize = getArguments() == null? 0 : getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + + if (songCount > 0) + { + pinButton.setVisibility(View.VISIBLE); + unpinButton.setVisibility(View.VISIBLE); + downloadButton.setVisibility(View.VISIBLE); + deleteButton.setVisibility(View.VISIBLE); + playNowButton.setVisibility(View.VISIBLE); + } + else + { + pinButton.setVisibility(View.GONE); + unpinButton.setVisibility(View.GONE); + downloadButton.setVisibility(View.GONE); + deleteButton.setVisibility(View.GONE); + playNowButton.setVisibility(View.GONE); + + if (listSize == 0 || result.getFirst().getChildren().size() < listSize) + { + albumButtons.setVisibility(View.GONE); + } + } + + enableButtons(); + + emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE); + + albumListView.setAdapter(new EntryAdapter(getContext(), imageLoader.getValue().getImageLoader(), entries, true)); + } + } + + private class GetDataTask extends AsyncTask + { + @Override + protected void onPostExecute(String[] result) + { + super.onPostExecute(result); + } + + @Override + protected String[] doInBackground(Void... params) + { + refresh(); + return null; + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ChatFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ChatFragment.java new file mode 100644 index 00000000..6fe91062 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ChatFragment.java @@ -0,0 +1,317 @@ +package org.moire.ultrasonic.fragment; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +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.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.data.ActiveServerProvider; +import org.moire.ultrasonic.domain.ChatMessage; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.util.BackgroundTask; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.ChatAdapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import kotlin.Lazy; + +import static org.koin.java.KoinJavaComponent.inject; + +public class ChatFragment extends Fragment { + + private ListView chatListView; + private EditText messageEditText; + private ImageButton sendButton; + private Timer timer; + private volatile static Long lastChatMessageTime = (long) 0; + private static final ArrayList messageList = new ArrayList(); + + private final Lazy activeServerProvider = inject(ActiveServerProvider.class); + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.chat, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + messageEditText = view.findViewById(R.id.chat_edittext); + sendButton = view.findViewById(R.id.chat_send); + + sendButton.setOnClickListener(new View.OnClickListener() + { + + @Override + public void onClick(View view) + { + sendMessage(); + } + }); + + chatListView = view.findViewById(R.id.chat_entries_list); + chatListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); + chatListView.setStackFromBottom(true); + + String serverName = activeServerProvider.getValue().getActiveServer().getName(); + String userName = activeServerProvider.getValue().getActiveServer().getUserName(); + String title = String.format("%s [%s@%s]", getResources().getString(R.string.button_bar_chat), userName, serverName); + + FragmentTitle.Companion.setTitle(this, title); + setHasOptionsMenu(true); + + messageEditText.setImeActionLabel("Send", KeyEvent.KEYCODE_ENTER); + + messageEditText.addTextChangedListener(new TextWatcher() + { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) + { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) + { + } + + @Override + public void afterTextChanged(Editable editable) + { + sendButton.setEnabled(!Util.isNullOrWhiteSpace(editable.toString())); + } + }); + + messageEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() + { + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) + { + if (actionId == EditorInfo.IME_ACTION_DONE || (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN)) + { + sendMessage(); + return true; + } + + return false; + } + }); + + load(); + timerMethod(); + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + inflater.inflate(R.menu.chat, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + /* + * Listen for option item selections so that we receive a notification + * when the user requests a refresh by selecting the refresh action bar item. + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + // Check if user triggered a refresh: + case R.id.menu_refresh: + // Start the refresh background task. + new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return true; + } + // User didn't trigger a refresh, let the superclass handle this action + return super.onOptionsItemSelected(item); + } + + @Override + public void onResume() + { + super.onResume(); + + if (!messageList.isEmpty()) + { + ListAdapter chatAdapter = new ChatAdapter(getContext(), messageList); + chatListView.setAdapter(chatAdapter); + } + + if (timer == null) + { + timerMethod(); + } + } + + @Override + public void onPause() + { + super.onPause(); + + if (timer != null) + { + timer.cancel(); + timer = null; + } + } + + private void timerMethod() + { + int refreshInterval = Util.getChatRefreshInterval(getContext()); + + if (refreshInterval > 0) + { + timer = new Timer(); + + timer.schedule(new TimerTask() + { + @Override + public void run() + { + getActivity().runOnUiThread(new Runnable() + { + @Override + public void run() + { + load(); + } + }); + } + }, refreshInterval, refreshInterval); + } + } + + private void sendMessage() + { + if (messageEditText != null) + { + final String message; + Editable text = messageEditText.getText(); + + if (text == null) + { + return; + } + + message = text.toString(); + + if (!Util.isNullOrWhiteSpace(message)) + { + messageEditText.setText(""); + + BackgroundTask task = new TabActivityBackgroundTask(getActivity(), false) + { + @Override + protected Void doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + musicService.addChatMessage(message, getContext(), this); + return null; + } + + @Override + protected void done(Void result) + { + load(); + } + }; + + task.execute(); + } + } + } + + private synchronized void load() + { + BackgroundTask> task = new TabActivityBackgroundTask>(getActivity(), false) + { + @Override + protected List doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + return musicService.getChatMessages(lastChatMessageTime, getContext(), this); + } + + @Override + protected void done(List result) + { + if (result != null && !result.isEmpty()) + { + // Reset lastChatMessageTime if we have a newer message + for (ChatMessage message : result) + { + if (message.getTime() > lastChatMessageTime) + { + lastChatMessageTime = message.getTime(); + } + } + + // Reverse results to show them on the bottom + Collections.reverse(result); + messageList.addAll(result); + + ListAdapter chatAdapter = new ChatAdapter(getContext(), messageList); + chatListView.setAdapter(chatAdapter); + } + } + + @Override + protected void error(Throwable error) { + // Stop the timer in case of an error, otherwise it may repeat the error message forever + if (timer != null) + { + timer.cancel(); + timer = null; + } + super.error(error); + } + }; + + task.execute(); + } + + private class GetDataTask extends AsyncTask + { + @Override + protected void onPostExecute(String[] result) + { + load(); + super.onPostExecute(result); + } + + @Override + protected String[] doInBackground(Void... params) + { + return null; + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/MainFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/MainFragment.java new file mode 100644 index 00000000..008d5284 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/MainFragment.java @@ -0,0 +1,278 @@ +package org.moire.ultrasonic.fragment; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.activity.ServerSelectorActivity; +import org.moire.ultrasonic.data.ActiveServerProvider; +import org.moire.ultrasonic.data.ServerSetting; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.MergeAdapter; +import org.moire.ultrasonic.util.Util; + +import java.util.Collections; + +import kotlin.Lazy; + +import static java.util.Arrays.asList; +import static org.koin.java.KoinJavaComponent.inject; + +public class MainFragment extends Fragment { + + private static boolean shouldUseId3; + private static String lastActiveServerProperties; + + private ListView list; + + private final Lazy activeServerProvider = inject(ActiveServerProvider.class); + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.main, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + + list = view.findViewById(R.id.main_list); + setupMenuList(list); + + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onResume() { + super.onResume(); + boolean shouldRestart = false; + + boolean id3 = Util.getShouldUseId3Tags(MainFragment.this.getContext()); + String currentActiveServerProperties = getActiveServerProperties(); + + if (id3 != shouldUseId3) + { + shouldUseId3 = id3; + shouldRestart = true; + } + + if (!currentActiveServerProperties.equals(lastActiveServerProperties)) + { + lastActiveServerProperties = currentActiveServerProperties; + shouldRestart = true; + } + + if (shouldRestart) { + setupMenuList(list); + } + } + + private void setupMenuList(ListView list) + { + final View buttons = getLayoutInflater().inflate(R.layout.main_buttons, null); + final View serverButton = buttons.findViewById(R.id.main_select_server); + final TextView serverTextView = serverButton.findViewById(R.id.main_select_server_2); + + lastActiveServerProperties = getActiveServerProperties(); + String name = activeServerProvider.getValue().getActiveServer().getName(); + + serverTextView.setText(name); + + final View musicTitle = buttons.findViewById(R.id.main_music); + final View artistsButton = buttons.findViewById(R.id.main_artists_button); + final View albumsButton = buttons.findViewById(R.id.main_albums_button); + final View genresButton = buttons.findViewById(R.id.main_genres_button); + final View videosTitle = buttons.findViewById(R.id.main_videos_title); + final View songsTitle = buttons.findViewById(R.id.main_songs); + final View randomSongsButton = buttons.findViewById(R.id.main_songs_button); + final View songsStarredButton = buttons.findViewById(R.id.main_songs_starred); + final View albumsTitle = buttons.findViewById(R.id.main_albums); + final View albumsNewestButton = buttons.findViewById(R.id.main_albums_newest); + final View albumsRandomButton = buttons.findViewById(R.id.main_albums_random); + final View albumsHighestButton = buttons.findViewById(R.id.main_albums_highest); + final View albumsStarredButton = buttons.findViewById(R.id.main_albums_starred); + final View albumsRecentButton = buttons.findViewById(R.id.main_albums_recent); + final View albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent); + final View albumsAlphaByNameButton = buttons.findViewById(R.id.main_albums_alphaByName); + final View albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist); + final View videosButton = buttons.findViewById(R.id.main_videos); + + final MergeAdapter adapter = new MergeAdapter(); + adapter.addViews(Collections.singletonList(serverButton), true); + + if (!ActiveServerProvider.Companion.isOffline(this.getContext())) + { + adapter.addView(musicTitle, false); + adapter.addViews(asList(artistsButton, albumsButton, genresButton), true); + adapter.addView(songsTitle, false); + adapter.addViews(asList(randomSongsButton, songsStarredButton), true); + adapter.addView(albumsTitle, false); + + if (Util.getShouldUseId3Tags(MainFragment.this.getContext())) + { + shouldUseId3 = true; + adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true); + } + else + { + shouldUseId3 = false; + adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsHighestButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true); + } + + adapter.addView(videosTitle, false); + adapter.addViews(Collections.singletonList(videosButton), true); + } + + list.setAdapter(adapter); + list.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if (view == serverButton) + { + showServers(); + } + else if (view == albumsNewestButton) + { + showAlbumList("newest", R.string.main_albums_newest); + } + else if (view == albumsRandomButton) + { + showAlbumList("random", R.string.main_albums_random); + } + else if (view == albumsHighestButton) + { + showAlbumList("highest", R.string.main_albums_highest); + } + else if (view == albumsRecentButton) + { + showAlbumList("recent", R.string.main_albums_recent); + } + else if (view == albumsFrequentButton) + { + showAlbumList("frequent", R.string.main_albums_frequent); + } + else if (view == albumsStarredButton) + { + showAlbumList(Constants.STARRED, R.string.main_albums_starred); + } + else if (view == albumsAlphaByNameButton) + { + showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName); + } + else if (view == albumsAlphaByArtistButton) + { + showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist); + } + else if (view == songsStarredButton) + { + showStarredSongs(); + } + else if (view == artistsButton) + { + showArtists(); + } + else if (view == albumsButton) + { + showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title); + } + else if (view == randomSongsButton) + { + showRandomSongs(); + } + else if (view == genresButton) + { + showGenres(); + } + else if (view == videosButton) + { + showVideos(); + } + } + }); + } + + private String getActiveServerProperties() + { + ServerSetting currentSetting = activeServerProvider.getValue().getActiveServer(); + return String.format("%s;%s;%s;%s;%s;%s", currentSetting.getUrl(), currentSetting.getUserName(), + currentSetting.getPassword(), currentSetting.getAllowSelfSignedCertificate(), + currentSetting.getLdapSupport(), currentSetting.getMinimumApiVersion()); + } + + private void showAlbumList(final String type, final int title) + { + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxAlbums(getContext())); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); + Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle); + } + + private void showStarredSongs() + { + Bundle bundle = new Bundle(); + bundle.putInt(Constants.INTENT_EXTRA_NAME_STARRED, 1); + Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle); + } + + private void showRandomSongs() + { + Bundle bundle = new Bundle(); + bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(getContext())); + Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle); + } + + private void showArtists() + { + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, getContext().getResources().getString(R.string.main_artists_title)); + Navigation.findNavController(getView()).navigate(R.id.selectArtistFragment, bundle); + } + + private void showGenres() + { + Navigation.findNavController(getView()).navigate(R.id.mainToSelectGenre); + } + + private void showVideos() + { + Bundle bundle = new Bundle(); + bundle.putInt(Constants.INTENT_EXTRA_NAME_VIDEOS, 1); + Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle); + } + + private void showServers() + { + final Intent intent = new Intent(getContext(), ServerSelectorActivity.class); + startActivityForResult(intent, 0); + } + + public void startActivityForResultWithoutTransition(Activity currentActivity, Intent intent) + { + startActivityForResult(intent, 0); + Util.disablePendingTransition(currentActivity); + } +} + diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlaylistsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlaylistsFragment.java new file mode 100644 index 00000000..d71abcf8 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlaylistsFragment.java @@ -0,0 +1,393 @@ +package org.moire.ultrasonic.fragment; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.jetbrains.annotations.NotNull; +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException; +import org.moire.ultrasonic.data.ActiveServerProvider; +import org.moire.ultrasonic.domain.Playlist; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.service.OfflineException; +import org.moire.ultrasonic.subsonic.DownloadHandler; +import org.moire.ultrasonic.util.BackgroundTask; +import org.moire.ultrasonic.util.CacheCleaner; +import org.moire.ultrasonic.util.CancellationToken; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.LoadingTask; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.PlaylistAdapter; + +import java.util.List; + +import kotlin.Lazy; +import timber.log.Timber; + +import static org.koin.java.KoinJavaComponent.inject; + +public class PlaylistsFragment extends Fragment { + + private SwipeRefreshLayout refreshPlaylistsListView; + private ListView playlistsListView; + private View emptyTextView; + private PlaylistAdapter playlistAdapter; + + private final Lazy downloadHandler = inject(DownloadHandler.class); + private CancellationToken cancellationToken; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.select_playlist, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + cancellationToken = new CancellationToken(); + + refreshPlaylistsListView = view.findViewById(R.id.select_playlist_refresh); + playlistsListView = view.findViewById(R.id.select_playlist_list); + + refreshPlaylistsListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() + { + @Override + public void onRefresh() + { + new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + emptyTextView = view.findViewById(R.id.select_playlist_empty); + playlistsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Playlist playlist = (Playlist) parent.getItemAtPosition(position); + + if (playlist == null) + { + return; + } + + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_ID, playlist.getId()); + bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); + bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + } + }); + registerForContextMenu(playlistsListView); + + FragmentTitle.Companion.setTitle(this, R.string.playlist_label); + + load(); + } + + @Override + public void onDestroyView() { + cancellationToken.cancel(); + super.onDestroyView(); + } + + private void refresh() + { + // TODO: create better restart + getView().post(new Runnable() { + public void run() { + Timber.d("Refresh called..."); + if (getArguments() == null) { + Bundle bundle = new Bundle(); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + setArguments(bundle); + } else { + getArguments().putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + } + onViewCreated(getView(), null); + } + }); + +/* finish(); + Intent intent = new Intent(this, SelectPlaylistActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + startActivityForResultWithoutTransition(this, intent); + */ + } + + private void load() + { + BackgroundTask> task = new TabActivityBackgroundTask>(getActivity(), true, refreshPlaylistsListView, cancellationToken) + { + @Override + protected List doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + boolean refresh = getArguments() != null && getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + List playlists = musicService.getPlaylists(refresh, getContext(), this); + + if (!ActiveServerProvider.Companion.isOffline(getContext())) + new CacheCleaner(getContext()).cleanPlaylists(playlists); + return playlists; + } + + @Override + protected void done(List result) + { + playlistsListView.setAdapter(playlistAdapter = new PlaylistAdapter(getContext(), result)); + emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); + } + }; + task.execute(); + } + + @Override + public void onCreateContextMenu(@NotNull ContextMenu menu, @NotNull View view, ContextMenu.ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, view, menuInfo); + + MenuInflater inflater = getActivity().getMenuInflater(); + if (ActiveServerProvider.Companion.isOffline(getContext())) inflater.inflate(R.menu.select_playlist_context_offline, menu); + else inflater.inflate(R.menu.select_playlist_context, menu); + + MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download); + + if (downloadMenuItem != null) + { + downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(getContext())); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) + { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + if (info == null) + { + return false; + } + + Playlist playlist = (Playlist) playlistsListView.getItemAtPosition(info.position); + if (playlist == null) + { + return false; + } + + Bundle bundle; + switch (menuItem.getItemId()) + { + case R.id.playlist_menu_pin: + downloadHandler.getValue().downloadPlaylist(this, playlist.getId(), playlist.getName(), true, true, false, false, true, false, false); + break; + case R.id.playlist_menu_unpin: + downloadHandler.getValue().downloadPlaylist(this, playlist.getId(), playlist.getName(), false, false, false, false, true, false, true); + break; + case R.id.playlist_menu_download: + downloadHandler.getValue().downloadPlaylist(this, playlist.getId(), playlist.getName(), false, false, false, false, true, false, false); + break; + case R.id.playlist_menu_play_now: + bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); + bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + break; + case R.id.playlist_menu_play_shuffled: + bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); + bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, true); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + break; + case R.id.playlist_menu_delete: + deletePlaylist(playlist); + break; + case R.id.playlist_info: + displayPlaylistInfo(playlist); + break; + case R.id.playlist_update_info: + updatePlaylistInfo(playlist); + break; + default: + return super.onContextItemSelected(menuItem); + } + return true; + } + + private void deletePlaylist(final Playlist playlist) + { + new AlertDialog.Builder(getContext()).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, playlist.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + new LoadingTask(getActivity(), false, refreshPlaylistsListView) + { + @Override + protected Void doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + musicService.deletePlaylist(playlist.getId(), getContext(), null); + return null; + } + + @Override + protected void done(Void result) + { + playlistAdapter.remove(playlist); + playlistAdapter.notifyDataSetChanged(); + Util.toast(getContext(), getResources().getString(R.string.menu_deleted_playlist, playlist.getName())); + } + + @Override + protected void error(Throwable error) + { + String msg; + msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()), getErrorMessage(error)); + + Util.toast(getContext(), msg, false); + } + }.execute(); + } + + }).setNegativeButton(R.string.common_cancel, null).show(); + } + + private void displayPlaylistInfo(final Playlist playlist) + { + final TextView textView = new TextView(getContext()); + textView.setPadding(5, 5, 5, 5); + + final Spannable message = new SpannableString("Owner: " + playlist.getOwner() + "\nComments: " + + ((playlist.getComment() == null) ? "" : playlist.getComment()) + + "\nSong Count: " + playlist.getSongCount() + + ((playlist.getPublic() == null) ? "" : ("\nPublic: " + playlist.getPublic()) + ((playlist.getCreated() == null) ? "" : ("\nCreation Date: " + playlist.getCreated().replace('T', ' '))))); + + Linkify.addLinks(message, Linkify.WEB_URLS); + textView.setText(message); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + + new AlertDialog.Builder(getContext()).setTitle(playlist.getName()).setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show(); + } + + private void updatePlaylistInfo(final Playlist playlist) + { + View dialogView = getLayoutInflater().inflate(R.layout.update_playlist, null); + + if (dialogView == null) + { + return; + } + + final EditText nameBox = dialogView.findViewById(R.id.get_playlist_name); + final EditText commentBox = dialogView.findViewById(R.id.get_playlist_comment); + final CheckBox publicBox = dialogView.findViewById(R.id.get_playlist_public); + + nameBox.setText(playlist.getName()); + commentBox.setText(playlist.getComment()); + Boolean pub = playlist.getPublic(); + + if (pub == null) + { + publicBox.setEnabled(false); + } + else + { + publicBox.setChecked(pub); + } + + AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext()); + + alertDialog.setIcon(android.R.drawable.ic_dialog_alert); + alertDialog.setTitle(R.string.playlist_update_info); + alertDialog.setView(dialogView); + alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + new LoadingTask(getActivity(), false, refreshPlaylistsListView) + { + @Override + protected Void doInBackground() throws Throwable + { + Editable nameBoxText = nameBox.getText(); + Editable commentBoxText = commentBox.getText(); + String name = nameBoxText != null ? nameBoxText.toString() : null; + String comment = commentBoxText != null ? commentBoxText.toString() : null; + + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + musicService.updatePlaylist(playlist.getId(), name, comment, publicBox.isChecked(), getContext(), null); + return null; + } + + @Override + protected void done(Void result) + { + refresh(); + Util.toast(getContext(), getResources().getString(R.string.playlist_updated_info, playlist.getName())); + } + + @Override + protected void error(Throwable error) + { + String msg; + msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, playlist.getName()), getErrorMessage(error)); + + Util.toast(getContext(), msg, false); + } + }.execute(); + } + + }); + alertDialog.setNegativeButton(R.string.common_cancel, null); + alertDialog.show(); + } + + private class GetDataTask extends AsyncTask + { + @Override + protected void onPostExecute(String[] result) + { + super.onPostExecute(result); + } + + @Override + protected String[] doInBackground(Void... params) + { + refresh(); + return null; + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PodcastFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PodcastFragment.java new file mode 100644 index 00000000..e901c812 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PodcastFragment.java @@ -0,0 +1,93 @@ +package org.moire.ultrasonic.fragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.domain.PodcastsChannel; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.util.BackgroundTask; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.PodcastsChannelsAdapter; + +import java.util.List; + +public class PodcastFragment extends Fragment { + + private View emptyTextView; + ListView channelItemsListView = null; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.podcasts, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + FragmentTitle.Companion.setTitle(this, R.string.podcasts_label); + + emptyTextView = view.findViewById(R.id.select_podcasts_empty); + channelItemsListView = view.findViewById(R.id.podcasts_channels_items_list); + channelItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + PodcastsChannel pc = (PodcastsChannel) parent.getItemAtPosition(position); + if (pc == null) { + return; + } + + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID, pc.getId()); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + } + }); + + // TODO: Probably a swipeRefresh should be added here in the long run + load(); + } + + private void load() + { + BackgroundTask> task = new TabActivityBackgroundTask>(getActivity(), true) + { + @Override + protected List doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + return musicService.getPodcastsChannels(false,getContext(), this); + + /* TODO Why is here a cache cleaning? (original TODO text: c'est quoi ce nettoyage de cache ?) + if (!Util.isOffline(PodcastsActivity.this)) + new CacheCleaner(PodcastsActivity.this, getDownloadService()).cleanPlaylists(playlists); + */ + } + + @Override + protected void done(List result) + { + channelItemsListView.setAdapter(new PodcastsChannelsAdapter(getContext(), result)); + emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); + } + }; + task.execute(); + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SearchFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SearchFragment.java new file mode 100644 index 00000000..4eb37c6f --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SearchFragment.java @@ -0,0 +1,586 @@ +package org.moire.ultrasonic.fragment; + +import android.app.Activity; +import android.app.SearchManager; +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +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.AdapterView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.SearchView; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.data.ActiveServerProvider; +import org.moire.ultrasonic.domain.Artist; +import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.domain.SearchCriteria; +import org.moire.ultrasonic.domain.SearchResult; +import org.moire.ultrasonic.service.MediaPlayerController; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.subsonic.DownloadHandler; +import org.moire.ultrasonic.subsonic.ImageLoaderProvider; +import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker; +import org.moire.ultrasonic.subsonic.ShareHandler; +import org.moire.ultrasonic.subsonic.VideoPlayer; +import org.moire.ultrasonic.util.BackgroundTask; +import org.moire.ultrasonic.util.CancellationToken; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.MergeAdapter; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.ArtistAdapter; +import org.moire.ultrasonic.view.EntryAdapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import kotlin.Lazy; +import timber.log.Timber; + +import static org.koin.java.KoinJavaComponent.inject; + +public class SearchFragment extends Fragment { + + private static int DEFAULT_ARTISTS; + private static int DEFAULT_ALBUMS; + private static int DEFAULT_SONGS; + + private ListView list; + + private View artistsHeading; + private View albumsHeading; + private View songsHeading; + private TextView searchButton; + private View moreArtistsButton; + private View moreAlbumsButton; + private View moreSongsButton; + private SearchResult searchResult; + private MergeAdapter mergeAdapter; + private ArtistAdapter artistAdapter; + private ListAdapter moreArtistsAdapter; + private EntryAdapter albumAdapter; + private ListAdapter moreAlbumsAdapter; + private ListAdapter moreSongsAdapter; + private EntryAdapter songAdapter; + private SwipeRefreshLayout searchRefresh; + + private final Lazy videoPlayer = inject(VideoPlayer.class); + private final Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); + private final Lazy imageLoaderProvider = inject(ImageLoaderProvider.class); + private final Lazy downloadHandler = inject(DownloadHandler.class); + private final Lazy shareHandler = inject(ShareHandler.class); + private final Lazy networkAndStorageChecker = inject(NetworkAndStorageChecker.class); + private CancellationToken cancellationToken; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.search, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + cancellationToken = new CancellationToken(); + + FragmentTitle.Companion.setTitle(this, R.string.search_title); + setHasOptionsMenu(true); + + DEFAULT_ARTISTS = Util.getDefaultArtists(getContext()); + DEFAULT_ALBUMS = Util.getDefaultAlbums(getContext()); + DEFAULT_SONGS = Util.getDefaultSongs(getContext()); + + View buttons = LayoutInflater.from(getContext()).inflate(R.layout.search_buttons, null); + + if (buttons != null) + { + artistsHeading = buttons.findViewById(R.id.search_artists); + albumsHeading = buttons.findViewById(R.id.search_albums); + songsHeading = buttons.findViewById(R.id.search_songs); + searchButton = buttons.findViewById(R.id.search_search); + moreArtistsButton = buttons.findViewById(R.id.search_more_artists); + moreAlbumsButton = buttons.findViewById(R.id.search_more_albums); + moreSongsButton = buttons.findViewById(R.id.search_more_songs); + } + + list = view.findViewById(R.id.search_list); + searchRefresh = view.findViewById(R.id.search_entries_refresh); + searchRefresh.setEnabled(false); // TODO: Should this be enabled? + + list.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if (view == moreArtistsButton) + { + expandArtists(); + } + else if (view == moreAlbumsButton) + { + expandAlbums(); + } + else if (view == moreSongsButton) + { + expandSongs(); + } + else + { + Object item = parent.getItemAtPosition(position); + if (item instanceof Artist) + { + onArtistSelected((Artist) item); + } + else if (item instanceof MusicDirectory.Entry) + { + MusicDirectory.Entry entry = (MusicDirectory.Entry) item; + if (entry.isDirectory()) + { + onAlbumSelected(entry, false); + } + else if (entry.isVideo()) + { + onVideoSelected(entry); + } + else + { + onSongSelected(entry, false, true, true, false); + } + + } + } + } + }); + + registerForContextMenu(list); + + populateList(); + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + Activity activity = getActivity(); + if (activity == null) return; + SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE); + Bundle arguments = getArguments(); + final boolean autoPlay = arguments != null && arguments.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); + + inflater.inflate(R.menu.search, menu); + MenuItem searchItem = menu.findItem(R.id.search_item); + final SearchView searchView = (SearchView) searchItem.getActionView(); + searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName())); + + searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { + @Override + public boolean onSuggestionSelect(int position) { return true; } + + @Override + public boolean onSuggestionClick(int position) { + Timber.d("onSuggestionClick: %d", position); + Cursor cursor= searchView.getSuggestionsAdapter().getCursor(); + cursor.moveToPosition(position); + String suggestion = cursor.getString(2); // TODO: Try to do something with this magic const -- 2 is the index of col containing suggestion name. + searchView.setQuery(suggestion,true); + return true; + } + }); + + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + Timber.d("onQueryTextSubmit: %s", query); + mergeAdapter = new MergeAdapter(); + list.setAdapter(mergeAdapter); + search(query, autoPlay); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { return true; } + }); + searchView.setIconifiedByDefault(false); + searchItem.expandActionView(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, view, menuInfo); + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Object selectedItem = list.getItemAtPosition(info.position); + + boolean isArtist = selectedItem instanceof Artist; + boolean isAlbum = selectedItem instanceof MusicDirectory.Entry && ((MusicDirectory.Entry) selectedItem).isDirectory(); + + if (!isArtist && !isAlbum) + { + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.select_song_context, menu); + } + else + { + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.select_album_context, menu); + } + + MenuItem shareButton = menu.findItem(R.id.menu_item_share); + MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download); + + if (downloadMenuItem != null) + { + downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(getContext())); + } + + if (ActiveServerProvider.Companion.isOffline(getContext()) || isArtist) + { + if (shareButton != null) + { + shareButton.setVisible(false); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) + { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + + if (info == null) + { + return true; + } + + Object selectedItem = list.getItemAtPosition(info.position); + + Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null; + MusicDirectory.Entry entry = selectedItem instanceof MusicDirectory.Entry ? (MusicDirectory.Entry) selectedItem : null; + + String entryId = null; + + if (entry != null) + { + entryId = entry.getId(); + } + + String id = artist != null ? artist.getId() : entryId; + + if (id == null) + { + return true; + } + + List songs = new ArrayList<>(1); + + switch (menuItem.getItemId()) + { + case R.id.album_menu_play_now: + downloadHandler.getValue().downloadRecursively(this, id, false, false, true, false, false, false, false, false); + break; + case R.id.album_menu_play_next: + downloadHandler.getValue().downloadRecursively(this, id, false, true, false, true, false, true, false, false); + break; + case R.id.album_menu_play_last: + downloadHandler.getValue().downloadRecursively(this, id, false, true, false, false, false, false, false, false); + break; + case R.id.album_menu_pin: + downloadHandler.getValue().downloadRecursively(this, id, true, true, false, false, false, false, false, false); + break; + case R.id.album_menu_unpin: + downloadHandler.getValue().downloadRecursively(this, id, false, false, false, false, false, false, true, false); + break; + case R.id.album_menu_download: + downloadHandler.getValue().downloadRecursively(this, id, false, false, false, false, true, false, false, false); + break; + case R.id.song_menu_play_now: + if (entry != null) + { + songs = new ArrayList<>(1); + songs.add(entry); + downloadHandler.getValue().download(this, false, false, true, false, false, songs); + } + break; + case R.id.song_menu_play_next: + if (entry != null) + { + songs = new ArrayList<>(1); + songs.add(entry); + downloadHandler.getValue().download(this, true, false, false, true, false, songs); + } + break; + case R.id.song_menu_play_last: + if (entry != null) + { + songs = new ArrayList<>(1); + songs.add(entry); + downloadHandler.getValue().download(this, true, false, false, false, false, songs); + } + break; + case R.id.song_menu_pin: + if (entry != null) + { + songs.add(entry); + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size())); + downloadBackground(true, songs); + } + break; + case R.id.song_menu_download: + if (entry != null) + { + songs.add(entry); + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size())); + downloadBackground(false, songs); + } + break; + case R.id.song_menu_unpin: + if (entry != null) + { + songs.add(entry); + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); + mediaPlayerControllerLazy.getValue().unpin(songs); + } + break; + case R.id.menu_item_share: + if (entry != null) + { + songs = new ArrayList<>(1); + songs.add(entry); + // TODO: Add SwipeRefresh spinner + shareHandler.getValue().createShare(this, songs, null, cancellationToken); + } + default: + return super.onContextItemSelected(menuItem); + } + + return true; + } + + @Override + public void onDestroyView() { + cancellationToken.cancel(); + super.onDestroyView(); + } + + private void downloadBackground(final boolean save, final List songs) + { + Runnable onValid = new Runnable() + { + @Override + public void run() + { + networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable(); + mediaPlayerControllerLazy.getValue().downloadBackground(songs, save); + } + }; + + onValid.run(); + } + + private void search(final String query, final boolean autoplay) + { + final int maxArtists = Util.getMaxArtists(getContext()); + final int maxAlbums = Util.getMaxAlbums(getContext()); + final int maxSongs = Util.getMaxSongs(getContext()); + + BackgroundTask task = new TabActivityBackgroundTask(getActivity(), true, searchRefresh, cancellationToken) + { + @Override + protected SearchResult doInBackground() throws Throwable + { + SearchCriteria criteria = new SearchCriteria(query, maxArtists, maxAlbums, maxSongs); + MusicService service = MusicServiceFactory.getMusicService(getContext()); + return service.search(criteria, getContext(), this); + } + + @Override + protected void done(SearchResult result) + { + searchResult = result; + + populateList(); + + if (autoplay) + { + autoplay(); + } + + } + }; + task.execute(); + } + + private void populateList() + { + mergeAdapter = new MergeAdapter(); + + // TODO: Remove this if the search widget can do the same + //mergeAdapter.addView(searchButton, true); + + if (searchResult != null) + { + List artists = searchResult.getArtists(); + if (!artists.isEmpty()) + { + mergeAdapter.addView(artistsHeading); + List displayedArtists = new ArrayList(artists.subList(0, Math.min(DEFAULT_ARTISTS, artists.size()))); + artistAdapter = new ArtistAdapter(getContext(), displayedArtists); + mergeAdapter.addAdapter(artistAdapter); + if (artists.size() > DEFAULT_ARTISTS) + { + moreArtistsAdapter = mergeAdapter.addView(moreArtistsButton, true); + } + } + + List albums = searchResult.getAlbums(); + if (!albums.isEmpty()) + { + mergeAdapter.addView(albumsHeading); + List displayedAlbums = new ArrayList(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size()))); + albumAdapter = new EntryAdapter(getContext(), imageLoaderProvider.getValue().getImageLoader(), displayedAlbums, false); + mergeAdapter.addAdapter(albumAdapter); + if (albums.size() > DEFAULT_ALBUMS) + { + moreAlbumsAdapter = mergeAdapter.addView(moreAlbumsButton, true); + } + } + + List songs = searchResult.getSongs(); + if (!songs.isEmpty()) + { + mergeAdapter.addView(songsHeading); + List displayedSongs = new ArrayList(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size()))); + songAdapter = new EntryAdapter(getContext(), imageLoaderProvider.getValue().getImageLoader(), displayedSongs, false); + mergeAdapter.addAdapter(songAdapter); + if (songs.size() > DEFAULT_SONGS) + { + moreSongsAdapter = mergeAdapter.addView(moreSongsButton, true); + } + } + + boolean empty = searchResult.getArtists().isEmpty() && searchResult.getAlbums().isEmpty() && searchResult.getSongs().isEmpty(); + searchButton.setText(empty ? R.string.search_no_match : R.string.search_search); + } + + list.setAdapter(mergeAdapter); + } + + private void expandArtists() + { + artistAdapter.clear(); + + for (Artist artist : searchResult.getArtists()) + { + artistAdapter.add(artist); + } + + artistAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreArtistsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void expandAlbums() + { + albumAdapter.clear(); + + for (MusicDirectory.Entry album : searchResult.getAlbums()) + { + albumAdapter.add(album); + } + + albumAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreAlbumsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void expandSongs() + { + songAdapter.clear(); + + for (MusicDirectory.Entry song : searchResult.getSongs()) + { + songAdapter.add(song); + } + + songAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreSongsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void onArtistSelected(Artist artist) + { + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); + bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getId()); + Navigation.findNavController(getView()).navigate(R.id.searchToSelectAlbum, bundle); + } + + private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay) + { + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_ID, album.getId()); + bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, album.getTitle()); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, album.isDirectory()); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoplay); + Navigation.findNavController(getView()).navigate(R.id.searchToSelectAlbum, bundle); + } + + private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) + { + MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue(); + if (mediaPlayerController != null) + { + if (!append && !playNext) + { + mediaPlayerController.clear(); + } + + mediaPlayerController.download(Collections.singletonList(song), save, false, playNext, false, false); + + if (autoplay) + { + mediaPlayerController.play(mediaPlayerController.getPlaylistSize() - 1); + } + + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1)); + } + } + + private void onVideoSelected(MusicDirectory.Entry entry) + { + videoPlayer.getValue().playVideo(entry); + } + + private void autoplay() + { + if (!searchResult.getSongs().isEmpty()) + { + onSongSelected(searchResult.getSongs().get(0), false, false, true, false); + } + else if (!searchResult.getAlbums().isEmpty()) + { + onAlbumSelected(searchResult.getAlbums().get(0), true); + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectAlbumFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectAlbumFragment.java new file mode 100644 index 00000000..2c6c4ce7 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectAlbumFragment.java @@ -0,0 +1,1321 @@ +package org.moire.ultrasonic.fragment; + +import android.os.AsyncTask; +import android.os.Bundle; +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.AdapterView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.data.ActiveServerProvider; +import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.domain.Share; +import org.moire.ultrasonic.service.DownloadFile; +import org.moire.ultrasonic.service.MediaPlayerController; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.subsonic.DownloadHandler; +import org.moire.ultrasonic.subsonic.ImageLoaderProvider; +import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker; +import org.moire.ultrasonic.subsonic.ShareHandler; +import org.moire.ultrasonic.subsonic.VideoPlayer; +import org.moire.ultrasonic.util.AlbumHeader; +import org.moire.ultrasonic.util.CancellationToken; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator; +import org.moire.ultrasonic.util.Pair; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.AlbumView; +import org.moire.ultrasonic.view.EntryAdapter; +import org.moire.ultrasonic.view.SongView; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import kotlin.Lazy; +import timber.log.Timber; + +import static org.koin.java.KoinJavaComponent.inject; + +public class SelectAlbumFragment extends Fragment { + + public static final String allSongsId = "-1"; + private SwipeRefreshLayout refreshAlbumListView; + private ListView albumListView; + private View header; + private View albumButtons; + private View emptyView; + private ImageView selectButton; + private ImageView playNowButton; + private ImageView playNextButton; + private ImageView playLastButton; + private ImageView pinButton; + private ImageView unpinButton; + private ImageView downloadButton; + private ImageView deleteButton; + private ImageView moreButton; + private boolean playAllButtonVisible; + private boolean shareButtonVisible; + private MenuItem playAllButton; + private MenuItem shareButton; + private boolean showHeader = true; + private Random random = new java.security.SecureRandom(); + + private final Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); + private final Lazy videoPlayer = inject(VideoPlayer.class); + private final Lazy downloadHandler = inject(DownloadHandler.class); + private final Lazy networkAndStorageChecker = inject(NetworkAndStorageChecker.class); + private final Lazy imageLoaderProvider = inject(ImageLoaderProvider.class); + private final Lazy shareHandler = inject(ShareHandler.class); + private CancellationToken cancellationToken; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.select_album, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + cancellationToken = new CancellationToken(); + + albumButtons = view.findViewById(R.id.menu_album); + + refreshAlbumListView = view.findViewById(R.id.select_album_entries_refresh); + albumListView = view.findViewById(R.id.select_album_entries_list); + + refreshAlbumListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() + { + @Override + public void onRefresh() { + new SelectAlbumFragment.GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + header = LayoutInflater.from(getContext()).inflate(R.layout.select_album_header, albumListView, false); + + albumListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + albumListView.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if (position >= 0) + { + MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position); + if (entry != null && entry.isDirectory()) + { + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getId()); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, entry.isDirectory()); + bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getTitle()); + bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.getParent()); + Navigation.findNavController(view).navigate(R.id.selectAlbumFragment, bundle); + } + else if (entry != null && entry.isVideo()) + { + videoPlayer.getValue().playVideo(entry); + } + else + { + enableButtons(); + } + } + } + }); + + // TODO: Long click on an item will first try to maximize / collapse the item, even when it fits inside the TextView. + // The context menu is only displayed on the second long click... + albumListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener(){ + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if (view instanceof AlbumView) { + AlbumView albumView = (AlbumView) view; + if (!albumView.isMaximized()) { + albumView.maximizeOrMinimize(); + return true; + } else { + return false; + } + } + if (view instanceof SongView) { + SongView songView = (SongView) view; + songView.maximizeOrMinimize(); + return true; + } + return false; + } + }); + + selectButton = view.findViewById(R.id.select_album_select); + playNowButton = view.findViewById(R.id.select_album_play_now); + playNextButton = view.findViewById(R.id.select_album_play_next); + playLastButton = view.findViewById(R.id.select_album_play_last); + pinButton = view.findViewById(R.id.select_album_pin); + unpinButton = view.findViewById(R.id.select_album_unpin); + downloadButton = view.findViewById(R.id.select_album_download); + deleteButton = view.findViewById(R.id.select_album_delete); + moreButton = view.findViewById(R.id.select_album_more); + emptyView = view.findViewById(R.id.select_album_empty); + + selectButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + selectAllOrNone(); + } + }); + playNowButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + playNow(false, false); + } + }); + playNextButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + downloadHandler.getValue().download(SelectAlbumFragment.this,true, false, false, true, false, getSelectedSongs(albumListView)); + selectAll(false, false); + } + }); + playLastButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + playNow(false, true); + } + }); + pinButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + downloadBackground(true); + selectAll(false, false); + } + }); + unpinButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + unpin(); + selectAll(false, false); + } + }); + downloadButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + downloadBackground(false); + selectAll(false, false); + } + }); + deleteButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + delete(); + selectAll(false, false); + } + }); + + registerForContextMenu(albumListView); + setHasOptionsMenu(true); + enableButtons(); + + String id = getArguments().getString(Constants.INTENT_EXTRA_NAME_ID); + boolean isAlbum = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false); + String name = getArguments().getString(Constants.INTENT_EXTRA_NAME_NAME); + String parentId = getArguments().getString(Constants.INTENT_EXTRA_NAME_PARENT_ID); + String playlistId = getArguments().getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); + String podcastChannelId = getArguments().getString(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID); + String playlistName = getArguments().getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); + String shareId = getArguments().getString(Constants.INTENT_EXTRA_NAME_SHARE_ID); + String shareName = getArguments().getString(Constants.INTENT_EXTRA_NAME_SHARE_NAME); + String albumListType = getArguments().getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + String genreName = getArguments().getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME); + int albumListTitle = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0); + int getStarredTracks = getArguments().getInt(Constants.INTENT_EXTRA_NAME_STARRED, 0); + int getVideos = getArguments().getInt(Constants.INTENT_EXTRA_NAME_VIDEOS, 0); + int getRandomTracks = getArguments().getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0); + int albumListSize = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + int albumListOffset = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); + + if (playlistId != null) + { + getPlaylist(playlistId, playlistName); + } + else if (podcastChannelId != null) { + getPodcastEpisodes(podcastChannelId); + } + else if (shareId != null) + { + getShare(shareId, shareName); + } + else if (albumListType != null) + { + getAlbumList(albumListType, albumListTitle, albumListSize, albumListOffset); + } + else if (genreName != null) + { + getSongsForGenre(genreName, albumListSize, albumListOffset); + } + else if (getStarredTracks != 0) + { + getStarred(); + } + else if (getVideos != 0) + { + getVideos(); + } + else if (getRandomTracks != 0) + { + getRandom(albumListSize); + } + else + { + if (!ActiveServerProvider.Companion.isOffline(getActivity()) && Util.getShouldUseId3Tags(getActivity())) + { + if (isAlbum) + { + getAlbum(id, name, parentId); + } + else + { + getArtist(id, name); + } + } + else + { + getMusicDirectory(id, name, parentId); + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, view, menuInfo); + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(info.position); + + if (entry != null && entry.isDirectory()) + { + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.select_album_context, menu); + } + + shareButton = menu.findItem(R.id.menu_item_share); + + if (shareButton != null) + { + shareButton.setVisible(!ActiveServerProvider.Companion.isOffline(getContext())); + } + + MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download); + + if (downloadMenuItem != null) + { + downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(getContext())); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) + { + Timber.d("onContextItemSelected"); + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + + if (info == null) + { + return true; + } + + MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(info.position); + + if (entry == null) + { + return true; + } + + String entryId = entry.getId(); + + switch (menuItem.getItemId()) + { + case R.id.album_menu_play_now: + downloadHandler.getValue().downloadRecursively(this, entryId, false, false, true, false, false, false, false, false); + break; + case R.id.album_menu_play_next: + downloadHandler.getValue().downloadRecursively(this, entryId, false, false, false, false, false, true, false, false); + break; + case R.id.album_menu_play_last: + downloadHandler.getValue().downloadRecursively(this, entryId, false, true, false, false, false, false, false, false); + break; + case R.id.album_menu_pin: + downloadHandler.getValue().downloadRecursively(this, entryId, true, true, false, false, false, false, false, false); + break; + case R.id.album_menu_unpin: + downloadHandler.getValue().downloadRecursively(this, entryId, false, false, false, false, false, false, true, false); + break; + case R.id.album_menu_download: + downloadHandler.getValue().downloadRecursively(this, entryId, false, false, false, false, true, false, false, false); + break; + case R.id.select_album_play_all: + playAll(); + break; + case R.id.menu_item_share: + List entries = new ArrayList(1); + entries.add(entry); + shareHandler.getValue().createShare(this, entries, refreshAlbumListView, cancellationToken); + return true; + default: + return super.onContextItemSelected(menuItem); + } + return true; + } + + @Override + public void onPrepareOptionsMenu(Menu menu) + { + super.onPrepareOptionsMenu(menu); + playAllButton = menu.findItem(R.id.select_album_play_all); + + if (playAllButton != null) + { + playAllButton.setVisible(playAllButtonVisible); + } + + shareButton = menu.findItem(R.id.menu_item_share); + + if (shareButton != null) + { + shareButton.setVisible(shareButtonVisible); + } + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + inflater.inflate(R.menu.select_album, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.select_album_play_all: + playAll(); + return true; + case R.id.menu_item_share: + shareHandler.getValue().createShare(this, getSelectedSongs(albumListView), refreshAlbumListView, cancellationToken); + return true; + } + + return false; + } + + @Override + public void onDestroyView() { + cancellationToken.cancel(); + super.onDestroyView(); + } + + private void playNow(final boolean shuffle, final boolean append) + { + List selectedSongs = getSelectedSongs(albumListView); + + if (!selectedSongs.isEmpty()) + { + downloadHandler.getValue().download(this, append, false, !append, false, shuffle, selectedSongs); + selectAll(false, false); + } + else + { + playAll(shuffle, append); + } + } + + private void playAll() + { + playAll(false, false); + } + + private void playAll(final boolean shuffle, final boolean append) + { + boolean hasSubFolders = false; + + for (int i = 0; i < albumListView.getCount(); i++) + { + MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i); + if (entry != null && entry.isDirectory()) + { + hasSubFolders = true; + break; + } + } + + boolean isArtist = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false); + String id = getArguments().getString(Constants.INTENT_EXTRA_NAME_ID); + + if (hasSubFolders && id != null) + { + downloadHandler.getValue().downloadRecursively(this, id, false, append, !append, shuffle, false, false, false, isArtist); + } + else + { + selectAll(true, false); + downloadHandler.getValue().download(this, append, false, !append, false, shuffle, getSelectedSongs(albumListView)); + selectAll(false, false); + } + } + + private static List getSelectedSongs(ListView albumListView) + { + List songs = new ArrayList(10); + + if (albumListView != null) + { + int count = albumListView.getCount(); + for (int i = 0; i < count; i++) + { + if (albumListView.isItemChecked(i)) + { + songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(i)); + } + } + } + + return songs; + } + + private void refresh() + { + // TODO: create better restart + getView().post(new Runnable() { + public void run() { + Timber.d("Refresh called..."); + getArguments().putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + onViewCreated(getView(), null); + } + }); + + /*finish(); + Intent intent = getArguments(); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + startActivityForResultWithoutTransition(this, intent);*/ + } + + private void getMusicDirectory(final String id, final String name, final String parentId) + { + FragmentTitle.Companion.setTitle(this, name); + //setActionBarSubtitle(name); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + MusicDirectory root = new MusicDirectory(); + + if (allSongsId.equals(id)) + { + boolean refresh = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + MusicDirectory musicDirectory = service.getMusicDirectory(parentId, name, refresh, getContext(), this); + + List songs = new LinkedList(); + getSongsRecursively(musicDirectory, songs); + + for (MusicDirectory.Entry song : songs) + { + if (!song.isDirectory()) + { + root.addChild(song); + } + } + } + else + { + boolean refresh = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + MusicDirectory musicDirectory = service.getMusicDirectory(id, name, refresh, getContext(), this); + + if (Util.getShouldShowAllSongsByArtist(getContext()) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size() == musicDirectory.getChildren(true, true).size()) + { + MusicDirectory.Entry allSongs = new MusicDirectory.Entry(); + + allSongs.setDirectory(true); + allSongs.setArtist(name); + allSongs.setParent(id); + allSongs.setId(allSongsId); + allSongs.setTitle(String.format(getResources().getString(R.string.select_album_all_songs), name)); + + root.addChild(allSongs); + + List children = musicDirectory.getChildren(); + + if (children != null) + { + root.addAll(children); + } + } + else + { + root = musicDirectory; + } + } + + return root; + } + + private void getSongsRecursively(MusicDirectory parent, List songs) throws Exception + { + for (MusicDirectory.Entry song : parent.getChildren(false, true)) + { + if (!song.isVideo() && !song.isDirectory()) + { + songs.add(song); + } + } + + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + + for (MusicDirectory.Entry dir : parent.getChildren(true, false)) + { + MusicDirectory root; + + if (!allSongsId.equals(dir.getId())) + { + root = musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, getContext(), this); + + getSongsRecursively(root, songs); + } + } + } + }.execute(); + } + + private void getArtist(final String id, final String name) + { + FragmentTitle.Companion.setTitle(this, name); + //setActionBarSubtitle(name); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + MusicDirectory root = new MusicDirectory(); + + boolean refresh = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + MusicDirectory musicDirectory = service.getArtist(id, name, refresh, getContext(), this); + + if (Util.getShouldShowAllSongsByArtist(getContext()) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size() == musicDirectory.getChildren(true, true).size()) + { + MusicDirectory.Entry allSongs = new MusicDirectory.Entry(); + + allSongs.setDirectory(true); + allSongs.setArtist(name); + allSongs.setParent(id); + allSongs.setId(allSongsId); + allSongs.setTitle(String.format(getResources().getString(R.string.select_album_all_songs), name)); + + root.addFirst(allSongs); + + List children = musicDirectory.getChildren(); + + if (children != null) + { + root.addAll(children); + } + } + else + { + root = musicDirectory; + } + + return root; + } + }.execute(); + } + + private void getAlbum(final String id, final String name, final String parentId) + { + FragmentTitle.Companion.setTitle(this, name); + //setActionBarSubtitle(name); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + MusicDirectory musicDirectory; + + boolean refresh = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + + if (allSongsId.equals(id)) + { + MusicDirectory root = new MusicDirectory(); + + Collection songs = new LinkedList(); + getSongsForArtist(parentId, songs); + + for (MusicDirectory.Entry song : songs) + { + if (!song.isDirectory()) + { + root.addChild(song); + } + } + + musicDirectory = root; + } + else + { + musicDirectory = service.getAlbum(id, name, refresh, getContext(), this); + } + + return musicDirectory; + } + + private void getSongsForArtist(String id, Collection songs) throws Exception + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + MusicDirectory artist = musicService.getArtist(id, "", false, getContext(), this); + + for (MusicDirectory.Entry album : artist.getChildren()) + { + if (!allSongsId.equals(album.getId())) + { + MusicDirectory albumDirectory = musicService.getAlbum(album.getId(), "", false, getContext(), this); + + for (MusicDirectory.Entry song : albumDirectory.getChildren()) + { + if (!song.isVideo()) + { + songs.add(song); + } + } + } + } + } + }.execute(); + } + + private void getSongsForGenre(final String genre, final int count, final int offset) + { + FragmentTitle.Companion.setTitle(this, genre); + //setActionBarSubtitle(genre); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return service.getSongsByGenre(genre, count, offset, getContext(), this); + } + + @Override + protected void done(Pair result) + { + // Hide more button when results are less than album list size + if (result.getFirst().getChildren().size() < getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)) + { + moreButton.setVisibility(View.GONE); + } + else + { + moreButton.setVisibility(View.VISIBLE); + } + + moreButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + String genre = getArguments().getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME); + int size = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + int offset = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + size; + + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, genre); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + } + }); + + super.done(result); + } + }.execute(); + } + + private void getStarred() + { + FragmentTitle.Companion.setTitle(this, R.string.main_songs_starred); + //setActionBarSubtitle(R.string.main_songs_starred); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return Util.getShouldUseId3Tags(getContext()) ? Util.getSongsFromSearchResult(service.getStarred2(getContext(), this)) : Util.getSongsFromSearchResult(service.getStarred(getContext(), this)); + } + }.execute(); + } + + private void getVideos() + { + showHeader = false; + + FragmentTitle.Companion.setTitle(this, R.string.main_videos); + //setActionBarSubtitle(R.string.main_videos); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + boolean refresh = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + return service.getVideos(refresh, getContext(), this); + } + }.execute(); + } + + private void getRandom(final int size) + { + FragmentTitle.Companion.setTitle(this, R.string.main_songs_random); + //setActionBarSubtitle(R.string.main_songs_random); + + new LoadTask() + { + @Override + protected boolean sortableCollection() { + return false; + } + + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return service.getRandomSongs(size, getContext(), this); + } + }.execute(); + } + + private void getPlaylist(final String playlistId, final String playlistName) + { + FragmentTitle.Companion.setTitle(this, playlistName); + //setActionBarSubtitle(playlistName); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return service.getPlaylist(playlistId, playlistName, getContext(), this); + } + }.execute(); + } + + private void getPodcastEpisodes(final String podcastChannelId) + { + // TODO: Not sure what the title should be for a podcast episode. Maybe a constant string should be used. + //setActionBarSubtitle(playlistName); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return service.getPodcastEpisodes(podcastChannelId, getContext(), this); + } + }.execute(); + } + + private void getShare(final String shareId, final CharSequence shareName) + { + FragmentTitle.Companion.setTitle(this, shareName); + //setActionBarSubtitle(shareName); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + List shares = service.getShares(true, getContext(), this); + + MusicDirectory md = new MusicDirectory(); + + for (Share share : shares) + { + if (share.getId().equals(shareId)) + { + for (MusicDirectory.Entry entry : share.getEntries()) + { + md.addChild(entry); + } + + break; + } + } + + return md; + } + }.execute(); + } + + private void getAlbumList(final String albumListType, final int albumListTitle, final int size, final int offset) + { + showHeader = false; + + FragmentTitle.Companion.setTitle(this, albumListTitle); + //setActionBarSubtitle(albumListTitle); + + new LoadTask() + { + @Override + protected boolean sortableCollection() { + return !albumListType.equals("newest") && !albumListType.equals("random") && + !albumListType.equals("highest") && !albumListType.equals("recent") && + !albumListType.equals("frequent"); + } + + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return Util.getShouldUseId3Tags(getContext()) ? service.getAlbumList2(albumListType, size, offset, getContext(), this) : service.getAlbumList(albumListType, size, offset, getContext(), this); + } + + @Override + protected void done(Pair result) + { + if (!result.getFirst().getChildren().isEmpty()) + { + pinButton.setVisibility(View.GONE); + unpinButton.setVisibility(View.GONE); + downloadButton.setVisibility(View.GONE); + deleteButton.setVisibility(View.GONE); + + // Hide more button when results are less than album list size + if (result.getFirst().getChildren().size() < getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)) + { + moreButton.setVisibility(View.GONE); + } + else + { + moreButton.setVisibility(View.VISIBLE); + + moreButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + int albumListTitle = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0); + String type = getArguments().getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + int size = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + int offset = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + size; + + Bundle bundle = new Bundle(); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, albumListTitle); + bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + } + }); + } + } + else + { + moreButton.setVisibility(View.GONE); + } + + super.done(result); + } + }.execute(); + } + + private void selectAllOrNone() + { + boolean someUnselected = false; + int count = albumListView.getCount(); + + for (int i = 0; i < count; i++) + { + if (!albumListView.isItemChecked(i) && albumListView.getItemAtPosition(i) instanceof MusicDirectory.Entry) + { + someUnselected = true; + break; + } + } + + selectAll(someUnselected, true); + } + + private void selectAll(boolean selected, boolean toast) + { + int count = albumListView.getCount(); + int selectedCount = 0; + + for (int i = 0; i < count; i++) + { + MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i); + if (entry != null && !entry.isDirectory() && !entry.isVideo()) + { + albumListView.setItemChecked(i, selected); + selectedCount++; + } + } + + // Display toast: N tracks selected / N tracks unselected + if (toast) + { + int toastResId = selected ? R.string.select_album_n_selected : R.string.select_album_n_unselected; + Util.toast(getActivity(), getString(toastResId, selectedCount)); + } + + enableButtons(); + } + + private void enableButtons() + { + List selection = getSelectedSongs(albumListView); + boolean enabled = !selection.isEmpty(); + boolean unpinEnabled = false; + boolean deleteEnabled = false; + + int pinnedCount = 0; + + for (MusicDirectory.Entry song : selection) + { + DownloadFile downloadFile = mediaPlayerControllerLazy.getValue().getDownloadFileForSong(song); + if (downloadFile.isWorkDone()) + { + deleteEnabled = true; + } + + if (downloadFile.isSaved()) + { + pinnedCount++; + unpinEnabled = true; + } + } + + playNowButton.setVisibility(enabled ? View.VISIBLE : View.GONE); + playNextButton.setVisibility(enabled ? View.VISIBLE : View.GONE); + playLastButton.setVisibility(enabled ? View.VISIBLE : View.GONE); + pinButton.setVisibility((enabled && !ActiveServerProvider.Companion.isOffline(getContext()) && selection.size() > pinnedCount) ? View.VISIBLE : View.GONE); + unpinButton.setVisibility(enabled && unpinEnabled ? View.VISIBLE : View.GONE); + downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(getContext()) ? View.VISIBLE : View.GONE); + deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE); + } + + private void downloadBackground(final boolean save) + { + List songs = getSelectedSongs(albumListView); + + if (songs.isEmpty()) + { + selectAll(true, false); + songs = getSelectedSongs(albumListView); + } + + downloadBackground(save, songs); + } + + private void downloadBackground(final boolean save, final List songs) + { + Runnable onValid = new Runnable() + { + @Override + public void run() + { + networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable(); + mediaPlayerControllerLazy.getValue().downloadBackground(songs, save); + + if (save) + { + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size())); + } + else + { + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size())); + } + } + }; + onValid.run(); + } + + private void delete() + { + List songs = getSelectedSongs(albumListView); + + if (songs.isEmpty()) + { + selectAll(true, false); + songs = getSelectedSongs(albumListView); + } + + mediaPlayerControllerLazy.getValue().delete(songs); + } + + private void unpin() + { + List songs = getSelectedSongs(albumListView); + Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); + mediaPlayerControllerLazy.getValue().unpin(songs); + } + + private abstract class LoadTask extends TabActivityBackgroundTask> + { + + public LoadTask() + { + super(SelectAlbumFragment.this.getActivity(), true, refreshAlbumListView, cancellationToken); + } + + protected abstract MusicDirectory load(MusicService service) throws Exception; + + protected boolean sortableCollection() { + return true; + } + + @Override + protected Pair doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + MusicDirectory dir = load(musicService); + boolean valid = musicService.isLicenseValid(getContext(), this); + return new Pair(dir, valid); + } + + @Override + protected void done(Pair result) + { + MusicDirectory musicDirectory = result.getFirst(); + List entries = musicDirectory.getChildren(); + + if (sortableCollection() && Util.getShouldSortByDisc(getContext())) + { + Collections.sort(entries, new EntryByDiscAndTrackComparator()); + } + + boolean allVideos = true; + int songCount = 0; + + for (MusicDirectory.Entry entry : entries) + { + if (!entry.isVideo()) + { + allVideos = false; + } + + if (!entry.isDirectory()) + { + songCount++; + } + } + + final int listSize = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + + if (songCount > 0) + { + if (showHeader) + { + String intentAlbumName = getArguments().getString(Constants.INTENT_EXTRA_NAME_NAME); + String directoryName = musicDirectory.getName(); + View header = createHeader(entries, intentAlbumName != null ? intentAlbumName : directoryName, songCount); + + if (header != null && albumListView.getHeaderViewsCount() == 0) + { + albumListView.addHeaderView(header, null, false); + } + } + + pinButton.setVisibility(View.VISIBLE); + unpinButton.setVisibility(View.VISIBLE); + downloadButton.setVisibility(View.VISIBLE); + deleteButton.setVisibility(View.VISIBLE); + selectButton.setVisibility(allVideos ? View.GONE : View.VISIBLE); + playNowButton.setVisibility(View.VISIBLE); + playNextButton.setVisibility(View.VISIBLE); + playLastButton.setVisibility(View.VISIBLE); + + if (listSize == 0 || songCount < listSize) + { + moreButton.setVisibility(View.GONE); + } + else + { + moreButton.setVisibility(View.VISIBLE); + + if (getArguments().getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0) > 0) + { + moreButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + int offset = getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + listSize; + + Bundle bundle = new Bundle(); + bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, listSize); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + } + }); + } + } + } + else + { + pinButton.setVisibility(View.GONE); + unpinButton.setVisibility(View.GONE); + downloadButton.setVisibility(View.GONE); + deleteButton.setVisibility(View.GONE); + selectButton.setVisibility(View.GONE); + playNowButton.setVisibility(View.GONE); + playNextButton.setVisibility(View.GONE); + playLastButton.setVisibility(View.GONE); + + if (listSize == 0 || result.getFirst().getChildren().size() < listSize) + { + albumButtons.setVisibility(View.GONE); + } + else + { + moreButton.setVisibility(View.VISIBLE); + } + } + + enableButtons(); + + boolean isAlbumList = getArguments().containsKey(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos; + shareButtonVisible = !ActiveServerProvider.Companion.isOffline(getContext()) && songCount > 0; + + emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE); + + if (playAllButton != null) + { + playAllButton.setVisible(playAllButtonVisible); + } + + if (shareButton != null) + { + shareButton.setVisible(shareButtonVisible); + } + + albumListView.setAdapter(new EntryAdapter(getContext(), imageLoaderProvider.getValue().getImageLoader(), entries, true)); + + boolean playAll = getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); + if (playAll && songCount > 0) + { + playAll(getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), false); + } + } + + protected View createHeader(List entries, CharSequence name, int songCount) + { + ImageView coverArtView = (ImageView) header.findViewById(R.id.select_album_art); + int artworkSelection = random.nextInt(entries.size()); + imageLoaderProvider.getValue().getImageLoader().loadImage(coverArtView, entries.get(artworkSelection), false, Util.getAlbumImageSize(getContext()), false, true); + + AlbumHeader albumHeader = AlbumHeader.processEntries(getContext(), entries); + + TextView titleView = (TextView) header.findViewById(R.id.select_album_title); + titleView.setText(name != null ? name : FragmentTitle.Companion.getTitle(SelectAlbumFragment.this)); //getActionBarSubtitle()); + + // Don't show a header if all entries are videos + if (albumHeader.getIsAllVideo()) + { + return null; + } + + TextView artistView = header.findViewById(R.id.select_album_artist); + String artist; + + artist = albumHeader.getArtists().size() == 1 ? albumHeader.getArtists().iterator().next() : albumHeader.getGrandParents().size() == 1 ? albumHeader.getGrandParents().iterator().next() : getResources().getString(R.string.common_various_artists); + + artistView.setText(artist); + + TextView genreView = header.findViewById(R.id.select_album_genre); + String genre; + + genre = albumHeader.getGenres().size() == 1 ? albumHeader.getGenres().iterator().next() : getResources().getString(R.string.common_multiple_genres); + + genreView.setText(genre); + + TextView yearView = header.findViewById(R.id.select_album_year); + String year; + + year = albumHeader.getYears().size() == 1 ? albumHeader.getYears().iterator().next().toString() : getResources().getString(R.string.common_multiple_years); + + yearView.setText(year); + + TextView songCountView = header.findViewById(R.id.select_album_song_count); + String songs = getResources().getQuantityString(R.plurals.select_album_n_songs, songCount, songCount); + songCountView.setText(songs); + + String duration = Util.formatTotalDuration(albumHeader.getTotalDuration()); + + TextView durationView = header.findViewById(R.id.select_album_duration); + durationView.setText(duration); + + return header; + } + } + + private class GetDataTask extends AsyncTask + { + @Override + protected void onPostExecute(String[] result) + { + super.onPostExecute(result); + } + + @Override + protected String[] doInBackground(Void... params) + { + refresh(); + return null; + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectGenreFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectGenreFragment.java new file mode 100644 index 00000000..c0cc3b0b --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SelectGenreFragment.java @@ -0,0 +1,174 @@ +package org.moire.ultrasonic.fragment; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.domain.Genre; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.util.BackgroundTask; +import org.moire.ultrasonic.util.CancellationToken; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.GenreAdapter; + +import java.util.ArrayList; +import java.util.List; + +import timber.log.Timber; + +public class SelectGenreFragment extends Fragment { + + private SwipeRefreshLayout refreshGenreListView; + private ListView genreListView; + private View emptyView; + private CancellationToken cancellationToken; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.select_genre, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + cancellationToken = new CancellationToken(); + refreshGenreListView = view.findViewById(R.id.select_genre_refresh); + genreListView = view.findViewById(R.id.select_genre_list); + + refreshGenreListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() + { + @Override + public void onRefresh() + { + new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + genreListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Genre genre = (Genre) parent.getItemAtPosition(position); + + if (genre != null) + { + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, genre.getName()); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(getContext())); + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + } + } + }); + + emptyView = view.findViewById(R.id.select_genre_empty); + registerForContextMenu(genreListView); + + FragmentTitle.Companion.setTitle(this, R.string.main_genres_title); + + load(); + } + + @Override + public void onDestroyView() { + cancellationToken.cancel(); + super.onDestroyView(); + } + + private void refresh() + { + // TODO: create better restart + getView().post(new Runnable() { + public void run() { + Timber.d("Refresh called..."); + if (getArguments() == null) { + Bundle bundle = new Bundle(); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + setArguments(bundle); + } else { + getArguments().putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + } + onViewCreated(getView(), null); + } + }); +/* finish(); + Intent intent = getIntent(); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + startActivityForResultWithoutTransition(this, intent); + + */ + } + + private void load() + { + BackgroundTask> task = new TabActivityBackgroundTask>(getActivity(), true, refreshGenreListView, cancellationToken) + { + @Override + protected List doInBackground() throws Throwable + { + boolean refresh = getArguments() != null && getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + + List genres = new ArrayList(); + + try + { + genres = musicService.getGenres(refresh, getContext(), this); + } + catch (Exception x) + { + Timber.e(x, "Failed to load genres"); + } + + return genres; + } + + @Override + protected void done(List result) + { + emptyView.setVisibility(result == null || result.isEmpty() ? View.VISIBLE : View.GONE); + + if (result != null) + { + genreListView.setAdapter(new GenreAdapter(getContext(), result)); + } + } + }; + task.execute(); + } + + private class GetDataTask extends AsyncTask + { + @Override + protected void onPostExecute(String[] result) + { + super.onPostExecute(result); + } + + @Override + protected String[] doInBackground(Void... params) + { + refresh(); + return null; + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index c6e97ba0..88843398 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -6,10 +6,18 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; -import android.preference.*; import android.provider.SearchRecentSuggestions; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.preference.CheckBoxPreference; +import androidx.preference.EditTextPreference; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; import timber.log.Timber; import android.view.View; @@ -26,6 +34,7 @@ import org.moire.ultrasonic.log.FileLoggerTree; import org.moire.ultrasonic.provider.SearchSuggestionProvider; import org.moire.ultrasonic.service.Consumer; import org.moire.ultrasonic.service.MediaPlayerController; +import org.moire.ultrasonic.subsonic.ImageLoaderProvider; import org.moire.ultrasonic.util.*; import java.io.File; @@ -38,9 +47,10 @@ import static org.moire.ultrasonic.activity.ServerSelectorActivity.SERVER_SELECT /** * Shows main app settings. */ -public class SettingsFragment extends PreferenceFragment +public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { + private Preference addServerPreference; private ListPreference theme; private ListPreference videoPlayer; private ListPreference maxBitrateWifi; @@ -77,53 +87,60 @@ public class SettingsFragment extends PreferenceFragment private SharedPreferences settings; private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); + private Lazy imageLoader = inject(ImageLoaderProvider.class); @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.settings); - settings = PreferenceManager.getDefaultSharedPreferences(getActivity()); } + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.settings, rootKey); + } + @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + FragmentTitle.Companion.setTitle(this, R.string.menu_settings); - theme = (ListPreference) findPreference(Constants.PREFERENCES_KEY_THEME); - videoPlayer = (ListPreference) findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER); - maxBitrateWifi = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); - maxBitrateMobile = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); - cacheSize = (ListPreference) findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); + addServerPreference = findPreference(Constants.PREFERENCES_KEY_SERVERS_EDIT); + theme = findPreference(Constants.PREFERENCES_KEY_THEME); + videoPlayer = findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER); + maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); + maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); + cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); cacheLocation = findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION); - preloadCount = (ListPreference) findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT); - bufferLength = (ListPreference) findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH); - incrementTime = (ListPreference) findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME); - networkTimeout = (ListPreference) findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT); - maxAlbums = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS); - maxSongs = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_SONGS); - maxArtists = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS); - defaultArtists = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS); - defaultSongs = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS); - defaultAlbums = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS); - chatRefreshInterval = (ListPreference) findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL); - directoryCacheTime = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME); - mediaButtonsEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS); - lockScreenEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS); - sendBluetoothAlbumArt = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART); - sendBluetoothNotifications = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS); - viewRefresh = (ListPreference) findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH); - imageLoaderConcurrency = (ListPreference) findPreference(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY); - sharingDefaultDescription = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION); - sharingDefaultGreeting = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING); - sharingDefaultExpiration = (TimeSpanPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION); - serversCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_SERVERS_KEY); + preloadCount = findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT); + bufferLength = findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH); + incrementTime = findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME); + networkTimeout = findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT); + maxAlbums = findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS); + maxSongs = findPreference(Constants.PREFERENCES_KEY_MAX_SONGS); + maxArtists = findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS); + defaultArtists = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS); + defaultSongs = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS); + defaultAlbums = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS); + chatRefreshInterval = findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL); + directoryCacheTime = findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME); + mediaButtonsEnabled = findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS); + lockScreenEnabled = findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS); + sendBluetoothAlbumArt = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART); + sendBluetoothNotifications = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS); + viewRefresh = findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH); + imageLoaderConcurrency = findPreference(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY); + sharingDefaultDescription = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION); + sharingDefaultGreeting = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING); + sharingDefaultExpiration = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION); + serversCategory = findPreference(Constants.PREFERENCES_KEY_SERVERS_KEY); resumeOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE); pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE); - debugLogToFile = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE); - showArtistPicture = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE); + debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE); + showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE); + setupServersCategory(); sharingDefaultGreeting.setText(Util.getShareGreeting(getActivity())); setupClearSearchPreference(); setupGaplessControlSettingsV14(); @@ -133,7 +150,7 @@ public class SettingsFragment extends PreferenceFragment // After API26 foreground services must be used for music playback, and they must have a notification if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PreferenceCategory notificationsCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS); + PreferenceCategory notificationsCategory = findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS); notificationsCategory.removePreference(findPreference(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION)); notificationsCategory.removePreference(findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION)); } @@ -149,8 +166,6 @@ public class SettingsFragment extends PreferenceFragment @Override public void onResume() { super.onResume(); - - setupServersCategory(); SharedPreferences preferences = Util.getPreferences(getActivity()); preferences.registerOnSharedPreferenceChangeListener(this); } @@ -158,7 +173,6 @@ public class SettingsFragment extends PreferenceFragment @Override public void onPause() { super.onPause(); - SharedPreferences prefs = Util.getPreferences(getActivity()); prefs.unregisterOnSharedPreferenceChangeListener(this); } @@ -184,6 +198,29 @@ public class SettingsFragment extends PreferenceFragment } } + @Override + public void onDisplayPreferenceDialog(Preference preference) + { + DialogFragment dialogFragment = null; + if (preference instanceof TimeSpanPreference) + { + dialogFragment = new TimeSpanPreferenceDialogFragmentCompat(); + Bundle bundle = new Bundle(1); + bundle.putString("key", preference.getKey()); + dialogFragment.setArguments(bundle); + } + + if (dialogFragment != null) + { + dialogFragment.setTargetFragment(this, 0); + dialogFragment.show(this.getFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG"); + } + else + { + super.onDisplayPreferenceDialog(preference); + } + } + private void setupCacheLocationPreference() { cacheLocation.setSummary(settings.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(getActivity()).getPath())); @@ -202,9 +239,9 @@ public class SettingsFragment extends PreferenceFragment filePickerDialog.setOnFileSelectedListener(new OnFileSelectedListener() { @Override public void onFileSelected(File file, String path) { - SharedPreferences.Editor editor = cacheLocation.getEditor(); + SharedPreferences.Editor editor = cacheLocation.getSharedPreferences().edit(); editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, path); - editor.commit(); + editor.apply(); setCacheLocation(path); } @@ -234,9 +271,9 @@ public class SettingsFragment extends PreferenceFragment new Consumer() { @Override public void accept(Integer choice) { - SharedPreferences.Editor editor = resumeOnBluetoothDevice.getEditor(); + SharedPreferences.Editor editor = resumeOnBluetoothDevice.getSharedPreferences().edit(); editor.putInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, choice); - editor.commit(); + editor.apply(); resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice)); } }); @@ -253,9 +290,9 @@ public class SettingsFragment extends PreferenceFragment new Consumer() { @Override public void accept(Integer choice) { - SharedPreferences.Editor editor = pauseOnBluetoothDevice.getEditor(); + SharedPreferences.Editor editor = pauseOnBluetoothDevice.getSharedPreferences().edit(); editor.putInt(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE, choice); - editor.commit(); + editor.apply(); pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice)); } }); @@ -330,7 +367,7 @@ public class SettingsFragment extends PreferenceFragment @Override public boolean onPreferenceChange(Preference preference, Object o) { featureStorage.changeFeatureFlag(Feature.NEW_IMAGE_DOWNLOADER, (Boolean) o); - ((SubsonicTabActivity) getActivity()).clearImageLoader(); + imageLoader.getValue().clearImageLoader(); return true; } }); @@ -355,9 +392,9 @@ public class SettingsFragment extends PreferenceFragment private void setupGaplessControlSettingsV14() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { PreferenceCategory playbackControlSettings = - (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_PLAYBACK_CONTROL_SETTINGS); + findPreference(Constants.PREFERENCES_KEY_PLAYBACK_CONTROL_SETTINGS); CheckBoxPreference gaplessPlaybackEnabled = - (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK); + findPreference(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK); if (gaplessPlaybackEnabled != null) { gaplessPlaybackEnabled.setChecked(false); @@ -371,15 +408,10 @@ public class SettingsFragment extends PreferenceFragment } private void setupServersCategory() { - final Preference addServerPreference = new Preference(getActivity()); addServerPreference.setPersistent(false); addServerPreference.setTitle(getResources().getString(R.string.settings_server_manage_servers)); addServerPreference.setEnabled(true); - // TODO new server management here - serversCategory.removeAll(); - serversCategory.addPreference(addServerPreference); - addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { @@ -389,7 +421,6 @@ public class SettingsFragment extends PreferenceFragment return true; } }); - } private void update() { @@ -437,15 +468,15 @@ public class SettingsFragment extends PreferenceFragment else showArtistPicture.setEnabled(false); } - private static void setImageLoaderConcurrency(int concurrency) { + private void setImageLoaderConcurrency(int concurrency) { SubsonicTabActivity instance = SubsonicTabActivity.getInstance(); if (instance != null) { - ImageLoader imageLoader = instance.getImageLoader(); + ImageLoader imageLoaderInstance = imageLoader.getValue().getImageLoader(); - if (imageLoader != null) { - imageLoader.stopImageLoader(); - imageLoader.setConcurrency(concurrency); + if (imageLoaderInstance != null) { + imageLoaderInstance.stopImageLoader(); + imageLoaderInstance.setConcurrency(concurrency); } } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SharesFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SharesFragment.java new file mode 100644 index 00000000..773a7aa9 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SharesFragment.java @@ -0,0 +1,388 @@ +package org.moire.ultrasonic.fragment; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException; +import org.moire.ultrasonic.domain.Share; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.service.OfflineException; +import org.moire.ultrasonic.subsonic.DownloadHandler; +import org.moire.ultrasonic.util.BackgroundTask; +import org.moire.ultrasonic.util.CancellationToken; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.LoadingTask; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.TimeSpan; +import org.moire.ultrasonic.util.TimeSpanPicker; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.ShareAdapter; + +import java.util.List; + +import kotlin.Lazy; +import timber.log.Timber; + +import static org.koin.java.KoinJavaComponent.inject; + +public class SharesFragment extends Fragment { + + private SwipeRefreshLayout refreshSharesListView; + private ListView sharesListView; + private View emptyTextView; + private ShareAdapter shareAdapter; + + private final Lazy downloadHandler = inject(DownloadHandler.class); + private CancellationToken cancellationToken; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + Util.applyTheme(this.getContext()); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.select_share, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + cancellationToken = new CancellationToken(); + + refreshSharesListView = view.findViewById(R.id.select_share_refresh); + sharesListView = view.findViewById(R.id.select_share_list); + + refreshSharesListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() + { + @Override + public void onRefresh() + { + new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + emptyTextView = view.findViewById(R.id.select_share_empty); + sharesListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Share share = (Share) parent.getItemAtPosition(position); + + if (share == null) + { + return; + } + + Bundle bundle = new Bundle(); + bundle.putString(Constants.INTENT_EXTRA_NAME_SHARE_ID, share.getId()); + bundle.putString(Constants.INTENT_EXTRA_NAME_SHARE_NAME, share.getName()); + Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle); + } + }); + registerForContextMenu(sharesListView); + + FragmentTitle.Companion.setTitle(this, R.string.button_bar_shares); + + load(); + } + + @Override + public void onDestroyView() { + cancellationToken.cancel(); + super.onDestroyView(); + } + + private void refresh() + { + // TODO: create better restart + getView().post(new Runnable() { + public void run() { + Timber.d("Refresh called..."); + if (getArguments() == null) { + Bundle bundle = new Bundle(); + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + setArguments(bundle); + } else { + getArguments().putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true); + } + onViewCreated(getView(), null); + } + }); + +/* finish(); + Intent intent = new Intent(this, ShareActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + startActivityForResultWithoutTransition(this, intent); + + */ + } + + private void load() + { + BackgroundTask> task = new TabActivityBackgroundTask>(getActivity(), true, refreshSharesListView, cancellationToken) + { + @Override + protected List doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + boolean refresh = getArguments() != null && getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, false); + return musicService.getShares(refresh, getContext(), this); + } + + @Override + protected void done(List result) + { + sharesListView.setAdapter(shareAdapter = new ShareAdapter(getContext(), result)); + emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); + } + }; + task.execute(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, view, menuInfo); + + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.select_share_context, menu); + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) + { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + if (info == null) + { + return false; + } + + Share share = (Share) sharesListView.getItemAtPosition(info.position); + if (share == null) + { + return false; + } + + switch (menuItem.getItemId()) + { + case R.id.share_menu_pin: + downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), true, true, false, false, true, false, false); + break; + case R.id.share_menu_unpin: + downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, false, false, true, false, true); + break; + case R.id.share_menu_download: + downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, false, false, true, false, false); + break; + case R.id.share_menu_play_now: + downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, true, false, false, false, false); + break; + case R.id.share_menu_play_shuffled: + downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, true, true, false, false, false); + break; + case R.id.share_menu_delete: + deleteShare(share); + break; + case R.id.share_info: + displayShareInfo(share); + break; + case R.id.share_update_info: + updateShareInfo(share); + break; + default: + return super.onContextItemSelected(menuItem); + } + return true; + } + + private void deleteShare(final Share share) + { + new AlertDialog.Builder(getContext()).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, share.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + new LoadingTask(getActivity(), false, refreshSharesListView) + { + @Override + protected Void doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + musicService.deleteShare(share.getId(), getContext(), null); + return null; + } + + @Override + protected void done(Void result) + { + shareAdapter.remove(share); + shareAdapter.notifyDataSetChanged(); + Util.toast(getContext(), getResources().getString(R.string.menu_deleted_share, share.getName())); + } + + @Override + protected void error(Throwable error) + { + String msg; + msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_share_error, share.getName()), getErrorMessage(error)); + + Util.toast(getContext(), msg, false); + } + }.execute(); + } + + }).setNegativeButton(R.string.common_cancel, null).show(); + } + + private void displayShareInfo(final Share share) + { + final TextView textView = new TextView(getContext()); + textView.setPadding(5, 5, 5, 5); + + final Spannable message = new SpannableString("Owner: " + share.getUsername() + + "\nComments: " + ((share.getDescription() == null) ? "" : share.getDescription()) + + "\nURL: " + share.getUrl() + + "\nEntry Count: " + share.getEntries().size() + + "\nVisit Count: " + share.getVisitCount() + + ((share.getCreated() == null) ? "" : ("\nCreation Date: " + share.getCreated().replace('T', ' '))) + + ((share.getLastVisited() == null) ? "" : ("\nLast Visited Date: " + share.getLastVisited().replace('T', ' '))) + + ((share.getExpires() == null) ? "" : ("\nExpiration Date: " + share.getExpires().replace('T', ' ')))); + + Linkify.addLinks(message, Linkify.WEB_URLS); + textView.setText(message); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + + new AlertDialog.Builder(getContext()).setTitle("Share Details").setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show(); + } + + private void updateShareInfo(final Share share) + { + View dialogView = getLayoutInflater().inflate(R.layout.share_details, null); + if (dialogView == null) + { + return; + } + + final EditText shareDescription = dialogView.findViewById(R.id.share_description); + final TimeSpanPicker timeSpanPicker = dialogView.findViewById(R.id.date_picker); + + shareDescription.setText(share.getDescription()); + + CheckBox hideDialogCheckBox = dialogView.findViewById(R.id.hide_dialog); + CheckBox saveAsDefaultsCheckBox = dialogView.findViewById(R.id.save_as_defaults); + CheckBox noExpirationCheckBox = dialogView.findViewById(R.id.timeSpanDisableCheckBox); + + noExpirationCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) + { + timeSpanPicker.setEnabled(!b); + } + }); + + noExpirationCheckBox.setChecked(true); + + timeSpanPicker.setTimeSpanDisableText(getResources().getText(R.string.no_expiration)); + + hideDialogCheckBox.setVisibility(View.GONE); + saveAsDefaultsCheckBox.setVisibility(View.GONE); + + AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext()); + + alertDialog.setIcon(android.R.drawable.ic_dialog_alert); + alertDialog.setTitle(R.string.playlist_update_info); + alertDialog.setView(dialogView); + alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + new LoadingTask(getActivity(), false, refreshSharesListView) + { + @Override + protected Void doInBackground() throws Throwable + { + long millis = timeSpanPicker.getTimeSpan().getTotalMilliseconds(); + + if (millis > 0) + { + millis = TimeSpan.getCurrentTime().add(millis).getTotalMilliseconds(); + } + + Editable shareDescriptionText = shareDescription.getText(); + String description = shareDescriptionText != null ? shareDescriptionText.toString() : null; + + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + musicService.updateShare(share.getId(), description, millis, getContext(), null); + return null; + } + + @Override + protected void done(Void result) + { + refresh(); + Util.toast(getContext(), getResources().getString(R.string.playlist_updated_info, share.getName())); + } + + @Override + protected void error(Throwable error) + { + String msg; + msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, share.getName()), getErrorMessage(error)); + + Util.toast(getContext(), msg, false); + } + }.execute(); + } + }); + + alertDialog.setNegativeButton(R.string.common_cancel, null); + alertDialog.show(); + } + + private class GetDataTask extends AsyncTask + { + @Override + protected void onPostExecute(String[] result) + { + super.onPostExecute(result); + } + + @Override + protected String[] doInBackground(Void... params) + { + refresh(); + return null; + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java index 8e5b0925..d273da04 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java @@ -76,6 +76,7 @@ public final class Constants // Preferences keys. public static final String PREFERENCES_KEY_SERVER_INSTANCE = "serverInstanceId"; public static final String PREFERENCES_KEY_SERVERS_KEY = "serversKey"; + public static final String PREFERENCES_KEY_SERVERS_EDIT = "editServers"; public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime"; public static final String PREFERENCES_KEY_THEME = "theme"; public static final String PREFERENCES_KEY_THEME_LIGHT = "light"; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java index 8f86535d..fb9d5f38 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java @@ -24,10 +24,13 @@ import android.graphics.BitmapFactory; import android.os.Build; import android.os.Environment; import android.text.TextUtils; + +import kotlin.Lazy; import timber.log.Timber; import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.subsonic.ImageLoaderProvider; import java.io.File; import java.io.FileInputStream; @@ -43,6 +46,8 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Pattern; +import static org.koin.java.KoinJavaComponent.inject; + /** * @author Sindre Mehus */ @@ -55,6 +60,8 @@ public class FileUtil private static final List PLAYLIST_FILE_EXTENSIONS = Collections.singletonList("m3u"); private static final Pattern TITLE_WITH_TRACK = Pattern.compile("^\\d\\d-.*"); + private static Lazy imageLoaderProvider = inject(ImageLoaderProvider.class); + public static File getSongFile(Context context, MusicDirectory.Entry song) { File dir = getAlbumDirectory(context, song); @@ -154,7 +161,7 @@ public class FileUtil if (subsonicTabActivity != null) { - imageLoader = subsonicTabActivity.getImageLoader(); + imageLoader = imageLoaderProvider.getValue().getImageLoader(); if (imageLoader != null) { @@ -224,7 +231,7 @@ public class FileUtil if (subsonicTabActivity != null) { - imageLoader = subsonicTabActivity.getImageLoader(); + imageLoader = imageLoaderProvider.getValue().getImageLoader(); if (imageLoader != null) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/LoadingTask.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/LoadingTask.java index 2ad49d9f..b1acb058 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/LoadingTask.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/LoadingTask.java @@ -1,10 +1,13 @@ package org.moire.ultrasonic.util; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.ProgressDialog; import android.content.DialogInterface; import android.os.Build; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import org.moire.ultrasonic.activity.SubsonicTabActivity; /** @@ -13,29 +16,23 @@ import org.moire.ultrasonic.activity.SubsonicTabActivity; */ public abstract class LoadingTask extends BackgroundTask { - private final SubsonicTabActivity tabActivity; + private final Activity tabActivity; private final boolean cancellable; private boolean cancelled; + private SwipeRefreshLayout swipe; - public LoadingTask(SubsonicTabActivity activity, final boolean cancellable) + public LoadingTask(Activity activity, final boolean cancellable, SwipeRefreshLayout swipe) { super(activity); tabActivity = activity; this.cancellable = cancellable; + this.swipe = swipe; } @Override public void execute() { - final ProgressDialog loading = ProgressDialog.show(tabActivity, "", "Loading. Please Wait...", true, cancellable, new DialogInterface.OnCancelListener() - { - @Override - public void onCancel(DialogInterface dialog) - { - cancelled = true; - } - - }); + swipe.setRefreshing(true); new Thread() { @@ -55,7 +52,7 @@ public abstract class LoadingTask extends BackgroundTask @Override public void run() { - loading.cancel(); + swipe.setRefreshing(false); done(result); } }); @@ -72,7 +69,7 @@ public abstract class LoadingTask extends BackgroundTask @Override public void run() { - loading.cancel(); + swipe.setRefreshing(false); error(t); } }); @@ -84,7 +81,9 @@ public abstract class LoadingTask extends BackgroundTask @SuppressLint("NewApi") private boolean isCancelled() { - return Build.VERSION.SDK_INT >= 17 ? tabActivity.isDestroyed() || cancelled : cancelled; + // TODO: Implement cancelled + //return Build.VERSION.SDK_INT >= 17 ? tabActivity.isDestroyed() || cancelled : cancelled; + return cancelled; } @Override @@ -95,7 +94,7 @@ public abstract class LoadingTask extends BackgroundTask @Override public void run() { - + // TODO: This seems to be NOOP, can it be removed? } }); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TabActivityBackgroundTask.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TabActivityBackgroundTask.java index 408a3355..861bb5f3 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TabActivityBackgroundTask.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TabActivityBackgroundTask.java @@ -1,6 +1,8 @@ package org.moire.ultrasonic.util; -import org.moire.ultrasonic.activity.SubsonicTabActivity; +import android.app.Activity; + +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; /** * @author Sindre Mehus @@ -9,14 +11,25 @@ import org.moire.ultrasonic.activity.SubsonicTabActivity; public abstract class TabActivityBackgroundTask extends BackgroundTask { - private final SubsonicTabActivity tabActivity; private final boolean changeProgress; + private final SwipeRefreshLayout swipe; + private CancellationToken cancel; - public TabActivityBackgroundTask(SubsonicTabActivity activity, boolean changeProgress) + // TODO: Try to remove this constructor + public TabActivityBackgroundTask(Activity activity, boolean changeProgress) { super(activity); - tabActivity = activity; this.changeProgress = changeProgress; + this.swipe = null; + } + + public TabActivityBackgroundTask(Activity activity, boolean changeProgress, + SwipeRefreshLayout swipe, CancellationToken cancel) + { + super(activity); + this.changeProgress = changeProgress; + this.swipe = swipe; + this.cancel = cancel; } @Override @@ -24,7 +37,7 @@ public abstract class TabActivityBackgroundTask extends BackgroundTask { if (changeProgress) { - tabActivity.setProgressVisible(true); + if (swipe != null) swipe.setRefreshing(true); } new Thread() @@ -35,7 +48,7 @@ public abstract class TabActivityBackgroundTask extends BackgroundTask try { final T result = doInBackground(); - if (isCancelled()) + if (cancel.isCancellationRequested()) { return; } @@ -47,7 +60,7 @@ public abstract class TabActivityBackgroundTask extends BackgroundTask { if (changeProgress) { - tabActivity.setProgressVisible(false); + if (swipe != null) swipe.setRefreshing(false); } done(result); @@ -56,7 +69,7 @@ public abstract class TabActivityBackgroundTask extends BackgroundTask } catch (final Throwable t) { - if (isCancelled()) + if (cancel.isCancellationRequested()) { return; } @@ -67,7 +80,7 @@ public abstract class TabActivityBackgroundTask extends BackgroundTask { if (changeProgress) { - tabActivity.setProgressVisible(false); + if (swipe != null) swipe.setRefreshing(false); } error(t); @@ -78,20 +91,16 @@ public abstract class TabActivityBackgroundTask extends BackgroundTask }.start(); } - private boolean isCancelled() - { - return tabActivity.getIsDestroyed(); - } - @Override public void updateProgress(final String message) { + // TODO: Remove getHandler().post(new Runnable() { @Override public void run() { - tabActivity.updateProgress(message); + //activity.updateProgress(message); } }); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreference.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreference.java index 7f1840b3..cd98946a 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreference.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreference.java @@ -1,10 +1,11 @@ package org.moire.ultrasonic.util; import android.content.Context; -import android.preference.DialogPreference; import android.util.AttributeSet; import android.view.View; +import androidx.preference.DialogPreference; + import org.moire.ultrasonic.R; import java.util.regex.Pattern; @@ -14,9 +15,7 @@ import java.util.regex.Pattern; */ public class TimeSpanPreference extends DialogPreference { - private static final Pattern COMPILE = Pattern.compile(":"); Context context; - TimeSpanPicker picker; public TimeSpanPreference(Context context, AttributeSet attrs) { @@ -27,6 +26,7 @@ public class TimeSpanPreference extends DialogPreference setNegativeButtonText(android.R.string.cancel); setDialogIcon(null); + } public String getText() @@ -40,59 +40,4 @@ public class TimeSpanPreference extends DialogPreference return this.context.getResources().getString(R.string.time_span_disabled); } - - @Override - public View onCreateDialogView() - { - picker = new TimeSpanPicker(this.context); - picker.setTimeSpanDisableText(this.context.getResources().getString(R.string.no_expiration)); - - String persisted = getPersistedString(""); - - if (!"".equals(persisted)) - { - String[] split = COMPILE.split(persisted); - - if (split.length == 2) - { - String amount = split[0]; - - if ("0".equals(amount) || "".equals(amount)) - { - picker.setTimeSpanDisableCheckboxChecked(true); - } - - picker.setTimeSpanAmount(amount); - picker.setTimeSpanType(split[1]); - } - } - else - { - picker.setTimeSpanDisableCheckboxChecked(true); - } - - return picker; - } - - @Override - protected void onDialogClosed(boolean positiveResult) - { - super.onDialogClosed(positiveResult); - - String persisted = ""; - - if (picker.getTimeSpanEnabled()) - { - int tsAmount = picker.getTimeSpanAmount(); - - if (tsAmount > 0) - { - String tsType = picker.getTimeSpanType(); - - persisted = String.format("%d:%s", tsAmount, tsType); - } - } - - persistString(persisted); - } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreferenceDialogFragmentCompat.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreferenceDialogFragmentCompat.java new file mode 100644 index 00000000..79507b5c --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeSpanPreferenceDialogFragmentCompat.java @@ -0,0 +1,88 @@ +package org.moire.ultrasonic.util; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.DialogPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceDialogFragmentCompat; + +import org.moire.ultrasonic.R; + +import java.util.regex.Pattern; + +/** + * Created by Joshua Bahnsen on 12/22/13. + */ +public class TimeSpanPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements DialogPreference.TargetFragment +{ + private static final Pattern COMPILE = Pattern.compile(":"); + Context context; + TimeSpanPicker picker; + + @Override + protected View onCreateDialogView(Context context) { + picker = new TimeSpanPicker(context); + this.context = context; + + picker.setTimeSpanDisableText(this.context.getResources().getString(R.string.no_expiration)); + + Preference preference = getPreference(); + String persisted = preference.getSharedPreferences().getString(preference.getKey(), ""); + + if (!"".equals(persisted)) + { + String[] split = COMPILE.split(persisted); + + if (split.length == 2) + { + String amount = split[0]; + + if ("0".equals(amount) || "".equals(amount)) + { + picker.setTimeSpanDisableCheckboxChecked(true); + } + + picker.setTimeSpanAmount(amount); + picker.setTimeSpanType(split[1]); + } + } + else + { + picker.setTimeSpanDisableCheckboxChecked(true); + } + + return picker; + } + + + @Override + public void onDialogClosed(boolean positiveResult) + { + String persisted = ""; + + if (picker.getTimeSpanEnabled()) + { + int tsAmount = picker.getTimeSpanAmount(); + + if (tsAmount > 0) + { + String tsType = picker.getTimeSpanType(); + + persisted = String.format("%d:%s", tsAmount, tsType); + } + } + + Preference preference = getPreference(); + preference.getSharedPreferences().edit().putString(preference.getKey(), persisted).apply(); + } + + @Nullable + @Override + public Preference findPreference(@NonNull CharSequence key) { + return getPreference(); + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java index 75c96683..a7c6492c 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -53,7 +53,6 @@ import androidx.annotation.ColorInt; import org.moire.ultrasonic.R; import org.moire.ultrasonic.activity.DownloadActivity; import org.moire.ultrasonic.activity.MainActivity; -import org.moire.ultrasonic.activity.SettingsActivity; import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.*; import org.moire.ultrasonic.domain.MusicDirectory.Entry; @@ -604,19 +603,6 @@ public class Util showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId); } - public static void showWelcomeDialog(final Context context, final MainActivity activity, int titleId, int messageId) - { - new AlertDialog.Builder(context).setIcon(android.R.drawable.ic_dialog_info).setTitle(titleId).setMessage(messageId).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int i) - { - dialog.dismiss(); - activity.startActivityForResultWithoutTransition(activity, SettingsActivity.class); - } - }).show(); - } - private static void showDialog(Context context, int icon, int titleId, int messageId) { new AlertDialog.Builder(context).setIcon(icon).setTitle(titleId).setMessage(messageId).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java index a03036a4..ea3e26dc 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java @@ -20,6 +20,7 @@ package org.moire.ultrasonic.util; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; @@ -38,27 +39,27 @@ public enum VideoPlayerType MX("mx") { @Override - public void playVideo(final Activity activity, MusicDirectory.Entry entry) throws Exception + public void playVideo(final Context context, MusicDirectory.Entry entry) throws Exception { // Check if MX Player is installed. - boolean installedAd = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_AD); - boolean installedPro = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_PRO); + boolean installedAd = Util.isPackageInstalled(context, PACKAGE_NAME_MX_AD); + boolean installedPro = Util.isPackageInstalled(context, PACKAGE_NAME_MX_PRO); if (!installedAd && !installedPro) { - new AlertDialog.Builder(activity).setMessage(R.string.video_get_mx_player_text).setPositiveButton(R.string.video_get_mx_player_button, new DialogInterface.OnClickListener() + new AlertDialog.Builder(context).setMessage(R.string.video_get_mx_player_text).setPositiveButton(R.string.video_get_mx_player_button, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int i) { try { - activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("market://details?id=%s", PACKAGE_NAME_MX_AD)))); + context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("market://details?id=%s", PACKAGE_NAME_MX_AD)))); } catch (android.content.ActivityNotFoundException x) { - activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://play.google.com/store/apps/details?id=%s", PACKAGE_NAME_MX_AD)))); + context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://play.google.com/store/apps/details?id=%s", PACKAGE_NAME_MX_AD)))); } dialog.dismiss(); @@ -79,8 +80,8 @@ public enum VideoPlayerType Intent intent = new Intent(Intent.ACTION_VIEW); intent.setPackage(installedPro ? PACKAGE_NAME_MX_PRO : PACKAGE_NAME_MX_AD); intent.putExtra("title", entry.getTitle()); - intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(activity).getVideoUrl(activity, entry.getId(), false)), "video/*"); - activity.startActivity(intent); + intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoUrl(context, entry.getId(), false)), "video/*"); + context.startActivity(intent); } } }, @@ -88,22 +89,22 @@ public enum VideoPlayerType FLASH("flash") { @Override - public void playVideo(Activity activity, MusicDirectory.Entry entry) throws Exception + public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(MusicServiceFactory.getMusicService(activity).getVideoUrl(activity, entry.getId(), true))); - activity.startActivity(intent); + intent.setData(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoUrl(context, entry.getId(), true))); + context.startActivity(intent); } }, DEFAULT("default") { @Override - public void playVideo(Activity activity, MusicDirectory.Entry entry) throws Exception + public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(activity).getVideoUrl(activity, entry.getId(), false)), "video/*"); - activity.startActivity(intent); + intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoUrl(context, entry.getId(), false)), "video/*"); + context.startActivity(intent); } }; @@ -131,7 +132,7 @@ public enum VideoPlayerType return null; } - public abstract void playVideo(Activity activity, MusicDirectory.Entry entry) throws Exception; + public abstract void playVideo(Context context, MusicDirectory.Entry entry) throws Exception; private static final String PACKAGE_NAME_MX_AD = "com.mxtech.videoplayer.ad"; private static final String PACKAGE_NAME_MX_PRO = "com.mxtech.videoplayer.pro"; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java index 235dccda..c84974b6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java @@ -1,5 +1,6 @@ package org.moire.ultrasonic.view; +import android.content.Context; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.view.LayoutInflater; @@ -9,11 +10,10 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import org.moire.ultrasonic.R; -import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.ChatMessage; +import org.moire.ultrasonic.subsonic.ImageLoaderProvider; import org.moire.ultrasonic.util.ImageLoader; -import org.moire.ultrasonic.util.Util; import java.text.DateFormat; import java.util.Date; @@ -26,18 +26,19 @@ import static org.koin.java.KoinJavaComponent.inject; public class ChatAdapter extends ArrayAdapter { - private final SubsonicTabActivity activity; + private final Context context; private List messages; private static final String phoneRegex = "1?\\W*([2-9][0-8][0-9])\\W*([2-9][0-9]{2})\\W*([0-9]{4})"; private static final Pattern phoneMatcher = Pattern.compile(phoneRegex); private Lazy activeServerProvider = inject(ActiveServerProvider.class); + private Lazy imageLoader = inject(ImageLoaderProvider.class); - public ChatAdapter(SubsonicTabActivity activity, List messages) + public ChatAdapter(Context context, List messages) { - super(activity, R.layout.chat_item, messages); - this.activity = activity; + super(context, R.layout.chat_item, messages); + this.context = context; this.messages = messages; } @@ -91,14 +92,14 @@ public class ChatAdapter extends ArrayAdapter holder.chatMessage = message; - DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(activity); + DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); String messageTimeFormatted = String.format("[%s]", timeFormat.format(messageTime)); - ImageLoader imageLoader = activity.getImageLoader(); + ImageLoader imageLoaderInstance = imageLoader.getValue().getImageLoader(); - if (imageLoader != null) + if (imageLoaderInstance != null) { - imageLoader.loadAvatarImage(holder.avatar, messageUser, false, holder.avatar.getWidth(), false, true); + imageLoaderInstance.loadAvatarImage(holder.avatar, messageUser, false, holder.avatar.getWidth(), false, true); } holder.username.setText(messageUser); @@ -110,7 +111,7 @@ public class ChatAdapter extends ArrayAdapter private View inflateView(int layout, ViewGroup parent) { - return LayoutInflater.from(activity).inflate(layout, parent, false); + return LayoutInflater.from(context).inflate(layout, parent, false); } private static ViewHolder createViewHolder(int layout, View convertView) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java index 2edcbfe5..5faf4eac 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java @@ -18,6 +18,7 @@ */ package org.moire.ultrasonic.view; +import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; @@ -25,7 +26,7 @@ import android.widget.CheckedTextView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import org.moire.ultrasonic.activity.SubsonicTabActivity; + import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.util.ImageLoader; @@ -36,15 +37,15 @@ import java.util.List; */ public class EntryAdapter extends ArrayAdapter { - private final SubsonicTabActivity activity; + private final Context context; private final ImageLoader imageLoader; private final boolean checkable; - public EntryAdapter(SubsonicTabActivity activity, ImageLoader imageLoader, List entries, boolean checkable) + public EntryAdapter(Context context, ImageLoader imageLoader, List entries, boolean checkable) { - super(activity, android.R.layout.simple_list_item_1, entries); + super(context, android.R.layout.simple_list_item_1, entries); - this.activity = activity; + this.context = context; this.imageLoader = imageLoader; this.checkable = checkable; } @@ -75,7 +76,7 @@ public class EntryAdapter extends ArrayAdapter } else { - view = new AlbumView(activity, imageLoader); + view = new AlbumView(context, imageLoader); view.setLayout(); } @@ -104,7 +105,7 @@ public class EntryAdapter extends ArrayAdapter } else { - view = new SongView(activity); + view = new SongView(context); view.setLayout(entry); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/PlaylistAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PlaylistAdapter.java index 39d6bbce..57286135 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/PlaylistAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PlaylistAdapter.java @@ -1,12 +1,12 @@ package org.moire.ultrasonic.view; +import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; import org.moire.ultrasonic.R; -import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.domain.Playlist; import java.io.Serializable; @@ -20,12 +20,12 @@ import java.util.List; public class PlaylistAdapter extends ArrayAdapter { - private final SubsonicTabActivity activity; + private final Context context; - public PlaylistAdapter(SubsonicTabActivity activity, List Playlists) + public PlaylistAdapter(Context context, List Playlists) { - super(activity, R.layout.playlist_list_item, Playlists); - this.activity = activity; + super(context, R.layout.playlist_list_item, Playlists); + this.context = context; } @Override @@ -44,7 +44,7 @@ public class PlaylistAdapter extends ArrayAdapter } else { - view = new PlaylistView(activity); + view = new PlaylistView(context); view.setLayout(); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ShareAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ShareAdapter.java index b1ae9ca7..52a77ead 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ShareAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ShareAdapter.java @@ -1,12 +1,12 @@ package org.moire.ultrasonic.view; +import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; import org.moire.ultrasonic.R; -import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.domain.Share; import java.io.Serializable; @@ -20,12 +20,12 @@ import java.util.List; public class ShareAdapter extends ArrayAdapter { - private final SubsonicTabActivity activity; + private final Context context; - public ShareAdapter(SubsonicTabActivity activity, List Shares) + public ShareAdapter(Context context, List Shares) { - super(activity, R.layout.share_list_item, Shares); - this.activity = activity; + super(context, R.layout.share_list_item, Shares); + this.context = context; } @Override @@ -44,7 +44,7 @@ public class ShareAdapter extends ArrayAdapter } else { - view = new ShareView(activity); + view = new ShareView(context); view.setLayout(); } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt new file mode 100644 index 00000000..12d4a745 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -0,0 +1,161 @@ +package org.moire.ultrasonic.activity + +import android.app.AlertDialog +import android.content.res.Resources +import android.os.Bundle +import android.preference.PreferenceManager +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.drawerlayout.widget.DrawerLayout +import androidx.navigation.NavController +import androidx.navigation.findNavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.onNavDestinationSelected +import androidx.navigation.ui.setupActionBarWithNavController +import androidx.navigation.ui.setupWithNavController +import com.google.android.material.navigation.NavigationView +import org.koin.android.ext.android.inject +import org.koin.android.viewmodel.ext.android.viewModel +import org.moire.ultrasonic.R +import org.moire.ultrasonic.service.MediaPlayerController +import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport +import org.moire.ultrasonic.subsonic.ImageLoaderProvider +import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.FileUtil +import org.moire.ultrasonic.util.Util +import timber.log.Timber + + +/** + * A simple activity demonstrating use of a NavHostFragment with a navigation drawer. + */ +class NavigationActivity : AppCompatActivity() { + private lateinit var appBarConfiguration : AppBarConfiguration + private val serverSettingsModel: ServerSettingsModel by viewModel() + private val lifecycleSupport: MediaPlayerLifecycleSupport by inject() + private val mediaPlayerController: MediaPlayerController by inject() + private val imageLoaderProvider: ImageLoaderProvider by inject() + + private var infoDialogDisplayed = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.navigation_activity) + + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + val host: NavHostFragment = supportFragmentManager + .findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return + + val navController = host.navController + + val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout) + appBarConfiguration = AppBarConfiguration( + setOf(R.id.mainFragment, R.id.selectArtistFragment, R.id.searchFragment, + R.id.playlistsFragment, R.id.sharesFragment, R.id.bookmarksFragment, + R.id.chatFragment, R.id.podcastFragment, R.id.settingsFragment, + R.id.aboutFragment), + drawerLayout) + + setupActionBar(navController, appBarConfiguration) + + setupNavigationMenu(navController) + + navController.addOnDestinationChangedListener { _, destination, _ -> + val dest: String = try { + resources.getResourceName(destination.id) + } catch (e: Resources.NotFoundException) { + Integer.toString(destination.id) + } + Timber.d("Navigated to $dest") + } + + // Determine first run and migrate server settings to DB as early as possible + var showWelcomeScreen = Util.isFirstRun(this) + val areServersMigrated: Boolean = serverSettingsModel.migrateFromPreferences() + + // If there are any servers in the DB, do not show the welcome screen + showWelcomeScreen = showWelcomeScreen and !areServersMigrated + + loadSettings() + showInfoDialog(showWelcomeScreen) + } + + private fun setupNavigationMenu(navController: NavController) { + val sideNavView = findViewById(R.id.nav_view) + sideNavView?.setupWithNavController(navController) + + // The exit menu is handled here manually + val exitItem: MenuItem = sideNavView.menu.findItem(R.id.menu_exit) + exitItem.setOnMenuItemClickListener { item -> + if (item.itemId == R.id.menu_exit) { + setResult(Constants.RESULT_CLOSE_ALL) + mediaPlayerController.stopJukeboxService() + imageLoaderProvider.getImageLoader().stopImageLoader() + finish() + exit() + } + true + } + } + + private fun setupActionBar(navController: NavController, appBarConfig: AppBarConfiguration) { + setupActionBarWithNavController(navController, appBarConfig) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val retValue = super.onCreateOptionsMenu(menu) + val navigationView = findViewById(R.id.nav_view) + if (navigationView == null) { + menuInflater.inflate(R.menu.navigation, menu) + return true + } + return retValue + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return item.onNavDestinationSelected(findNavController(R.id.nav_host_fragment)) + || super.onOptionsItemSelected(item) + } + + override fun onSupportNavigateUp(): Boolean { + return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration) + } + + private fun loadSettings() { + PreferenceManager.setDefaultValues(this, R.xml.settings, false) + val preferences = Util.getPreferences(this) + if (!preferences.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION)) { + val editor = preferences.edit() + editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(this).path) + editor.apply() + } + } + + private fun exit() { + lifecycleSupport.onDestroy() + Util.unregisterMediaButtonEventReceiver(this, false) + finish() + } + + private fun showInfoDialog(show: Boolean) { + if (!infoDialogDisplayed) { + infoDialogDisplayed = true + if (show) { + AlertDialog.Builder(this) + .setIcon(android.R.drawable.ic_dialog_info) + .setTitle(R.string.main_welcome_title) + .setMessage(R.string.main_welcome_text) + .setPositiveButton(R.string.common_ok) { dialog, i -> + dialog.dismiss() + findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment) + }.show() + } + } + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt index 07a6fdf8..2b7ce542 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt @@ -9,6 +9,7 @@ import org.moire.ultrasonic.activity.ServerSettingsModel import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.AppDatabase import org.moire.ultrasonic.data.MIGRATION_1_2 +import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.util.Util const val SP_NAME = "Default_SP" @@ -32,4 +33,6 @@ val appPermanentStorage = module { viewModel { ServerSettingsModel(get(), get(), androidContext()) } single { ActiveServerProvider(get(), androidContext()) } + + single { ImageLoaderProvider(androidContext()) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 42ac9b6b..153b8efc 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -20,6 +20,10 @@ import org.moire.ultrasonic.service.CachedMusicService import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.OfflineMusicService import org.moire.ultrasonic.service.RESTMusicService +import org.moire.ultrasonic.subsonic.DownloadHandler +import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker +import org.moire.ultrasonic.subsonic.ShareHandler +import org.moire.ultrasonic.subsonic.VideoPlayer import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader import org.moire.ultrasonic.util.Constants @@ -75,4 +79,9 @@ val musicServiceModule = module { single { SubsonicImageLoader(androidContext(), get()) } viewModel { ArtistListModel(get(), androidContext()) } + + single { DownloadHandler(get(), get()) } + single { NetworkAndStorageChecker(androidContext()) } + single { VideoPlayer(androidContext()) } + single { ShareHandler(androidContext()) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadFragment.kt new file mode 100644 index 00000000..bec1e973 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadFragment.kt @@ -0,0 +1,6 @@ +package org.moire.ultrasonic.fragment + +import androidx.fragment.app.Fragment + +class DownloadFragment: Fragment() { +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/FragmentTitle.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/FragmentTitle.kt new file mode 100644 index 00000000..3a1159ef --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/FragmentTitle.kt @@ -0,0 +1,32 @@ +package org.moire.ultrasonic.fragment + +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment + +class FragmentTitle { + companion object { + fun setTitle(fragment: Fragment, title: CharSequence?) { + (fragment.activity as AppCompatActivity).supportActionBar?.setTitle(title) + } + + fun setTitle(fragment: Fragment, id: Int) { + (fragment.activity as AppCompatActivity).supportActionBar?.setTitle(id) + } + + fun getTitle(fragment: Fragment): CharSequence? { + return (fragment.activity as AppCompatActivity).supportActionBar?.title + } + + fun setSubtitle(fragment: Fragment, title: CharSequence?) { + (fragment.activity as AppCompatActivity).supportActionBar?.setSubtitle(title) + } + + fun setSubtitle(fragment: Fragment, id: Int) { + (fragment.activity as AppCompatActivity).supportActionBar?.setSubtitle(id) + } + + fun getSubtitle(fragment: Fragment): CharSequence? { + return (fragment.activity as AppCompatActivity).supportActionBar?.subtitle + } + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/SelectArtistActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectArtistFragment.kt similarity index 58% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/SelectArtistActivity.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectArtistFragment.kt index 87a4c4c8..ed5bde28 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/SelectArtistActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectArtistFragment.kt @@ -1,49 +1,38 @@ -/* - This file is part of Subsonic. +package org.moire.ultrasonic.fragment - 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 2020 (C) Jozsef Varga - */ -package org.moire.ultrasonic.activity - -import android.content.Intent import android.os.Bundle +import android.view.LayoutInflater import android.view.MenuItem import android.view.View +import android.view.ViewGroup import android.widget.PopupMenu +import androidx.fragment.app.Fragment import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import org.koin.android.ext.android.inject import org.koin.android.viewmodel.ext.android.viewModel import org.moire.ultrasonic.R +import org.moire.ultrasonic.activity.ArtistListModel +import org.moire.ultrasonic.activity.ArtistRowAdapter +import org.moire.ultrasonic.activity.ServerSettingsModel import org.moire.ultrasonic.data.ActiveServerProvider -import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.MusicFolder +import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle +import org.moire.ultrasonic.subsonic.DownloadHandler +import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Util -/** - * Displays the available Artists in a list - */ -class SelectArtistActivity : SubsonicTabActivity() { +class SelectArtistFragment : Fragment() { private val activeServerProvider: ActiveServerProvider by inject() private val serverSettingsModel: ServerSettingsModel by viewModel() private val artistListModel: ArtistListModel by viewModel() + private val imageLoaderProvider: ImageLoaderProvider by inject() + private val downloadHandler: DownloadHandler by inject() private var refreshArtistListView: SwipeRefreshLayout? = null private var artistListView: RecyclerView? = null @@ -54,35 +43,44 @@ class SelectArtistActivity : SubsonicTabActivity() { /** * Called when the activity is first created. */ - public override fun onCreate(savedInstanceState: Bundle?) { + @Override + override fun onCreate(savedInstanceState: Bundle?) { + Util.applyTheme(this.context) super.onCreate(savedInstanceState) - setContentView(R.layout.select_artist) + } - refreshArtistListView = findViewById(R.id.select_artist_refresh) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.select_artist, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + refreshArtistListView = view.findViewById(R.id.select_artist_refresh) refreshArtistListView!!.setOnRefreshListener { artistListModel.refresh(refreshArtistListView!!) } - val shouldShowHeader = (!isOffline(this) && !Util.getShouldUseId3Tags(this)) + val shouldShowHeader = (!ActiveServerProvider.isOffline(this.context) && !Util.getShouldUseId3Tags(this.context)) + + val title = arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE) - val title = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE) if (title == null) { - setActionBarSubtitle( - if (isOffline(this)) R.string.music_library_label_offline + setTitle( + this, + if (ActiveServerProvider.isOffline(this.context)) R.string.music_library_label_offline else R.string.music_library_label ) } else { - actionBarSubtitle = title + setTitle(this, title) } - val browseMenuItem = findViewById(R.id.menu_browse) - menuDrawer.setActiveView(browseMenuItem) + musicFolders = null - val refresh = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false) + val refresh = arguments?.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) ?: false artistListModel.getMusicFolders() .observe( - this, + viewLifecycleOwner, Observer { changedFolders -> if (changedFolders != null) { musicFolders = changedFolders @@ -93,41 +91,26 @@ class SelectArtistActivity : SubsonicTabActivity() { val artists = artistListModel.getArtists(refresh, refreshArtistListView!!) artists.observe( - this, Observer { changedArtists -> viewAdapter.setData(changedArtists) } + viewLifecycleOwner, Observer { changedArtists -> viewAdapter.setData(changedArtists) } ) - viewManager = LinearLayoutManager(this) + viewManager = LinearLayoutManager(this.context) viewAdapter = ArtistRowAdapter( artists.value ?: listOf(), getText(R.string.select_artist_all_folders).toString(), shouldShowHeader, { artist -> onItemClick(artist) }, { menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) }, - { view -> onFolderClick(view) }, - imageLoader + { onFolderClick(it) }, + imageLoaderProvider.getImageLoader() ) - artistListView = findViewById(R.id.select_artist_list).apply { + artistListView = view.findViewById(R.id.select_artist_list).apply { setHasFixedSize(true) layoutManager = viewManager adapter = viewAdapter } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - menuDrawer.toggleMenu() - return true - } - R.id.main_shuffle -> { - val intent = Intent(this, DownloadActivity::class.java) - intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true) - startActivityForResultWithoutTransition(this, intent) - return true - } - } - return false + super.onViewCreated(view, savedInstanceState) } private fun getMusicFolderName(musicFolders: List): String { @@ -143,16 +126,16 @@ class SelectArtistActivity : SubsonicTabActivity() { } private fun onItemClick(artist: Artist) { - val intent = Intent(this, SelectAlbumActivity::class.java) - intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, artist.id) - intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, artist.name) - intent.putExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID, artist.id) - intent.putExtra(Constants.INTENT_EXTRA_NAME_ARTIST, true) - startActivityForResultWithoutTransition(this, intent) + val bundle = Bundle() + bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.id) + bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.name) + bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, artist.id) + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true) + findNavController().navigate(R.id.selectArtistToSelectAlbum, bundle) } private fun onFolderClick(view: View) { - val popup = PopupMenu(this, view) + val popup = PopupMenu(this.context, view) val musicFolderId = activeServerProvider.getActiveServer().musicFolderId var menuItem = popup.menu.add( @@ -179,17 +162,17 @@ class SelectArtistActivity : SubsonicTabActivity() { private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean { when (menuItem.itemId) { R.id.artist_menu_play_now -> - downloadRecursively(artist.id, false, false, true, false, false, false, false, true) + downloadHandler.downloadRecursively(this, artist.id, false, false, true, false, false, false, false, true) R.id.artist_menu_play_next -> - downloadRecursively(artist.id, false, false, true, true, false, true, false, true) + downloadHandler.downloadRecursively(this, artist.id, false, false, true, true, false, true, false, true) R.id.artist_menu_play_last -> - downloadRecursively(artist.id, false, true, false, false, false, false, false, true) + downloadHandler.downloadRecursively(this, artist.id, false, true, false, false, false, false, false, true) R.id.artist_menu_pin -> - downloadRecursively(artist.id, true, true, false, false, false, false, false, true) + downloadHandler.downloadRecursively(this, artist.id, true, true, false, false, false, false, false, true) R.id.artist_menu_unpin -> - downloadRecursively(artist.id, false, false, false, false, false, false, true, true) + downloadHandler.downloadRecursively(this, artist.id, false, false, false, false, false, false, true, true) R.id.artist_menu_download -> - downloadRecursively(artist.id, false, false, false, false, true, false, false, true) + downloadHandler.downloadRecursively(this, artist.id, false, false, false, false, true, false, false, true) } return true } @@ -199,7 +182,7 @@ class SelectArtistActivity : SubsonicTabActivity() { val musicFolderId = selectedFolder?.id val musicFolderName = selectedFolder?.name ?: getString(R.string.select_artist_all_folders) - if (!isOffline(this)) { + if (!ActiveServerProvider.isOffline(this.context)) { val currentSetting = activeServerProvider.getActiveServer() currentSetting.musicFolderId = musicFolderId serverSettingsModel.updateItem(currentSetting) @@ -212,4 +195,4 @@ class SelectArtistActivity : SubsonicTabActivity() { companion object { private const val MENU_GROUP_MUSIC_FOLDER = 10 } -} +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt new file mode 100644 index 00000000..2df99798 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -0,0 +1,210 @@ +package org.moire.ultrasonic.subsonic + +import android.app.Activity +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import org.moire.ultrasonic.R +import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.service.MediaPlayerController +import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService +import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator +import org.moire.ultrasonic.util.ModalBackgroundTask +import org.moire.ultrasonic.util.Util +import java.util.* + +class DownloadHandler( + val mediaPlayerController: MediaPlayerController, + val networkAndStorageChecker: NetworkAndStorageChecker +) { + private val MAX_SONGS = 500 + + fun download(fragment: Fragment, append: Boolean, save: Boolean, autoPlay: Boolean, playNext: Boolean, shuffle: Boolean, songs: List) { + val onValid = Runnable { + if (!append && !playNext) { + mediaPlayerController.clear() + } + networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() + mediaPlayerController.download(songs, save, autoPlay, playNext, shuffle, false) + val playlistName: String? = fragment.arguments?.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME) + if (playlistName != null) { + mediaPlayerController.suggestedPlaylistName = playlistName + } + if (autoPlay) { + if (Util.getShouldTransitionOnPlaybackPreference(fragment.activity)) { + fragment.findNavController().popBackStack(R.id.downloadFragment, true) + fragment.findNavController().navigate(R.id.downloadFragment) + //startActivityForResultWithoutTransition(this@SubsonicTabActivity, DownloadActivity::class.java) + } + } else if (save) { + Util.toast(fragment.context, fragment.resources.getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size, songs.size)) + } else if (playNext) { + Util.toast(fragment.context, fragment.resources.getQuantityString(R.plurals.select_album_n_songs_play_next, songs.size, songs.size)) + } else if (append) { + Util.toast(fragment.context, fragment.resources.getQuantityString(R.plurals.select_album_n_songs_added, songs.size, songs.size)) + } + } + onValid.run() + } + + fun downloadPlaylist(fragment: Fragment, id: String, name: String?, save: Boolean, append: Boolean, autoplay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean) { + downloadRecursively(fragment, id, name, false, false, save, append, autoplay, shuffle, background, playNext, unpin, false) + } + + fun downloadShare(fragment: Fragment, id: String, name: String?, save: Boolean, append: Boolean, autoplay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean) { + downloadRecursively(fragment, id, name, true, false, save, append, autoplay, shuffle, background, playNext, unpin, false) + } + + fun downloadRecursively( + fragment: Fragment, + id: String?, + save: Boolean, + append: Boolean, + autoPlay: Boolean, + shuffle: Boolean, + background: Boolean, + playNext: Boolean, + unpin: Boolean, + isArtist: Boolean + ) { + if (id.isNullOrEmpty()) return + downloadRecursively( + fragment, + id, + "", + isShare = false, + isDirectory = true, + save = save, + append = append, + autoPlay = autoPlay, + shuffle = shuffle, + background = background, + playNext = playNext, + unpin = unpin, + isArtist = isArtist) + } + + fun downloadRecursively( + fragment: Fragment, + id: String, + name: String?, + isShare: Boolean, + isDirectory: Boolean, + save: Boolean, + append: Boolean, + autoPlay: Boolean, + shuffle: Boolean, + background: Boolean, + playNext: Boolean, + unpin: Boolean, + isArtist: Boolean + ) { + val activity = fragment.activity as Activity + val task = object: ModalBackgroundTask>( + activity, + false + ) { + + @Throws(Throwable::class) + override fun doInBackground(): List { + // TODO: Handle swipe spinner here instead of the ProgressListener + val musicService = getMusicService(activity) + val songs: MutableList = LinkedList() + val root: MusicDirectory + if (!isOffline(activity) && isArtist && Util.getShouldUseId3Tags(activity)) { + getSongsForArtist(id, songs) + } else { + if (isDirectory) { + root = if (!isOffline(activity) && Util.getShouldUseId3Tags(activity)) + musicService.getAlbum(id, name, false, activity, null) + else + musicService.getMusicDirectory(id, name, false, activity, null) + } else if (isShare) { + root = MusicDirectory() + val shares = musicService.getShares(true, activity, null) + for (share in shares) { + if (share.id == id) { + for (entry in share.getEntries()) { + root.addChild(entry) + } + break + } + } + } else { + root = musicService.getPlaylist(id, name, activity, null) + } + getSongsRecursively(root, songs) + } + return songs + } + + @Throws(Exception::class) + private fun getSongsRecursively(parent: MusicDirectory, songs: MutableList) { + if (songs.size > MAX_SONGS) { + return + } + for (song in parent.getChildren(false, true)) { + if (!song.isVideo) { + songs.add(song) + } + } + val musicService = getMusicService(activity) + for ((id1, _, _, title) in parent.getChildren(true, false)) { + var root: MusicDirectory + root = if (!isOffline(activity) && Util.getShouldUseId3Tags(activity)) musicService.getAlbum(id1, title, false, activity, null) + else musicService.getMusicDirectory(id1, title, false, activity, null) + getSongsRecursively(root, songs) + } + } + + @Throws(Exception::class) + private fun getSongsForArtist(id: String, songs: MutableCollection) { + if (songs.size > MAX_SONGS) { + return + } + val musicService = getMusicService(activity) + val artist = musicService.getArtist(id, "", false, activity, null) + for ((id1) in artist.getChildren()) { + val albumDirectory = musicService.getAlbum(id1, "", false, activity, null) + for (song in albumDirectory.getChildren()) { + if (!song.isVideo) { + songs.add(song) + } + } + } + } + + override fun done(songs: List) { + if (Util.getShouldSortByDisc(activity)) { + Collections.sort(songs, EntryByDiscAndTrackComparator()) + } + if (songs.isNotEmpty()) { + if (!append && !playNext && !unpin && !background) { + mediaPlayerController.clear() + } + networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() + if (!background) { + if (unpin) { + mediaPlayerController.unpin(songs) + } else { + mediaPlayerController.download(songs, save, autoPlay, playNext, shuffle, false) + if (!append && Util.getShouldTransitionOnPlaybackPreference(activity)) { + fragment.findNavController().popBackStack(R.id.downloadFragment, true) + fragment.findNavController().navigate(R.id.downloadFragment) + //startActivityForResultWithoutTransition(activity, DownloadActivity::class.java) + } + } + } else { + if (unpin) { + mediaPlayerController.unpin(songs) + } else { + mediaPlayerController.downloadBackground(songs, save) + } + } + } + } + } + task.execute() + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt new file mode 100644 index 00000000..b3c28e23 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt @@ -0,0 +1,45 @@ +package org.moire.ultrasonic.subsonic + +import android.content.Context +import org.koin.java.KoinJavaComponent.get +import org.moire.ultrasonic.featureflags.Feature +import org.moire.ultrasonic.featureflags.FeatureStorage +import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader +import org.moire.ultrasonic.util.ImageLoader +import org.moire.ultrasonic.util.LegacyImageLoader +import org.moire.ultrasonic.util.Util + +class ImageLoaderProvider (val context: Context) { + private var imageLoader: ImageLoader? = null + + @Synchronized + fun clearImageLoader() { + if (imageLoader != null && + imageLoader!!.isRunning) { + imageLoader!!.clear() + } + imageLoader = null + } + + @Synchronized + fun getImageLoader(): ImageLoader { + if (imageLoader == null || !imageLoader!!.isRunning) { + val legacyImageLoader = LegacyImageLoader( + context, + Util.getImageLoaderConcurrency(context) + ) + val isNewImageLoaderEnabled = get(FeatureStorage::class.java) + .isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER) + if (isNewImageLoaderEnabled) { + imageLoader = SubsonicImageLoaderProxy( + legacyImageLoader, + get(SubsonicImageLoader::class.java) + ) + } else { + imageLoader = legacyImageLoader + } + imageLoader!!.startImageLoader() + } + return imageLoader!! + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt new file mode 100644 index 00000000..6845e9d4 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt @@ -0,0 +1,16 @@ +package org.moire.ultrasonic.subsonic + +import android.content.Context +import org.moire.ultrasonic.R +import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline +import org.moire.ultrasonic.util.Util + +class NetworkAndStorageChecker(val context: Context) { + fun warnIfNetworkOrStorageUnavailable() { + if (!Util.isExternalStoragePresent()) { + Util.toast(context, R.string.select_album_no_sdcard) + } else if (!isOffline(context) && !Util.isNetworkConnected(context)) { + Util.toast(context, R.string.select_album_no_network) + } + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt new file mode 100644 index 00000000..09dfdd35 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt @@ -0,0 +1,139 @@ +package org.moire.ultrasonic.subsonic + +import android.app.AlertDialog +import android.content.Context +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import android.widget.CheckBox +import android.widget.EditText +import androidx.fragment.app.Fragment +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import org.moire.ultrasonic.R +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.domain.Share +import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService +import org.moire.ultrasonic.util.BackgroundTask +import org.moire.ultrasonic.util.CancellationToken +import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.ShareDetails +import org.moire.ultrasonic.util.TabActivityBackgroundTask +import org.moire.ultrasonic.util.TimeSpan +import org.moire.ultrasonic.util.TimeSpanPicker +import org.moire.ultrasonic.util.Util +import java.util.* +import java.util.regex.Pattern + +class ShareHandler(val context: Context) { + private var shareDescription: EditText? = null + private var timeSpanPicker: TimeSpanPicker? = null + private var hideDialogCheckBox: CheckBox? = null + private var noExpirationCheckBox: CheckBox? = null + private var saveAsDefaultsCheckBox: CheckBox? = null + private val pattern = Pattern.compile(":") + + fun createShare(fragment: Fragment, entries: List?, swipe: SwipeRefreshLayout, cancellationToken: CancellationToken) { + val askForDetails = Util.getShouldAskForShareDetails(context) + val shareDetails = ShareDetails() + shareDetails.Entries = entries + if (askForDetails) { + showDialog(fragment, shareDetails, swipe, cancellationToken) + } else { + shareDetails.Description = Util.getDefaultShareDescription(context) + shareDetails.Expiration = TimeSpan.getCurrentTime().add(Util.getDefaultShareExpirationInMillis(context)).totalMilliseconds + share(fragment, shareDetails, swipe, cancellationToken) + } + } + + fun share(fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout, cancellationToken: CancellationToken) { + val task: BackgroundTask = object : TabActivityBackgroundTask(fragment.requireActivity(), true, swipe, cancellationToken) { + @Throws(Throwable::class) + override fun doInBackground(): Share { + val ids: MutableList = ArrayList() + if (shareDetails.Entries.isEmpty()) { + ids.add(fragment.arguments?.getString(Constants.INTENT_EXTRA_NAME_ID)) + } else { + for ((id) in shareDetails.Entries) { + ids.add(id) + } + } + val musicService = getMusicService(context) + var timeInMillis: Long = 0 + if (shareDetails.Expiration != 0L) { + timeInMillis = shareDetails.Expiration + } + val shares = musicService.createShare(ids, shareDetails.Description, timeInMillis, context, this) + return shares[0] + } + + override fun done(result: Share) { + val intent = Intent(Intent.ACTION_SEND) + intent.type = "text/plain" + intent.putExtra(Intent.EXTRA_TEXT, String.format("%s\n\n%s", Util.getShareGreeting(context), result.url)) + fragment.activity?.startActivity(Intent.createChooser(intent, context.getResources().getString(R.string.share_via))) + } + } + task.execute() + } + + private fun showDialog(fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout, cancellationToken: CancellationToken) { + val layout = LayoutInflater.from(fragment.context).inflate(R.layout.share_details, null) + + if (layout != null) { + shareDescription = layout.findViewById(R.id.share_description) as EditText + hideDialogCheckBox = layout.findViewById(R.id.hide_dialog) as CheckBox + noExpirationCheckBox = layout.findViewById(R.id.timeSpanDisableCheckBox) as CheckBox + saveAsDefaultsCheckBox = layout.findViewById(R.id.save_as_defaults) as CheckBox + timeSpanPicker = layout.findViewById(R.id.date_picker) as TimeSpanPicker + } + val builder = AlertDialog.Builder(fragment.context) + builder.setTitle(R.string.share_set_share_options) + builder.setPositiveButton(R.string.common_save) { dialog, clickId -> + if (!noExpirationCheckBox!!.isChecked) { + val timeSpan: TimeSpan = timeSpanPicker!!.timeSpan + val now = TimeSpan.getCurrentTime() + shareDetails.Expiration = now.add(timeSpan).totalMilliseconds + } + shareDetails.Description = shareDescription!!.text.toString() + if (hideDialogCheckBox!!.isChecked) { + Util.setShouldAskForShareDetails(context, false) + } + if (saveAsDefaultsCheckBox!!.isChecked) { + val timeSpanType: String = timeSpanPicker!!.timeSpanType + val timeSpanAmount: Int = timeSpanPicker!!.timeSpanAmount + Util.setDefaultShareExpiration(context, if (!noExpirationCheckBox!!.isChecked && timeSpanAmount > 0) String.format("%d:%s", timeSpanAmount, timeSpanType) else "") + Util.setDefaultShareDescription(context, shareDetails.Description) + } + share(fragment, shareDetails, swipe, cancellationToken) + } + builder.setNegativeButton(R.string.common_cancel) { dialog, clickId -> + dialog.cancel() + } + builder.setView(layout) + builder.setCancelable(true) + timeSpanPicker!!.setTimeSpanDisableText(context.resources.getString(R.string.no_expiration)) + noExpirationCheckBox!!.setOnCheckedChangeListener { _, b -> timeSpanPicker!!.isEnabled = !b } + val defaultDescription = Util.getDefaultShareDescription(context) + val timeSpan = Util.getDefaultShareExpiration(context) + val split = pattern.split(timeSpan) + if (split.size == 2) { + val timeSpanAmount = split[0].toInt() + val timeSpanType = split[1] + if (timeSpanAmount > 0) { + noExpirationCheckBox!!.isChecked = false + timeSpanPicker!!.isEnabled = true + timeSpanPicker!!.setTimeSpanAmount(timeSpanAmount.toString()) + timeSpanPicker!!.setTimeSpanType(timeSpanType) + } else { + noExpirationCheckBox!!.isChecked = true + timeSpanPicker!!.isEnabled = false + } + } else { + noExpirationCheckBox!!.isChecked = true + timeSpanPicker!!.isEnabled = false + } + shareDescription!!.setText(defaultDescription) + builder.create() + builder.show() + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt new file mode 100644 index 00000000..bc3c5724 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt @@ -0,0 +1,21 @@ +package org.moire.ultrasonic.subsonic + +import android.content.Context +import org.moire.ultrasonic.R +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.util.Util + +class VideoPlayer(val context: Context) { + fun playVideo(entry: MusicDirectory.Entry?) { + if (!Util.isNetworkConnected(context)) { + Util.toast(context, R.string.select_album_no_network) + return + } + val player = Util.getVideoPlayerType(context) + try { + player.playVideo(context, entry) + } catch (e: Exception) { + Util.toast(context, e.message, false) + } + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CancellationToken.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CancellationToken.kt new file mode 100644 index 00000000..df791784 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CancellationToken.kt @@ -0,0 +1,33 @@ +/* + 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 2021 (C) Jozsef Varga + */ +package org.moire.ultrasonic.util + +/** + * This class contains a very simple implementation of a CancellationToken + */ +class CancellationToken { + var isCancellationRequested: Boolean = false + + /** + * Requests that this token be cancelled + */ + fun cancel() { + isCancellationRequested = true; + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/help.xml b/ultrasonic/src/main/res/layout/help.xml index 33c7e55d..b06b9b00 100644 --- a/ultrasonic/src/main/res/layout/help.xml +++ b/ultrasonic/src/main/res/layout/help.xml @@ -29,12 +29,18 @@ - + a:layout_below="@id/help_buttons"> + + + diff --git a/ultrasonic/src/main/res/layout/navigation_activity.xml b/ultrasonic/src/main/res/layout/navigation_activity.xml new file mode 100644 index 00000000..114f6b21 --- /dev/null +++ b/ultrasonic/src/main/res/layout/navigation_activity.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/navigation_header.xml b/ultrasonic/src/main/res/layout/navigation_header.xml new file mode 100644 index 00000000..0fa1c393 --- /dev/null +++ b/ultrasonic/src/main/res/layout/navigation_header.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/search.xml b/ultrasonic/src/main/res/layout/search.xml index e78c4cb4..e0378fa5 100644 --- a/ultrasonic/src/main/res/layout/search.xml +++ b/ultrasonic/src/main/res/layout/search.xml @@ -4,7 +4,11 @@ a:layout_width="fill_parent" a:layout_height="fill_parent"> - + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/select_album.xml b/ultrasonic/src/main/res/layout/select_album.xml index 5a037202..b2f27f13 100644 --- a/ultrasonic/src/main/res/layout/select_album.xml +++ b/ultrasonic/src/main/res/layout/select_album.xml @@ -9,8 +9,6 @@ a:layout_height="1dp" a:background="@color/dividerColor" /> - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/menu/search.xml b/ultrasonic/src/main/res/menu/search.xml new file mode 100644 index 00000000..83f06a03 --- /dev/null +++ b/ultrasonic/src/main/res/menu/search.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/navigation/navigation_graph.xml b/ultrasonic/src/main/res/navigation/navigation_graph.xml new file mode 100644 index 00000000..e928b056 --- /dev/null +++ b/ultrasonic/src/main/res/navigation/navigation_graph.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/values/styles.xml b/ultrasonic/src/main/res/values/styles.xml index 65012f6d..317c433e 100644 --- a/ultrasonic/src/main/res/values/styles.xml +++ b/ultrasonic/src/main/res/values/styles.xml @@ -9,6 +9,11 @@ true + + + + diff --git a/ultrasonic/src/main/res/xml/settings.xml b/ultrasonic/src/main/res/xml/settings.xml index 2f451ad2..d6edcc0d 100644 --- a/ultrasonic/src/main/res/xml/settings.xml +++ b/ultrasonic/src/main/res/xml/settings.xml @@ -1,327 +1,404 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" a:title="@string/common.appname"> + a:title="@string/settings.servers_title" + app:iconSpaceReserved="false"> + - + + a:title="@string/settings.theme_title" + app:iconSpaceReserved="false"/> + a:title="@string/settings.server_scaling_title" + app:iconSpaceReserved="false"/> + a:title="@string/settings.display_bitrate" + app:iconSpaceReserved="false"/> + a:title="@string/settings.use_folder_for_album_artist" + app:iconSpaceReserved="false"/> + a:title="@string/settings.show_all_songs_by_artist" + app:iconSpaceReserved="false"/> + a:title="@string/settings.show_track_number" + app:iconSpaceReserved="false"/> + a:title="@string/settings.disc_sort" + app:iconSpaceReserved="false"/> + a:title="@string/settings.view_refresh" + app:iconSpaceReserved="false"/> + a:title="@string/settings.image_loader_concurrency" + app:iconSpaceReserved="false"/> + a:key="playbackControlSettings" + app:iconSpaceReserved="false"> + a:title="@string/settings.use_id3" + app:iconSpaceReserved="false"/> + a:title="@string/settings.show_artist_picture" + app:iconSpaceReserved="false"/> + a:title="@string/settings.media_button_title" + app:iconSpaceReserved="false"/> + a:title="@string/settings.download_transition" + app:iconSpaceReserved="false"/> + a:title="@string/settings.gapless_playback" + app:iconSpaceReserved="false"/> + a:title="@string/settings.clear_playlist" + app:iconSpaceReserved="false"/> + a:title="@string/settings.clear_bookmark" + app:iconSpaceReserved="false"/> + a:title="@string/settings.scan_media" + app:iconSpaceReserved="false"/> + a:title="@string/settings.increment_time" + app:iconSpaceReserved="false"/> + a:summary="@string/settings.playback.resume_play_on_headphones_plug.summary" + app:iconSpaceReserved="false"/> + a:title="@string/settings.playback.resume_on_bluetooth_device" + app:iconSpaceReserved="false"/> + a:title="@string/settings.playback.pause_on_bluetooth_device" + app:iconSpaceReserved="false"/> + a:title="@string/settings.playback.single_button_bluetooth_device" + app:iconSpaceReserved="false"/> + a:key="notificationsCategory" + app:iconSpaceReserved="false"> + a:title="@string/settings.show_now_playing" + app:iconSpaceReserved="false"/> + a:title="@string/settings.show_notification" + app:iconSpaceReserved="false"/> + a:title="@string/settings.show_notification_always" + app:iconSpaceReserved="false"/> + a:title="@string/settings.show_lockscreen_controls" + app:iconSpaceReserved="false"/> + a:title="@string/settings.send_bluetooth_notification" + app:iconSpaceReserved="false"/> + a:title="@string/settings.send_bluetooth_album_art" + app:iconSpaceReserved="false"/> - + + a:title="@string/settings.video_player" + app:iconSpaceReserved="false"/> - + + a:title="@string/settings.share_description_default" + app:iconSpaceReserved="false"/> + a:title="@string/settings.share_greeting_default" + app:iconSpaceReserved="false"/> + a:title="@string/settings.share_expiration_default" + app:iconSpaceReserved="false"/> + a:title="@string/settings.sharing_always_ask_for_details" + app:iconSpaceReserved="false"/> - + + a:title="@string/settings.max_bitrate_wifi" + app:iconSpaceReserved="false"/> + a:title="@string/settings.max_bitrate_mobile" + app:iconSpaceReserved="false"/> + a:title="@string/settings.wifi_required_title" + app:iconSpaceReserved="false"/> + a:title="@string/settings.buffer_length" + app:iconSpaceReserved="false"/> + a:title="@string/settings.network_timeout" + app:iconSpaceReserved="false"/> + a:title="@string/settings.chat_refresh" + app:iconSpaceReserved="false"/> - + + a:title="@string/settings.cache_size" + app:iconSpaceReserved="false"/> + a:title="@string/settings.cache_location" + app:iconSpaceReserved="false"/> + a:title="@string/settings.preload" + app:iconSpaceReserved="false"/> + a:title="@string/settings.directory_cache_time" + app:iconSpaceReserved="false"/> - + + a:title="@string/settings.default_artists" + app:iconSpaceReserved="false"/> + a:title="@string/settings.max_artists" + app:iconSpaceReserved="false"/> + a:title="@string/settings.default_albums" + app:iconSpaceReserved="false"/> + a:title="@string/settings.max_albums" + app:iconSpaceReserved="false"/> + a:title="@string/settings.default_songs" + app:iconSpaceReserved="false"/> - + a:title="@string/settings.max_songs" + app:iconSpaceReserved="false"/> + a:title="@string/settings.clear_search_history" + app:iconSpaceReserved="false"/> - + + a:title="@string/settings.scrobble_title" + app:iconSpaceReserved="false"/> + a:title="@string/settings.hide_media_title" + app:iconSpaceReserved="false"/> + a:title="@string/settings.screen_lit_title" + app:iconSpaceReserved="false"/> - + + app:iconSpaceReserved="false"/> + app:iconSpaceReserved="false"/> - + + app:iconSpaceReserved="false"/> \ No newline at end of file