diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt
index f5820cb7..b56dc097 100644
--- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt
+++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt
@@ -1,10 +1,8 @@
package org.moire.ultrasonic.api.subsonic.di
-import org.koin.dsl.context.ModuleDefinition
+import org.koin.dsl.module
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
-const val SUBSONIC_API_CLIENT_CONTEXT = "SubsonicApiClientContext"
-
-fun ModuleDefinition.subsonicApiModule() = module(SUBSONIC_API_CLIENT_CONTEXT) {
+val subsonicApiModule = module {
single { SubsonicAPIClient(get(), get()) }
}
diff --git a/dependencies.gradle b/dependencies.gradle
index 0aa4162d..66193268 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -12,17 +12,20 @@ ext.versions = [
androidSupport : "28.0.0",
androidLegacySupport : "1.0.0",
- androidSupportDesign : "1.0.0",
+ androidSupportDesign : "1.2.1",
+ constraintLayout : "2.0.1",
multidex : "2.0.1",
-
+ room : "2.2.5",
kotlin : "1.3.72",
+ kotlinxCoroutines : "1.3.9",
+ viewModelKtx : "2.2.0",
retrofit : "2.4.0",
jackson : "2.9.5",
okhttp : "3.10.0",
semver : "1.0.0",
twitterSerial : "0.1.6",
- koin : "1.0.0-beta-3",
+ koin : "2.1.6",
picasso : "2.71828",
junit4 : "4.12",
@@ -45,15 +48,21 @@ ext.gradlePlugins = [
]
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",
+ 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"
]
ext.other = [
kotlinStdlib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin",
kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin",
+ kotlinxCoroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.kotlinxCoroutines",
retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit",
gsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit",
jacksonConverter : "com.squareup.retrofit2:converter-jackson:$versions.retrofit",
@@ -62,8 +71,8 @@ ext.other = [
semver : "net.swiftzer.semver:semver:$versions.semver",
twitterSerial : "com.twitter.serial:serial:$versions.twitterSerial",
koinCore : "org.koin:koin-core:$versions.koin",
- koinJava : "org.koin:koin-java:$versions.koin",
koinAndroid : "org.koin:koin-android:$versions.koin",
+ koinViewModel : "org.koin:koin-android-viewmodel:$versions.koin",
picasso : "com.squareup.picasso:picasso:$versions.picasso",
dexter : "com.karumi:dexter:$versions.dexter",
]
diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle
index b0e8b248..390566c7 100644
--- a/ultrasonic/build.gradle
+++ b/ultrasonic/build.gradle
@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco'
apply from: "../gradle_scripts/code_quality.gradle"
@@ -65,10 +66,17 @@ dependencies {
implementation androidSupport.support
implementation androidSupport.design
implementation androidSupport.multidex
+ implementation androidSupport.roomRuntime
+ implementation androidSupport.roomKtx
+ implementation androidSupport.viewModelKtx
+ implementation androidSupport.constraintLayout
implementation other.kotlinStdlib
+ implementation other.kotlinxCoroutines
implementation other.koinAndroid
- implementation other.koinJava
+ implementation other.koinViewModel
+
+ kapt androidSupport.room
testImplementation other.kotlinReflect
testImplementation testing.junit
diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml
index 93d6ce3e..14db75f5 100644
--- a/ultrasonic/src/main/AndroidManifest.xml
+++ b/ultrasonic/src/main/AndroidManifest.xml
@@ -115,7 +115,12 @@
android:resource="@xml/searchable"/>
-
+
+
pinnedCount) ? 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 && !Util.isOffline(this) ? View.VISIBLE : View.GONE);
+ downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(this) ? View.VISIBLE : View.GONE);
deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE);
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java
index e91fc007..83a20677 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ChatActivity.java
@@ -19,7 +19,9 @@ 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;
@@ -33,6 +35,10 @@ 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
*/
@@ -44,6 +50,7 @@ public final class ChatActivity extends SubsonicTabActivity
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)
@@ -68,8 +75,8 @@ public final class ChatActivity extends SubsonicTabActivity
chatListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
chatListView.setStackFromBottom(true);
- String serverName = Util.getServerName(this, Util.getActiveServer(this));
- String userName = Util.getUserName(this, Util.getActiveServer(this));
+ 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);
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 4b6369a9..65fb14b0 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java
@@ -51,8 +51,9 @@ import android.widget.ViewFlipper;
import com.mobeta.android.dslv.DragSortListView;
-import org.koin.java.standalone.KoinJavaComponent;
+import org.koin.java.KoinJavaComponent;
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.domain.PlayerState;
@@ -743,7 +744,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
MenuItem bookmarkRemoveMenuItem = menu.findItem(R.id.menu_item_bookmark_delete);
- if (Util.isOffline(this))
+ if (ActiveServerProvider.Companion.isOffline(this))
{
if (shareMenuItem != null)
{
@@ -870,7 +871,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
}
}
- if (Util.isOffline(this) || !Util.getShouldUseId3Tags(this))
+ if (ActiveServerProvider.Companion.isOffline(this) || !Util.getShouldUseId3Tags(this))
{
MenuItem menuItem = menu.findItem(R.id.menu_show_artist);
@@ -880,7 +881,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
}
}
- if (Util.isOffline(this))
+ if (ActiveServerProvider.Companion.isOffline(this))
{
MenuItem menuItem = menu.findItem(R.id.menu_lyrics);
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java
index 07b48a96..51c39876 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java
@@ -39,7 +39,7 @@ import java.util.Map;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* Equalizer controls.
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java
index a664899f..d2732909 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/HelpActivity.java
@@ -36,6 +36,7 @@ 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;
@@ -155,7 +156,7 @@ public final class HelpActivity extends ResultActivity implements OnClickListene
{
super.onPostCreate(bundle);
- int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE;
+ int visibility = ActiveServerProvider.Companion.isOffline(this) ? View.GONE : View.VISIBLE;
chatMenuItem.setVisibility(visibility);
bookmarksMenuItem.setVisibility(visibility);
sharesMenuItem.setVisibility(visibility);
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 66297dba..6b5a4e21 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java
@@ -23,7 +23,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -34,7 +33,7 @@ import android.widget.ListView;
import android.widget.TextView;
import org.moire.ultrasonic.R;
-import org.moire.ultrasonic.service.MediaPlayerController;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
@@ -49,28 +48,18 @@ import java.util.Collections;
import kotlin.Lazy;
import static java.util.Arrays.asList;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.android.viewmodel.compat.ViewModelCompat.viewModel;
+import static org.koin.java.KoinJavaComponent.inject;
public class MainActivity extends SubsonicTabActivity
{
-
- private static final int MENU_GROUP_SERVER = 10;
- private static final int MENU_ITEM_OFFLINE = 111;
- private static final int MENU_ITEM_SERVER_1 = 101;
- private static final int MENU_ITEM_SERVER_2 = 102;
- private static final int MENU_ITEM_SERVER_3 = 103;
- private static final int MENU_ITEM_SERVER_4 = 104;
- private static final int MENU_ITEM_SERVER_5 = 105;
- private static final int MENU_ITEM_SERVER_6 = 106;
- private static final int MENU_ITEM_SERVER_7 = 107;
- private static final int MENU_ITEM_SERVER_8 = 108;
- private static final int MENU_ITEM_SERVER_9 = 109;
- private static final int MENU_ITEM_SERVER_10 = 110;
-
private static boolean infoDialogDisplayed;
private static boolean shouldUseId3;
+ private static int lastActiveServer;
private Lazy lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
+ private Lazy activeServerProvider = inject(ActiveServerProvider.class);
+ private Lazy serverSettingsModel = viewModel(this, ServerSettingsModel.class);
/**
* Called when the activity is first created.
@@ -80,6 +69,13 @@ 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);
@@ -105,7 +101,7 @@ public class MainActivity extends SubsonicTabActivity
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 = (TextView) serverButton.findViewById(R.id.main_select_server_2);
+ 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);
@@ -124,35 +120,18 @@ public class MainActivity extends SubsonicTabActivity
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 View dummyView = findViewById(R.id.main_dummy);
- boolean shouldShowDialog = false;
-
- if (!getActiveServerEnabled())
- {
- shouldShowDialog = true;
- Util.setActiveServer(this, 0);
- }
-
- int instance = Util.getActiveServer(this);
- String name = Util.getServerName(this, instance);
-
- if (name == null)
- {
- shouldShowDialog = true;
- Util.setActiveServer(this, 0);
- instance = Util.getActiveServer(this);
- name = Util.getServerName(this, instance);
- }
+ lastActiveServer = ActiveServerProvider.Companion.getActiveServerId(this);
+ String name = activeServerProvider.getValue().getActiveServer().getName();
serverTextView.setText(name);
- final ListView list = (ListView) findViewById(R.id.main_list);
+ final ListView list = findViewById(R.id.main_list);
final MergeAdapter adapter = new MergeAdapter();
adapter.addViews(Collections.singletonList(serverButton), true);
- if (!Util.isOffline(this))
+ if (!ActiveServerProvider.Companion.isOffline(this))
{
adapter.addView(musicTitle, false);
adapter.addViews(asList(artistsButton, albumsButton, genresButton), true);
@@ -180,7 +159,6 @@ public class MainActivity extends SubsonicTabActivity
}
list.setAdapter(adapter);
- registerForContextMenu(dummyView);
list.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@@ -189,7 +167,7 @@ public class MainActivity extends SubsonicTabActivity
{
if (view == serverButton)
{
- dummyView.showContextMenu();
+ showServers();
}
else if (view == albumsNewestButton)
{
@@ -259,7 +237,7 @@ public class MainActivity extends SubsonicTabActivity
// Remember the current theme.
theme = Util.getTheme(this);
- showInfoDialog(shouldShowDialog);
+ showInfoDialog(showWelcomeScreen);
}
private void loadSettings()
@@ -279,14 +257,24 @@ public class MainActivity extends SubsonicTabActivity
protected void onResume()
{
super.onResume();
+ boolean shouldRestart = false;
boolean id3 = Util.getShouldUseId3Tags(MainActivity.this);
+ int currentActiveServer = ActiveServerProvider.Companion.getActiveServerId(MainActivity.this);
if (id3 != shouldUseId3)
{
shouldUseId3 = id3;
- restart();
+ shouldRestart = true;
}
+
+ if (currentActiveServer != lastActiveServer)
+ {
+ lastActiveServer = currentActiveServer;
+ shouldRestart = true;
+ }
+
+ if (shouldRestart) restart();
}
@Override
@@ -299,148 +287,6 @@ public class MainActivity extends SubsonicTabActivity
return true;
}
- @Override
- public void onCreateContextMenu(final ContextMenu menu, final View view, final ContextMenu.ContextMenuInfo menuInfo)
- {
- super.onCreateContextMenu(menu, view, menuInfo);
-
- final int activeServer = Util.getActiveServer(this);
- boolean checked = false;
-
- for (int i = 0; i <= Util.getActiveServers(this); i++)
- {
- final String serverName = Util.getServerName(this, i);
-
- if (serverName == null)
- {
- continue;
- }
-
- if (Util.getServerEnabled(this, i))
- {
- final int menuItemNum = getMenuItem(i);
-
- final MenuItem menuItem = menu.add(MENU_GROUP_SERVER, menuItemNum, menuItemNum, serverName);
-
- if (activeServer == i)
- {
- checked = true;
- menuItem.setChecked(true);
- }
- }
- }
-
- if (!checked)
- {
- MenuItem menuItem = menu.findItem(getMenuItem(0));
-
- if (menuItem != null)
- {
- menuItem.setChecked(true);
- }
- }
-
- menu.setGroupCheckable(MENU_GROUP_SERVER, true, true);
- menu.setHeaderTitle(R.string.main_select_server);
- }
-
- private boolean getActiveServerEnabled()
- {
- final int activeServer = Util.getActiveServer(this);
- boolean activeServerEnabled = false;
-
- for (int i = 0; i <= Util.getActiveServers(this); i++)
- {
- if (Util.getServerEnabled(this, i))
- {
- if (activeServer == i)
- {
- activeServerEnabled = true;
- }
- }
- }
-
- return activeServerEnabled;
- }
-
- private static int getMenuItem(final int serverInstance)
- {
- switch (serverInstance)
- {
- case 0:
- return MENU_ITEM_OFFLINE;
- case 1:
- return MENU_ITEM_SERVER_1;
- case 2:
- return MENU_ITEM_SERVER_2;
- case 3:
- return MENU_ITEM_SERVER_3;
- case 4:
- return MENU_ITEM_SERVER_4;
- case 5:
- return MENU_ITEM_SERVER_5;
- case 6:
- return MENU_ITEM_SERVER_6;
- case 7:
- return MENU_ITEM_SERVER_7;
- case 8:
- return MENU_ITEM_SERVER_8;
- case 9:
- return MENU_ITEM_SERVER_9;
- case 10:
- return MENU_ITEM_SERVER_10;
- }
-
- return 0;
- }
-
- @Override
- public boolean onContextItemSelected(final MenuItem menuItem)
- {
- switch (menuItem.getItemId())
- {
- case MENU_ITEM_OFFLINE:
- setActiveServer(0);
- break;
- case MENU_ITEM_SERVER_1:
- setActiveServer(1);
- break;
- case MENU_ITEM_SERVER_2:
- setActiveServer(2);
- break;
- case MENU_ITEM_SERVER_3:
- setActiveServer(3);
- break;
- case MENU_ITEM_SERVER_4:
- setActiveServer(4);
- break;
- case MENU_ITEM_SERVER_5:
- setActiveServer(5);
- break;
- case MENU_ITEM_SERVER_6:
- setActiveServer(6);
- break;
- case MENU_ITEM_SERVER_7:
- setActiveServer(7);
- break;
- case MENU_ITEM_SERVER_8:
- setActiveServer(8);
- break;
- case MENU_ITEM_SERVER_9:
- setActiveServer(9);
- break;
- case MENU_ITEM_SERVER_10:
- setActiveServer(10);
- break;
- default:
- return super.onContextItemSelected(menuItem);
- }
-
- // Restart activity
- restart();
- return true;
- }
-
@Override
public boolean onOptionsItemSelected(final MenuItem item)
{
@@ -459,26 +305,6 @@ public class MainActivity extends SubsonicTabActivity
return false;
}
- private void setActiveServer(final int instance)
- {
- final MediaPlayerController service = getMediaPlayerController();
-
- if (Util.getActiveServer(this) != instance)
- {
- if (service != null)
- {
- service.clearIncomplete();
- }
- }
-
- Util.setActiveServer(this, instance);
-
- if (service != null)
- {
- service.setJukeboxEnabled(Util.getJukeboxEnabled(this, instance));
- }
- }
-
private void exit()
{
lifecycleSupport.getValue().onDestroy();
@@ -492,9 +318,9 @@ public class MainActivity extends SubsonicTabActivity
{
infoDialogDisplayed = true;
- if (show || Util.getRestUrl(this, null).contains("yourhost"))
+ if (show)
{
- Util.showWelcomeDialog(this, this, R.string.main_welcome_title, R.string.main_welcome_text);
+ Util.showWelcomeDialog(this, this, R.string.main_welcome_title, R.string.main_welcome_text_new);
}
}
}
@@ -546,7 +372,13 @@ public class MainActivity extends SubsonicTabActivity
startActivityForResultWithoutTransition(this, intent);
}
- /**
+ private void showServers()
+ {
+ final Intent intent = new Intent(this, ServerSelectorActivity.class);
+ startActivityForResult(intent, 0);
+ }
+
+ /**
* Temporary task to make a ping to server to get it supported api version.
*/
private static class PingTask extends TabActivityBackgroundTask {
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 88b7a70a..11bf4078 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java
@@ -32,6 +32,7 @@ import android.widget.ListView;
import android.widget.TextView;
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.MusicDirectory.Entry;
@@ -214,10 +215,10 @@ public class SearchActivity extends SubsonicTabActivity
if (downloadMenuItem != null)
{
- downloadMenuItem.setVisible(!Util.isOffline(this));
+ downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this));
}
- if (Util.isOffline(this) || isArtist)
+ if (ActiveServerProvider.Companion.isOffline(this) || isArtist)
{
if (shareButton != null)
{
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 769e7e0c..b146794f 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java
@@ -35,6 +35,7 @@ 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.MusicDirectory;
import org.moire.ultrasonic.domain.Share;
import org.moire.ultrasonic.service.DownloadFile;
@@ -298,7 +299,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity
}
else
{
- if (!Util.isOffline(SelectAlbumActivity.this) && Util.getShouldUseId3Tags(SelectAlbumActivity.this))
+ if (!ActiveServerProvider.Companion.isOffline(SelectAlbumActivity.this) && Util.getShouldUseId3Tags(SelectAlbumActivity.this))
{
if (isAlbum)
{
@@ -441,14 +442,14 @@ public class SelectAlbumActivity extends SubsonicTabActivity
if (shareButton != null)
{
- shareButton.setVisible(!Util.isOffline(this));
+ shareButton.setVisible(!ActiveServerProvider.Companion.isOffline(this));
}
MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download);
if (downloadMenuItem != null)
{
- downloadMenuItem.setVisible(!Util.isOffline(this));
+ downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this));
}
}
@@ -1011,9 +1012,9 @@ public class SelectAlbumActivity extends SubsonicTabActivity
playNowButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
playNextButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
playLastButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
- pinButton.setVisibility((enabled && !Util.isOffline(this) && selection.size() > pinnedCount) ? 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 && !Util.isOffline(this) ? View.VISIBLE : View.GONE);
+ downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(this) ? View.VISIBLE : View.GONE);
deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE);
}
@@ -1208,7 +1209,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity
boolean isAlbumList = getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE);
playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos;
- shareButtonVisible = !Util.isOffline(SelectAlbumActivity.this) && songCount > 0;
+ shareButtonVisible = !ActiveServerProvider.Companion.isOffline(SelectAlbumActivity.this) && songCount > 0;
emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE);
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectArtistActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectArtistActivity.java
index 7b838b22..aa6f66e0 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectArtistActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectArtistActivity.java
@@ -35,6 +35,8 @@ import android.widget.TextView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.moire.ultrasonic.R;
+import org.moire.ultrasonic.data.ActiveServerProvider;
+import org.moire.ultrasonic.data.ServerSetting;
import org.moire.ultrasonic.domain.Artist;
import org.moire.ultrasonic.domain.Indexes;
import org.moire.ultrasonic.domain.MusicFolder;
@@ -49,8 +51,15 @@ import org.moire.ultrasonic.view.ArtistAdapter;
import java.util.ArrayList;
import java.util.List;
+import kotlin.Lazy;
+
+import static org.koin.android.viewmodel.compat.ViewModelCompat.viewModel;
+import static org.koin.java.KoinJavaComponent.inject;
+
public class SelectArtistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener
{
+ private Lazy activeServerProvider = inject(ActiveServerProvider.class);
+ private Lazy serverSettingsModel = viewModel(this, ServerSettingsModel.class);
private static final int MENU_GROUP_MUSIC_FOLDER = 10;
@@ -90,7 +99,7 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
folderName = (TextView) folderButton.findViewById(R.id.select_artist_folder_2);
}
- if (!Util.isOffline(this) && !Util.getShouldUseId3Tags(this))
+ if (!ActiveServerProvider.Companion.isOffline(this) && !Util.getShouldUseId3Tags(this))
{
artistListView.addHeaderView(folderButton);
}
@@ -100,7 +109,7 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
String title = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE);
if (title == null)
{
- setActionBarSubtitle(Util.isOffline(this) ? R.string.music_library_label_offline : R.string.music_library_label);
+ setActionBarSubtitle(ActiveServerProvider.Companion.isOffline(this) ? R.string.music_library_label_offline : R.string.music_library_label);
}
else
{
@@ -146,7 +155,7 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
MusicService musicService = MusicServiceFactory.getMusicService(SelectArtistActivity.this);
- boolean isOffline = Util.isOffline(SelectArtistActivity.this);
+ boolean isOffline = ActiveServerProvider.Companion.isOffline(SelectArtistActivity.this);
boolean useId3Tags = Util.getShouldUseId3Tags(SelectArtistActivity.this);
if (!isOffline && !useId3Tags)
@@ -154,7 +163,7 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
musicFolders = musicService.getMusicFolders(refresh, SelectArtistActivity.this, this);
}
- String musicFolderId = Util.getSelectedMusicFolderId(SelectArtistActivity.this);
+ String musicFolderId = activeServerProvider.getValue().getActiveServer().getMusicFolderId();
return !isOffline && useId3Tags ? musicService.getArtists(refresh, SelectArtistActivity.this, this) : musicService.getIndexes(musicFolderId, refresh, SelectArtistActivity.this, this);
}
@@ -173,8 +182,8 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
// Display selected music folder
if (musicFolders != null)
{
- String musicFolderId = Util.getSelectedMusicFolderId(SelectArtistActivity.this);
- if (musicFolderId == null)
+ String musicFolderId = activeServerProvider.getValue().getActiveServer().getMusicFolderId();
+ if (musicFolderId == null || musicFolderId.equals(""))
{
if (folderName != null)
{
@@ -239,10 +248,10 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
}
else if (info.position == 1)
{
- String musicFolderId = Util.getSelectedMusicFolderId(this);
+ String musicFolderId = activeServerProvider.getValue().getActiveServer().getMusicFolderId();
MenuItem menuItem = menu.add(MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders);
- if (musicFolderId == null)
+ if (musicFolderId == null || musicFolderId.isEmpty())
{
menuItem.setChecked(true);
}
@@ -268,7 +277,7 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
if (downloadMenuItem != null)
{
- downloadMenuItem.setVisible(!Util.isOffline(this));
+ downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this));
}
}
@@ -315,7 +324,13 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
MusicFolder selectedFolder = menuItem.getItemId() == -1 ? null : musicFolders.get(menuItem.getItemId());
String musicFolderId = selectedFolder == null ? null : selectedFolder.getId();
String musicFolderName = selectedFolder == null ? getString(R.string.select_artist_all_folders) : selectedFolder.getName();
- Util.setSelectedMusicFolderId(this, musicFolderId);
+
+ if (!ActiveServerProvider.Companion.isOffline(this)) {
+ ServerSetting currentSetting = activeServerProvider.getValue().getActiveServer();
+ currentSetting.setMusicFolderId(musicFolderId);
+ serverSettingsModel.getValue().updateItem(currentSetting);
+ }
+
folderName.setText(musicFolderName);
refresh();
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java
index eeeccd2d..3d1ada74 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java
@@ -44,6 +44,7 @@ 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;
@@ -123,7 +124,7 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
List playlists = musicService.getPlaylists(refresh, SelectPlaylistActivity.this, this);
- if (!Util.isOffline(SelectPlaylistActivity.this))
+ if (!ActiveServerProvider.Companion.isOffline(SelectPlaylistActivity.this))
new CacheCleaner(SelectPlaylistActivity.this).cleanPlaylists(playlists);
return playlists;
}
@@ -144,14 +145,14 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
super.onCreateContextMenu(menu, view, menuInfo);
MenuInflater inflater = getMenuInflater();
- if (Util.isOffline(this)) inflater.inflate(R.menu.select_playlist_context_offline, menu);
+ 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(!Util.isOffline(this));
+ downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this));
}
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ServerSettingsActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ServerSettingsActivity.java
deleted file mode 100644
index 9b4027e1..00000000
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/ServerSettingsActivity.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.moire.ultrasonic.activity;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.MenuItem;
-
-import org.moire.ultrasonic.R;
-import org.moire.ultrasonic.fragment.ServerSettingsFragment;
-import org.moire.ultrasonic.util.Util;
-
-public class ServerSettingsActivity extends AppCompatActivity {
- public static final String ARG_SERVER_ID = "argServerId";
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- applyTheme();
- super.onCreate(savedInstanceState);
-
- final Bundle extras = getIntent().getExtras();
- if (!extras.containsKey(ARG_SERVER_ID)) {
- finish();
- return;
- }
-
- if (savedInstanceState == null) {
- configureActionBar();
-
- final int serverId = extras.getInt(ARG_SERVER_ID);
- getFragmentManager().beginTransaction()
- .add(android.R.id.content, ServerSettingsFragment.newInstance(serverId))
- .commit();
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- finish();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void applyTheme() {
- String theme = Util.getTheme(this);
-
- if ("dark".equalsIgnoreCase(theme) || "fullscreen".equalsIgnoreCase(theme)) {
- setTheme(R.style.UltraSonicTheme);
- } else if ("light".equalsIgnoreCase(theme) || "fullscreenlight".equalsIgnoreCase(theme)) {
- setTheme(R.style.UltraSonicTheme_Light);
- }
- }
-
- private void configureActionBar() {
- final ActionBar actionBar = getSupportActionBar();
- if (actionBar != null) {
- actionBar.setDisplayShowHomeEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
- }
- }
-}
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 6d790618..e9aee792 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
@@ -37,9 +37,11 @@ import android.view.View.OnTouchListener;
import android.widget.*;
import net.simonvt.menudrawer.MenuDrawer;
import net.simonvt.menudrawer.Position;
-import org.koin.java.standalone.KoinJavaComponent;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
+
+import org.koin.java.KoinJavaComponent;
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.domain.PlayerState;
@@ -145,7 +147,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
super.onPostCreate(bundle);
instance = this;
- int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE;
+ int visibility = ActiveServerProvider.Companion.isOffline(this) ? View.GONE : View.VISIBLE;
chatMenuItem.setVisibility(visibility);
bookmarksMenuItem.setVisibility(visibility);
sharesMenuItem.setVisibility(visibility);
@@ -225,6 +227,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtras(getIntent());
startActivityForResultWithoutTransition(this, intent);
+ Log.d(TAG, "Restarting activity...");
}
@Override
@@ -773,7 +776,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
{
Util.toast(this, R.string.select_album_no_sdcard);
}
- else if (!Util.isOffline(this) && !Util.isNetworkConnected(this))
+ else if (!ActiveServerProvider.Companion.isOffline(this) && !Util.isNetworkConnected(this))
{
Util.toast(this, R.string.select_album_no_network);
}
@@ -892,7 +895,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
List songs = new LinkedList();
MusicDirectory root;
- if (!Util.isOffline(SubsonicTabActivity.this) && isArtist && Util.getShouldUseId3Tags(SubsonicTabActivity.this))
+ if (!ActiveServerProvider.Companion.isOffline(SubsonicTabActivity.this) && isArtist && Util.getShouldUseId3Tags(SubsonicTabActivity.this))
{
getSongsForArtist(id, songs);
}
@@ -900,7 +903,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
{
if (isDirectory)
{
- root = !Util.isOffline(SubsonicTabActivity.this) && Util.getShouldUseId3Tags(SubsonicTabActivity.this) ? musicService.getAlbum(id, name, false, SubsonicTabActivity.this, this) : musicService.getMusicDirectory(id, name, false, SubsonicTabActivity.this, this);
+ root = !ActiveServerProvider.Companion.isOffline(SubsonicTabActivity.this) && Util.getShouldUseId3Tags(SubsonicTabActivity.this) ? musicService.getAlbum(id, name, false, SubsonicTabActivity.this, this) : musicService.getMusicDirectory(id, name, false, SubsonicTabActivity.this, this);
}
else if (isShare)
{
@@ -953,7 +956,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
{
MusicDirectory root;
- root = !Util.isOffline(SubsonicTabActivity.this) && Util.getShouldUseId3Tags(SubsonicTabActivity.this) ? musicService.getAlbum(dir.getId(), dir.getTitle(), false, SubsonicTabActivity.this, this) : musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, SubsonicTabActivity.this, this);
+ root = !ActiveServerProvider.Companion.isOffline(SubsonicTabActivity.this) && Util.getShouldUseId3Tags(SubsonicTabActivity.this) ? musicService.getAlbum(dir.getId(), dir.getTitle(), false, SubsonicTabActivity.this, this) : musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, SubsonicTabActivity.this, this);
getSongsRecursively(root, songs);
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java
deleted file mode 100644
index 58e26778..00000000
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java
+++ /dev/null
@@ -1,321 +0,0 @@
-package org.moire.ultrasonic.fragment;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import androidx.annotation.Nullable;
-import android.util.Log;
-import android.view.View;
-
-import org.moire.ultrasonic.BuildConfig;
-import org.moire.ultrasonic.R;
-import org.moire.ultrasonic.cache.Directories;
-import org.moire.ultrasonic.cache.PermanentFileStorage;
-import org.moire.ultrasonic.service.MusicService;
-import org.moire.ultrasonic.service.MusicServiceFactory;
-import org.moire.ultrasonic.util.Constants;
-import org.moire.ultrasonic.util.ErrorDialog;
-import org.moire.ultrasonic.util.ModalBackgroundTask;
-import org.moire.ultrasonic.util.Util;
-
-import java.net.URL;
-
-/**
- * Settings for Subsonic server.
- */
-public class ServerSettingsFragment extends PreferenceFragment
- implements Preference.OnPreferenceChangeListener,
- Preference.OnPreferenceClickListener {
- private static final String LOG_TAG = ServerSettingsFragment.class.getSimpleName();
- private static final String ARG_SERVER_ID = "serverId";
-
- private EditTextPreference serverNamePref;
- private EditTextPreference serverUrlPref;
- private EditTextPreference serverUsernamePref;
- private EditTextPreference serverPasswordPref;
- private CheckBoxPreference equalizerPref;
- private CheckBoxPreference jukeboxPref;
- private CheckBoxPreference allowSelfSignedCertificatePref;
- private CheckBoxPreference enableLdapUserSupportPref;
- private Preference removeServerPref;
- private Preference testConnectionPref;
-
- private int serverId;
- private SharedPreferences sharedPreferences;
-
- public static ServerSettingsFragment newInstance(final int serverId) {
- final ServerSettingsFragment fragment = new ServerSettingsFragment();
- final Bundle args = new Bundle();
- args.putInt(ARG_SERVER_ID, serverId);
- fragment.setArguments(args);
-
- return fragment;
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- serverId = getArguments().getInt(ARG_SERVER_ID);
- sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
-
- addPreferencesFromResource(R.xml.server_settings);
- }
-
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- serverNamePref = (EditTextPreference) findPreference(getString(R.string.settings_server_name));
- serverUrlPref = (EditTextPreference) findPreference(getString(R.string.settings_server_address));
- serverUsernamePref = (EditTextPreference) findPreference(getString(R.string.settings_server_username));
- serverPasswordPref = (EditTextPreference) findPreference(getString(R.string.settings_server_password));
- equalizerPref = (CheckBoxPreference) findPreference(getString(R.string.equalizer_enabled));
- jukeboxPref = (CheckBoxPreference) findPreference(getString(R.string.jukebox_is_default));
- removeServerPref = findPreference(getString(R.string.settings_server_remove_server));
- testConnectionPref = findPreference(getString(R.string.settings_test_connection_title));
- allowSelfSignedCertificatePref = (CheckBoxPreference) findPreference(
- getString(R.string.settings_allow_self_signed_certificate));
- enableLdapUserSupportPref = (CheckBoxPreference) findPreference(
- getString(R.string.settings_enable_ldap_user_support)
- );
-
- setupPreferencesValues();
- setupPreferencesListeners();
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (preference == serverNamePref) {
- sharedPreferences.edit()
- .putString(Constants.PREFERENCES_KEY_SERVER_NAME + serverId, (String) newValue)
- .apply();
- updateName();
- return true;
- } else if (preference == serverUrlPref) {
- final String url = (String) newValue;
- try {
- new URL(url);
- if (!url.equals(url.trim()) || url.contains("@")) {
- throw new Exception();
- }
- } catch (Exception x) {
- new ErrorDialog(getActivity(), R.string.settings_invalid_url, false);
- return false;
- }
-
- sharedPreferences.edit()
- .putString(Constants.PREFERENCES_KEY_SERVER_URL + serverId, url)
- .apply();
- updateUrl();
- return true;
- } else if (preference == serverUsernamePref) {
- String username = (String) newValue;
- if (username == null || !username.equals(username.trim())) {
- new ErrorDialog(getActivity(), R.string.settings_invalid_username, false);
- return false;
- }
-
- sharedPreferences.edit()
- .putString(Constants.PREFERENCES_KEY_USERNAME + serverId, username)
- .apply();
- updateUsername();
- return true;
- } else if (preference == serverPasswordPref) {
- sharedPreferences.edit()
- .putString(Constants.PREFERENCES_KEY_PASSWORD + serverId, (String) newValue)
- .apply();
- updatePassword();
- return true;
- } else if (preference == equalizerPref) {
- sharedPreferences.edit()
- .putBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId, (Boolean) newValue)
- .apply();
- return true;
- } else if (preference == jukeboxPref) {
- sharedPreferences.edit()
- .putBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId, (Boolean) newValue)
- .apply();
- return true;
- } else if (preference == allowSelfSignedCertificatePref) {
- sharedPreferences.edit()
- .putBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, (Boolean) newValue)
- .apply();
- return true;
- } else if (preference == enableLdapUserSupportPref) {
- sharedPreferences.edit()
- .putBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + serverId, (Boolean) newValue)
- .apply();
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- if (preference == removeServerPref) {
- removeServer();
- return true;
- } else if (preference == testConnectionPref) {
- testConnection();
- return true;
- }
- return false;
- }
-
- private void setupPreferencesValues() {
- updateName();
- updateUrl();
- updateUsername();
- updatePassword();
-
- if (!sharedPreferences.contains(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId)) {
- sharedPreferences.edit()
- .putBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId, true)
- .apply();
- }
- equalizerPref.setChecked(sharedPreferences
- .getBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId, true));
-
- jukeboxPref.setChecked(sharedPreferences
- .getBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId, false));
-
- allowSelfSignedCertificatePref.setChecked(sharedPreferences
- .getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, false));
-
- enableLdapUserSupportPref.setChecked(sharedPreferences
- .getBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + serverId, false));
- }
-
- private void updatePassword() {
- serverPasswordPref.setText(sharedPreferences
- .getString(Constants.PREFERENCES_KEY_PASSWORD + serverId,
- ""));
- }
-
- private void updateUsername() {
- serverUsernamePref.setText(sharedPreferences
- .getString(Constants.PREFERENCES_KEY_USERNAME + serverId,
- ""));
- }
-
- private void updateUrl() {
- final String serverUrl = sharedPreferences
- .getString(Constants.PREFERENCES_KEY_SERVER_URL + serverId,
- "http://");
- serverUrlPref.setText(serverUrl);
- serverUrlPref.setSummary(serverUrl);
- }
-
- private void updateName() {
- final String serverName = sharedPreferences
- .getString(Constants.PREFERENCES_KEY_SERVER_NAME + serverId,
- "");
- serverNamePref.setText(serverName);
- serverNamePref.setSummary(serverName);
- }
-
- private void setupPreferencesListeners() {
- serverNamePref.setOnPreferenceChangeListener(this);
- serverUrlPref.setOnPreferenceChangeListener(this);
- serverUsernamePref.setOnPreferenceChangeListener(this);
- serverPasswordPref.setOnPreferenceChangeListener(this);
- equalizerPref.setOnPreferenceChangeListener(this);
- jukeboxPref.setOnPreferenceChangeListener(this);
- allowSelfSignedCertificatePref.setOnPreferenceChangeListener(this);
- enableLdapUserSupportPref.setOnPreferenceChangeListener(this);
-
- removeServerPref.setOnPreferenceClickListener(this);
- testConnectionPref.setOnPreferenceClickListener(this);
- }
-
- private void testConnection() {
- ModalBackgroundTask task = new ModalBackgroundTask(getActivity(), false) {
- private int previousInstance;
-
- @Override
- protected Boolean doInBackground() throws Throwable {
- updateProgress(R.string.settings_testing_connection);
-
- final Context context = getActivity();
- previousInstance = Util.getActiveServer(context);
- Util.setActiveServer(context, serverId);
- try {
- MusicService musicService = MusicServiceFactory.getMusicService(context);
- musicService.ping(context, this);
- return musicService.isLicenseValid(context, null);
- } finally {
- Util.setActiveServer(context, previousInstance);
- }
- }
-
- @Override
- protected void done(Boolean licenseValid) {
- if (licenseValid) {
- Util.toast(getActivity(), R.string.settings_testing_ok);
- } else {
- Util.toast(getActivity(), R.string.settings_testing_unlicensed);
- }
- }
-
- @Override
- protected void cancel() {
- super.cancel();
- Util.setActiveServer(getActivity(), previousInstance);
- }
-
- @Override
- protected void error(Throwable error) {
- Log.w(LOG_TAG, error.toString(), error);
- new ErrorDialog(getActivity(), String.format("%s %s", getResources().getString(R.string.settings_connection_failure), getErrorMessage(error)), false);
- }
- };
- task.execute();
- }
-
- private void removeServer() {
- int activeServers = sharedPreferences
- .getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 0);
-
- // Clear permanent storage
- final String storageServerId = MusicServiceFactory.getServerId();
- final Directories directories = MusicServiceFactory.getDirectories();
- final PermanentFileStorage fileStorage = new PermanentFileStorage(
- directories,
- storageServerId,
- BuildConfig.DEBUG
- );
- fileStorage.clearAll();
-
- // Reset values to null so when we ask for them again they are new
- sharedPreferences.edit()
- .remove(Constants.PREFERENCES_KEY_SERVER_NAME + serverId)
- .remove(Constants.PREFERENCES_KEY_SERVER_URL + serverId)
- .remove(Constants.PREFERENCES_KEY_USERNAME + serverId)
- .remove(Constants.PREFERENCES_KEY_PASSWORD + serverId)
- .remove(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId)
- .remove(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId)
- .remove(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId)
- .apply();
-
- if (serverId < activeServers) {
- int activeServer = Util.getActiveServer(getActivity());
- for (int i = serverId; i <= activeServers; i++) {
- Util.removeInstanceName(getActivity(), i, activeServer);
- }
- }
-
- activeServers--;
-
- sharedPreferences.edit()
- .putInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, activeServers)
- .apply();
-
- getActivity().finish();
- }
-}
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 b621d8d5..3edebcd7 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java
@@ -9,9 +9,10 @@ import android.provider.SearchRecentSuggestions;
import androidx.annotation.Nullable;
import android.util.Log;
import android.view.View;
-import org.koin.java.standalone.KoinJavaComponent;
+
+import org.koin.java.KoinJavaComponent;
import org.moire.ultrasonic.R;
-import org.moire.ultrasonic.activity.ServerSettingsActivity;
+import org.moire.ultrasonic.activity.ServerSelectorActivity;
import org.moire.ultrasonic.activity.SubsonicTabActivity;
import org.moire.ultrasonic.featureflags.Feature;
import org.moire.ultrasonic.featureflags.FeatureStorage;
@@ -25,7 +26,8 @@ import java.io.File;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
+import static org.moire.ultrasonic.activity.ServerSelectorActivity.SERVER_SELECTOR_MANAGE_MODE;
/**
* Shows main app settings.
@@ -63,9 +65,7 @@ public class SettingsFragment extends PreferenceFragment
private TimeSpanPreference sharingDefaultExpiration;
private PreferenceCategory serversCategory;
- private int maxServerCount = 10;
private SharedPreferences settings;
- private int activeServers;
private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class);
@@ -271,77 +271,25 @@ public class SettingsFragment extends PreferenceFragment
}
private void setupServersCategory() {
- activeServers = settings.getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 0);
final Preference addServerPreference = new Preference(getActivity());
- addServerPreference.setKey(Constants.PREFERENCES_KEY_ADD_SERVER);
addServerPreference.setPersistent(false);
- addServerPreference.setTitle(getResources().getString(R.string.settings_server_add_server));
- addServerPreference.setEnabled(activeServers < maxServerCount);
+ addServerPreference.setTitle(getResources().getString(R.string.settings_server_manage_servers));
+ addServerPreference.setEnabled(true);
+ // TODO new server management here
serversCategory.removeAll();
serversCategory.addPreference(addServerPreference);
- for (int i = 1; i <= activeServers; i++) {
- final int serverId = i;
- Preference preference = new Preference(getActivity());
- preference.setPersistent(false);
- preference.setTitle(settings.getString(Constants.PREFERENCES_KEY_SERVER_NAME + serverId,
- getString(R.string.settings_server_name)));
- preference.setSummary(settings.getString(Constants.PREFERENCES_KEY_SERVER_URL + serverId,
- getString(R.string.settings_server_address_unset)));
- preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- final Intent intent = new Intent(getActivity(), ServerSettingsActivity.class);
- intent.putExtra(ServerSettingsActivity.ARG_SERVER_ID, serverId);
- startActivity(intent);
- return true;
- }
- });
- serversCategory.addPreference(preference);
- }
-
addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
- if (activeServers == maxServerCount) {
- return false;
- }
-
- activeServers++;
-
- settings.edit()
- .putInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, activeServers)
- .apply();
-
- Preference addServerPreference = findPreference(Constants.PREFERENCES_KEY_ADD_SERVER);
-
- if (addServerPreference != null) {
- serversCategory.removePreference(addServerPreference);
- }
-
- Preference newServerPrefs = new Preference(getActivity());
- newServerPrefs.setTitle(getString(R.string.settings_server_name));
- newServerPrefs.setSummary(getString(R.string.settings_server_address_unset));
- newServerPrefs.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- final Intent intent = new Intent(getActivity(), ServerSettingsActivity.class);
- intent.putExtra(ServerSettingsActivity.ARG_SERVER_ID, activeServers);
- startActivity(intent);
- return true;
- }
- });
- serversCategory.addPreference(newServerPrefs);
-
- if (addServerPreference != null) {
- serversCategory.addPreference(addServerPreference);
- addServerPreference.setEnabled(activeServers < maxServerCount);
- }
-
+ final Intent intent = new Intent(getActivity(), ServerSelectorActivity.class);
+ intent.putExtra(SERVER_SELECTOR_MANAGE_MODE, true);
+ startActivityForResult(intent, 0);
return true;
}
});
+
}
private void update() {
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java
index 9bc2ad66..52274108 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java
@@ -9,7 +9,7 @@ import org.moire.ultrasonic.service.MediaPlayerController;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
public class A2dpIntentReceiver extends BroadcastReceiver
{
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java
index 51b9db57..e4a219f9 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java
@@ -31,7 +31,7 @@ import org.moire.ultrasonic.util.Util;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* @author Sindre Mehus
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java
index 282b1061..d96429ff 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java
@@ -11,7 +11,7 @@ import org.moire.ultrasonic.util.Util;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
public class AudioFocusHandler
{
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java
index d3ed1276..173604c1 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java
@@ -21,6 +21,7 @@ package org.moire.ultrasonic.service;
import android.content.Context;
import android.graphics.Bitmap;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.Bookmark;
import org.moire.ultrasonic.domain.ChatMessage;
import org.moire.ultrasonic.domain.Genre;
@@ -48,13 +49,17 @@ import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import kotlin.Lazy;
import kotlin.Pair;
+import static org.koin.java.KoinJavaComponent.inject;
+
/**
* @author Sindre Mehus
*/
public class CachedMusicService implements MusicService
{
+ private Lazy activeServerProvider = inject(ActiveServerProvider.class);
private static final int MUSIC_DIR_CACHE_SIZE = 100;
@@ -365,7 +370,7 @@ public class CachedMusicService implements MusicService
private void checkSettingsChanged(Context context)
{
- String newUrl = Util.getRestUrl(context, null);
+ String newUrl = activeServerProvider.getValue().getRestUrl(null);
if (!Util.equals(newUrl, restUrl))
{
cachedMusicFolders.clear();
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java
index 4bfa4277..3b441ab6 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java
@@ -44,7 +44,7 @@ import kotlin.Pair;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.ON_AFTER_RELEASE;
import static android.os.PowerManager.SCREEN_DIM_WAKE_LOCK;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* @author Sindre Mehus
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java
index 285870c3..ce016bd1 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java
@@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
import static org.moire.ultrasonic.domain.PlayerState.STARTED;
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java
index 80860352..411e836e 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java
@@ -30,6 +30,7 @@ import android.widget.Toast;
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.JukeboxStatus;
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.util.Util;
@@ -47,7 +48,7 @@ import java.util.concurrent.atomic.AtomicLong;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* Provides an asynchronous interface to the remote jukebox on the Subsonic server.
@@ -97,6 +98,7 @@ public class JukeboxMediaPlayer
running.set(true);
startProcessTasks();
+ Log.d(TAG, "Started Jukebox Service");
}
public void stopJukeboxService()
@@ -108,6 +110,7 @@ public class JukeboxMediaPlayer
{
serviceThread.interrupt();
}
+ Log.d(TAG, "Stopped Jukebox Service");
}
private void startProcessTasks()
@@ -158,7 +161,7 @@ public class JukeboxMediaPlayer
try
{
- if (!Util.isOffline(context))
+ if (!ActiveServerProvider.Companion.isOffline(context))
{
task = tasks.take();
JukeboxStatus status = task.execute();
@@ -319,6 +322,7 @@ public class JukeboxMediaPlayer
public void setEnabled(boolean enabled)
{
+ Log.d(TAG, String.format("Jukebox Service setting enabled to %b", enabled));
this.enabled = enabled;
tasks.clear();
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java
index d10cf796..6b9aba0e 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java
@@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
import org.moire.ultrasonic.activity.DownloadActivity;
import org.moire.ultrasonic.audiofx.EqualizerController;
import org.moire.ultrasonic.audiofx.VisualizerController;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
@@ -968,7 +969,7 @@ public class LocalMediaPlayer
{
setPlayerState(DOWNLOADING);
- while (!bufferComplete() && !Util.isOffline(context))
+ while (!bufferComplete() && !ActiveServerProvider.Companion.isOffline(context))
{
Util.sleepQuietly(1000L);
if (isCancelled())
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java
index b7e9f034..bbe29d7e 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java
@@ -22,9 +22,10 @@ import android.content.Context;
import android.content.Intent;
import android.util.Log;
-import org.koin.java.standalone.KoinJavaComponent;
+import org.koin.java.KoinJavaComponent;
import org.moire.ultrasonic.audiofx.EqualizerController;
import org.moire.ultrasonic.audiofx.VisualizerController;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.domain.PlayerState;
@@ -40,7 +41,7 @@ import java.util.List;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* The implementation of the Media Player Controller.
@@ -63,6 +64,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
private Context context;
private Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
+ private Lazy activeServerProvider = inject(ActiveServerProvider.class);
private final DownloadQueueSerializer downloadQueueSerializer;
private final ExternalStorageMonitor externalStorageMonitor;
private final Downloader downloader;
@@ -93,8 +95,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
}
});
- int instance = Util.getActiveServer(context);
- setJukeboxEnabled(Util.getJukeboxEnabled(context, instance));
+ setJukeboxEnabled(activeServerProvider.getValue().getActiveServer().getJukeboxByDefault());
created = true;
Log.i(TAG, "MediaPlayerControllerImpl created");
@@ -519,7 +520,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
{
try
{
- String username = Util.getUserName(context, Util.getActiveServer(context));
+ String username = activeServerProvider.getValue().getActiveServer().getUserName();
UserInfo user = MusicServiceFactory.getMusicService(context).getUser(username, context, null);
return user.getJukeboxRole();
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java
index 7c50d0e6..f5a09d03 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java
@@ -18,7 +18,7 @@ import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
-import org.koin.java.standalone.KoinJavaComponent;
+import org.koin.java.KoinJavaComponent;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.activity.DownloadActivity;
import org.moire.ultrasonic.activity.SubsonicTabActivity;
@@ -38,7 +38,7 @@ import org.moire.ultrasonic.util.Util;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
import static org.moire.ultrasonic.domain.PlayerState.COMPLETED;
import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
import static org.moire.ultrasonic.domain.PlayerState.IDLE;
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java
index 97e3d125..c8bf71f4 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java
@@ -25,6 +25,7 @@ import android.util.Log;
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
import org.moire.ultrasonic.cache.PermanentFileStorage;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.Artist;
import org.moire.ultrasonic.domain.Genre;
import org.moire.ultrasonic.domain.Indexes;
@@ -60,6 +61,10 @@ import java.util.SortedSet;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
+import kotlin.Lazy;
+
+import static org.koin.java.KoinJavaComponent.inject;
+
/**
* @author Sindre Mehus
*/
@@ -68,7 +73,9 @@ public class OfflineMusicService extends RESTMusicService
private static final String TAG = OfflineMusicService.class.getSimpleName();
private static final Pattern COMPILE = Pattern.compile(" ");
- public OfflineMusicService(SubsonicAPIClient subsonicAPIClient, PermanentFileStorage storage) {
+ private Lazy activeServerProvider = inject(ActiveServerProvider.class);
+
+ public OfflineMusicService(SubsonicAPIClient subsonicAPIClient, PermanentFileStorage storage) {
super(subsonicAPIClient, storage);
}
@@ -626,7 +633,7 @@ public class OfflineMusicService extends RESTMusicService
@Override
public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception
{
- File playlistFile = FileUtil.getPlaylistFile(context, Util.getServerName(context), name);
+ File playlistFile = FileUtil.getPlaylistFile(context, activeServerProvider.getValue().getActiveServer().getName(), name);
FileWriter fw = new FileWriter(playlistFile);
BufferedWriter bw = new BufferedWriter(fw);
try
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java
index eaf191f3..b5dd7bc9 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java
@@ -63,6 +63,7 @@ import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse;
import org.moire.ultrasonic.api.subsonic.response.VideosResponse;
import org.moire.ultrasonic.cache.PermanentFileStorage;
import org.moire.ultrasonic.cache.serializers.DomainSerializers;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.APIAlbumConverter;
import org.moire.ultrasonic.domain.APIArtistConverter;
import org.moire.ultrasonic.domain.APIBookmarkConverter;
@@ -109,15 +110,20 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import kotlin.Lazy;
import kotlin.Pair;
import retrofit2.Response;
+import static org.koin.java.KoinJavaComponent.inject;
+
/**
* @author Sindre Mehus
*/
public class RESTMusicService implements MusicService {
private static final String TAG = RESTMusicService.class.getSimpleName();
+ private Lazy activeServerProvider = inject(ActiveServerProvider.class);
+
private static final String MUSIC_FOLDER_STORAGE_NAME = "music_folder";
private static final String INDEXES_STORAGE_NAME = "indexes";
private static final String ARTISTS_STORAGE_NAME = "artists";
@@ -306,7 +312,7 @@ public class RESTMusicService implements MusicService {
Context context,
ProgressListener progressListener) throws Exception {
try {
- return !Util.isOffline(context) &&
+ return !ActiveServerProvider.Companion.isOffline(context) &&
Util.getShouldUseId3Tags(context) ?
search3(criteria, context, progressListener) :
search2(criteria, context, progressListener);
@@ -388,7 +394,7 @@ public class RESTMusicService implements MusicService {
private void savePlaylist(String name,
Context context,
MusicDirectory playlist) throws IOException {
- File playlistFile = FileUtil.getPlaylistFile(context, Util.getServerName(context), name);
+ File playlistFile = FileUtil.getPlaylistFile(context, activeServerProvider.getValue().getActiveServer().getName(), name);
FileWriter fw = new FileWriter(playlistFile);
BufferedWriter bw = new BufferedWriter(fw);
try {
@@ -629,7 +635,7 @@ public class RESTMusicService implements MusicService {
synchronized (entry) {
// Use cached file, if existing.
Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size, highQuality);
- boolean serverScaling = Util.isServerScalingEnabled(context);
+ boolean serverScaling = ActiveServerProvider.Companion.isServerScalingEnabled(context);
if (bitmap == null) {
Log.d(TAG, "Loading cover art for: " + entry);
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java
index 80bdc035..40bd791f 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java
@@ -3,6 +3,7 @@ package org.moire.ultrasonic.service;
import android.content.Context;
import android.util.Log;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.util.Util;
/**
@@ -13,7 +14,6 @@ import org.moire.ultrasonic.util.Util;
*/
public class Scrobbler
{
-
private static final String TAG = Scrobbler.class.getSimpleName();
private String lastSubmission;
@@ -21,7 +21,7 @@ public class Scrobbler
public void scrobble(final Context context, final DownloadFile song, final boolean submission)
{
- if (song == null || !Util.isScrobblingEnabled(context))
+ if (song == null || !ActiveServerProvider.Companion.isScrobblingEnabled(context))
{
return;
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java
index 830eb882..2382712b 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java
@@ -5,6 +5,7 @@ import android.os.AsyncTask;
import android.os.StatFs;
import android.util.Log;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.Playlist;
import org.moire.ultrasonic.service.DownloadFile;
import org.moire.ultrasonic.service.Downloader;
@@ -21,7 +22,7 @@ import java.util.SortedSet;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* @author Sindre Mehus
@@ -39,6 +40,7 @@ public class CacheCleaner
private final Context context;
private Lazy downloader = inject(Downloader.class);
+ private Lazy activeServerProvider = inject(ActiveServerProvider.class);
public CacheCleaner(Context context)
{
@@ -301,7 +303,7 @@ public class CacheCleaner
try
{
Thread.currentThread().setName("BackgroundPlaylistsCleanup");
- String server = Util.getServerName(context);
+ String server = activeServerProvider.getValue().getActiveServer().getName();
SortedSet playlistFiles = FileUtil.listFiles(FileUtil.getPlaylistDirectory(context, server));
List playlists = params[0];
for (Playlist playlist : playlists)
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 303bc680..4008f7b5 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java
@@ -73,20 +73,8 @@ public final class Constants
public static final int NOTIFICATION_ID_PLAYING = 100;
// Preferences keys.
- public static final String PREFERENCES_KEY_SERVER = "server";
- public static final String PREFERENCES_KEY_SERVER_ENABLED = "serverEnabled";
- public static final String PREFERENCES_KEY_JUKEBOX_BY_DEFAULT = "jukeboxEnabled";
public static final String PREFERENCES_KEY_SERVER_INSTANCE = "serverInstanceId";
- public static final String PREFERENCES_KEY_SERVER_NAME = "serverName";
- public static final String PREFERENCES_KEY_SERVER_URL = "serverUrl";
public static final String PREFERENCES_KEY_SERVERS_KEY = "serversKey";
- public static final String PREFERENCES_KEY_ADD_SERVER = "addServer";
- public static final String PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers";
- public static final String PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId";
- public static final String PREFERENCES_KEY_USERNAME = "username";
- public static final String PREFERENCES_KEY_PASSWORD = "password";
- public static final String PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate";
- public static final String PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport";
public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime";
public static final String PREFERENCES_KEY_THEME = "theme";
public static final String PREFERENCES_KEY_DISPLAY_BITRATE_WITH_ARTIST = "displayBitrateWithArtist";
@@ -142,6 +130,7 @@ public final class Constants
public static final String PREFERENCES_KEY_FF_IMAGE_LOADER = "ff_new_image_loader";
public static final String PREFERENCES_KEY_USE_FIVE_STAR_RATING = "use_five_star_rating";
public static final String PREFERENCES_KEY_CATEGORY_NOTIFICATIONS = "notificationsCategory";
+ public static final String PREFERENCES_KEY_FIRST_RUN_EXECUTED = "firstRunExecuted";
// Number of free trial days for non-licensed servers.
public static final int FREE_TRIAL_DAYS = 30;
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java
index 9c84d6a0..e45bee66 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java
@@ -21,6 +21,7 @@ package org.moire.ultrasonic.util;
import android.content.Context;
import android.util.Log;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
@@ -97,7 +98,7 @@ public class ShufflePlayBuffer
// Check if active server has changed.
clearBufferIfNecessary();
- if (buffer.size() > REFILL_THRESHOLD || (!Util.isNetworkConnected(context) && !Util.isOffline(context)))
+ if (buffer.size() > REFILL_THRESHOLD || (!Util.isNetworkConnected(context) && !ActiveServerProvider.Companion.isOffline(context)))
{
return;
}
@@ -124,9 +125,9 @@ public class ShufflePlayBuffer
{
synchronized (buffer)
{
- if (currentServer != Util.getActiveServer(context))
+ if (currentServer != ActiveServerProvider.Companion.getActiveServerId(context))
{
- currentServer = Util.getActiveServer(context);
+ currentServer = ActiveServerProvider.Companion.getActiveServerId(context);
buffer.clear();
}
}
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 8b4108f2..c6c1525d 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
@@ -24,6 +24,7 @@ import android.app.PendingIntent;
import android.content.*;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -31,7 +32,6 @@ import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -42,10 +42,14 @@ import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.widget.RemoteViews;
import android.widget.Toast;
+
+import androidx.annotation.ColorInt;
+
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.activity.DownloadActivity;
import org.moire.ultrasonic.activity.MainActivity;
@@ -54,8 +58,6 @@ import org.moire.ultrasonic.domain.*;
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
import org.moire.ultrasonic.service.DownloadFile;
-import org.moire.ultrasonic.service.MediaPlayerController;
-import org.moire.ultrasonic.service.MusicServiceFactory;
import java.io.*;
import java.security.MessageDigest;
@@ -71,9 +73,8 @@ import java.util.regex.Pattern;
* @author Sindre Mehus
* @version $Id$
*/
-public class Util extends DownloadActivity
+public class Util
{
-
private static final String TAG = Util.class.getSimpleName();
private static final DecimalFormat GIGA_BYTE_FORMAT = new DecimalFormat("0.00 GB");
@@ -107,11 +108,6 @@ public class Util extends DownloadActivity
{
}
- public static boolean isOffline(Context context)
- {
- return context == null || getActiveServer(context) == 0;
- }
-
public static boolean isScreenLitOnDownload(Context context)
{
SharedPreferences preferences = getPreferences(context);
@@ -132,26 +128,6 @@ public class Util extends DownloadActivity
editor.commit();
}
- public static boolean isScrobblingEnabled(Context context)
- {
- if (isOffline(context))
- {
- return false;
- }
- SharedPreferences preferences = getPreferences(context);
- return preferences.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false);
- }
-
- public static boolean isServerScalingEnabled(Context context)
- {
- if (isOffline(context))
- {
- return false;
- }
- SharedPreferences preferences = getPreferences(context);
- return preferences.getBoolean(Constants.PREFERENCES_KEY_SERVER_SCALING, false);
- }
-
public static boolean isNotificationEnabled(Context context)
{
// After API26 foreground services must be used for music playback, and they must have a notification
@@ -174,152 +150,6 @@ public class Util extends DownloadActivity
return preferences.getBoolean(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS, false);
}
- public static void setActiveServer(
- Context context,
- int instance
- ) {
- MusicServiceFactory.resetMusicService();
- SharedPreferences preferences = getPreferences(context);
- SharedPreferences.Editor editor = preferences.edit();
- editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
- editor.apply();
- }
-
- public static int getActiveServer(Context context)
- {
- SharedPreferences preferences = getPreferences(context);
- return preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
- }
-
- public static int getActiveServers(Context context)
- {
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
- return settings.getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 3);
- }
-
- public static String getServerName(Context context)
- {
- int instance = getActiveServer(context);
-
- if (instance == 0)
- {
- return context.getResources().getString(R.string.main_offline);
- }
-
- SharedPreferences preferences = getPreferences(context);
- return preferences.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
- }
-
- public static String getServerName(Context context, int instance)
- {
- if (instance == 0)
- {
- return context.getResources().getString(R.string.main_offline);
- }
- SharedPreferences preferences = getPreferences(context);
- return preferences.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
- }
-
- public static String getUserName(Context context, int instance)
- {
- if (instance == 0)
- {
- return context.getResources().getString(R.string.main_offline);
- }
- SharedPreferences preferences = getPreferences(context);
- return preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
- }
-
- public static boolean getServerEnabled(Context context, int instance)
- {
- if (instance == 0)
- {
- return true;
- }
- SharedPreferences preferences = getPreferences(context);
- return preferences.getBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + instance, true);
- }
-
- public static boolean getJukeboxEnabled(Context context, int instance)
- {
- if (instance == 0)
- {
- return false;
- }
-
- SharedPreferences preferences = getPreferences(context);
- return preferences.getBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + instance, false);
- }
-
- public static void setServerRestVersion(Context context, Version version)
- {
- SERVER_REST_VERSIONS.put(getActiveServer(context), version);
- }
-
- public static Version getServerRestVersion(Context context)
- {
- return SERVER_REST_VERSIONS.get(getActiveServer(context));
- }
-
- public static void removeInstanceName(Context context, int instance, int activeInstance)
- {
- SharedPreferences preferences = getPreferences(context);
- SharedPreferences.Editor editor = preferences.edit();
-
- int newInstance = instance + 1;
-
- String server = preferences.getString(Constants.PREFERENCES_KEY_SERVER + newInstance, null);
- String serverName = preferences.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
- String serverUrl = preferences.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
- String userName = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
- String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
- boolean serverEnabled = preferences.getBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + newInstance, true);
- boolean jukeboxEnabled = preferences.getBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + newInstance, true);
-
- editor.putString(Constants.PREFERENCES_KEY_SERVER + instance, server);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl);
- editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName);
- editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password);
- editor.putBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + instance, serverEnabled);
- editor.putBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + instance, jukeboxEnabled);
-
- editor.putString(Constants.PREFERENCES_KEY_SERVER + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
- editor.putBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + newInstance, true);
- editor.putBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + newInstance, false);
- editor.commit();
-
- if (instance == activeInstance)
- {
- Util.setActiveServer(context, 0);
- }
-
- if (newInstance == activeInstance)
- {
- Util.setActiveServer(context, instance);
- }
- }
-
- public static void setSelectedMusicFolderId(Context context, String musicFolderId)
- {
- int instance = getActiveServer(context);
- SharedPreferences preferences = getPreferences(context);
- SharedPreferences.Editor editor = preferences.edit();
- editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId);
- editor.commit();
- }
-
- public static String getSelectedMusicFolderId(Context context)
- {
- SharedPreferences preferences = getPreferences(context);
- int instance = getActiveServer(context);
- return preferences.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null);
- }
-
public static String getTheme(Context context)
{
SharedPreferences preferences = getPreferences(context);
@@ -356,35 +186,6 @@ public class Util extends DownloadActivity
return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize;
}
- public static String getRestUrl(Context context, String method)
- {
- StringBuilder builder = new StringBuilder(8192);
-
- SharedPreferences preferences = getPreferences(context);
-
- int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
- String serverUrl = preferences.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
- String username = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
- String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
-
- // Slightly obfuscate password
- password = "enc:" + Util.utf8HexEncode(password);
-
- builder.append(serverUrl);
- if (builder.charAt(builder.length() - 1) != '/')
- {
- builder.append('/');
- }
-
- builder.append("rest/").append(method).append(".view");
- builder.append("?u=").append(username);
- builder.append("&p=").append(password);
- builder.append("&v=").append(Constants.REST_PROTOCOL_VERSION);
- builder.append("&c=").append(Constants.REST_CLIENT_ID);
-
- return builder.toString();
- }
-
public static SharedPreferences getPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
@@ -1619,4 +1420,31 @@ public class Util extends DownloadActivity
SharedPreferences preferences = getPreferences(context);
return Integer.parseInt(preferences.getString(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY, "5"));
}
+
+ public static @ColorInt int getColorFromAttribute(Context context, int resId)
+ {
+ TypedValue typedValue = new TypedValue();
+ Resources.Theme theme = context.getTheme();
+ theme.resolveAttribute(resId, typedValue, true);
+ return typedValue.data;
+ }
+
+ public static int getResourceFromAttribute(Context context, int resId)
+ {
+ TypedValue typedValue = new TypedValue();
+ Resources.Theme theme = context.getTheme();
+ theme.resolveAttribute(resId, typedValue, true);
+ return typedValue.resourceId;
+ }
+
+ public static boolean isFirstRun(Context context)
+ {
+ SharedPreferences preferences = getPreferences(context);
+ boolean firstExecuted = preferences.getBoolean(Constants.PREFERENCES_KEY_FIRST_RUN_EXECUTED, false);
+ if (firstExecuted) return false;
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(Constants.PREFERENCES_KEY_FIRST_RUN_EXECUTED, true);
+ editor.apply();
+ return true;
+ }
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java
index 1884e201..a3a3e181 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java
@@ -26,6 +26,7 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.moire.ultrasonic.R;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
@@ -127,7 +128,7 @@ public class AlbumView extends UpdateView
viewHolder.artist.setVisibility(artist == null ? View.GONE : View.VISIBLE);
viewHolder.star.setImageDrawable(starred ? starDrawable : starHollowDrawable);
- if (Util.isOffline(this.context) || "-1".equals(album.getId()))
+ if (ActiveServerProvider.Companion.isOffline(this.context) || "-1".equals(album.getId()))
{
viewHolder.star.setVisibility(View.GONE);
}
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 0361e81f..235dccda 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java
@@ -10,6 +10,7 @@ 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.util.ImageLoader;
import org.moire.ultrasonic.util.Util;
@@ -19,6 +20,10 @@ import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
+import kotlin.Lazy;
+
+import static org.koin.java.KoinJavaComponent.inject;
+
public class ChatAdapter extends ArrayAdapter
{
private final SubsonicTabActivity activity;
@@ -27,6 +32,8 @@ public class ChatAdapter extends ArrayAdapter
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);
+
public ChatAdapter(SubsonicTabActivity activity, List messages)
{
super(activity, R.layout.chat_item, messages);
@@ -62,7 +69,7 @@ public class ChatAdapter extends ArrayAdapter
Date messageTime = new java.util.Date(message.getTime());
String messageText = message.getMessage();
- String me = Util.getUserName(activity, Util.getActiveServer(activity));
+ String me = activeServerProvider.getValue().getActiveServer().getUserName();
layout = messageUser.equals(me) ? R.layout.chat_item_reverse : R.layout.chat_item;
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java
index b12b438a..8c2a879c 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java
@@ -31,8 +31,9 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-import org.koin.java.standalone.KoinJavaComponent;
+import org.koin.java.KoinJavaComponent;
import org.moire.ultrasonic.R;
+import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.featureflags.Feature;
import org.moire.ultrasonic.featureflags.FeatureStorage;
@@ -47,7 +48,7 @@ import java.io.File;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* Used to display songs in a {@code ListView}.
@@ -73,8 +74,6 @@ public class SongView extends UpdateView implements Checkable
private ImageType previousLeftImageType;
private ImageType previousRightImageType;
private ImageType leftImageType;
- private ImageType rightImageType;
- private Drawable rightImage;
private DownloadFile downloadFile;
private boolean playing;
private EntryAdapter.SongViewHolder viewHolder;
@@ -134,20 +133,20 @@ public class SongView extends UpdateView implements Checkable
{
inflater.inflate(song.isVideo() ? R.layout.video_list_item : R.layout.song_list_item, this, true);
viewHolder = new EntryAdapter.SongViewHolder();
- viewHolder.check = (CheckedTextView) findViewById(R.id.song_check);
- viewHolder.rating = (LinearLayout) findViewById(R.id.song_rating);
- viewHolder.fiveStar1 = (ImageView) findViewById(R.id.song_five_star_1);
- viewHolder.fiveStar2 = (ImageView) findViewById(R.id.song_five_star_2);
- viewHolder.fiveStar3 = (ImageView) findViewById(R.id.song_five_star_3);
- viewHolder.fiveStar4 = (ImageView) findViewById(R.id.song_five_star_4);
- viewHolder.fiveStar5 = (ImageView) findViewById(R.id.song_five_star_5);
- viewHolder.star = (ImageView) findViewById(R.id.song_star);
- viewHolder.drag = (ImageView) findViewById(R.id.song_drag);
- viewHolder.track = (TextView) findViewById(R.id.song_track);
- viewHolder.title = (TextView) findViewById(R.id.song_title);
- viewHolder.artist = (TextView) findViewById(R.id.song_artist);
- viewHolder.duration = (TextView) findViewById(R.id.song_duration);
- viewHolder.status = (TextView) findViewById(R.id.song_status);
+ viewHolder.check = findViewById(R.id.song_check);
+ viewHolder.rating = findViewById(R.id.song_rating);
+ viewHolder.fiveStar1 = findViewById(R.id.song_five_star_1);
+ viewHolder.fiveStar2 = findViewById(R.id.song_five_star_2);
+ viewHolder.fiveStar3 = findViewById(R.id.song_five_star_3);
+ viewHolder.fiveStar4 = findViewById(R.id.song_five_star_4);
+ viewHolder.fiveStar5 = findViewById(R.id.song_five_star_5);
+ viewHolder.star = findViewById(R.id.song_star);
+ viewHolder.drag = findViewById(R.id.song_drag);
+ viewHolder.track = findViewById(R.id.song_track);
+ viewHolder.title = findViewById(R.id.song_title);
+ viewHolder.artist = findViewById(R.id.song_artist);
+ viewHolder.duration = findViewById(R.id.song_duration);
+ viewHolder.status = findViewById(R.id.song_status);
setTag(viewHolder);
}
@@ -248,7 +247,7 @@ public class SongView extends UpdateView implements Checkable
viewHolder.drag.setVisibility(draggable ? View.VISIBLE : View.GONE);
}
- if (Util.isOffline(this.context))
+ if (ActiveServerProvider.Companion.isOffline(this.context))
{
viewHolder.star.setVisibility(View.GONE);
viewHolder.rating.setVisibility(View.GONE);
@@ -338,6 +337,8 @@ public class SongView extends UpdateView implements Checkable
this.leftImage = null;
}
+ ImageType rightImageType;
+ Drawable rightImage;
if (downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && partialFile.exists())
{
if (this.viewHolder.status != null)
@@ -345,13 +346,13 @@ public class SongView extends UpdateView implements Checkable
this.viewHolder.status.setText(Util.formatLocalizedBytes(partialFile.length(), this.context));
}
- this.rightImageType = ImageType.downloading;
- this.rightImage = downloadingImage;
+ rightImageType = ImageType.downloading;
+ rightImage = downloadingImage;
}
else
{
- this.rightImageType = ImageType.none;
- this.rightImage = null;
+ rightImageType = ImageType.none;
+ rightImage = null;
if (this.viewHolder.status != null)
{
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java
index d08e062f..e4cc2cdf 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java
@@ -31,7 +31,7 @@ import org.moire.ultrasonic.service.MediaPlayerController;
import kotlin.Lazy;
-import static org.koin.java.standalone.KoinJavaComponent.inject;
+import static org.koin.java.KoinJavaComponent.inject;
/**
* A simple class that draws waveform data received from a
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/EditServerActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/EditServerActivity.kt
new file mode 100644
index 00000000..8956610c
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/EditServerActivity.kt
@@ -0,0 +1,342 @@
+package org.moire.ultrasonic.activity
+
+import android.app.AlertDialog
+import android.os.Bundle
+import android.util.Log
+import android.view.MenuItem
+import android.widget.Button
+import androidx.appcompat.app.ActionBar
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import com.google.android.material.switchmaterial.SwitchMaterial
+import com.google.android.material.textfield.TextInputLayout
+import java.io.IOException
+import java.net.MalformedURLException
+import java.net.URL
+import org.koin.android.viewmodel.ext.android.viewModel
+import org.moire.ultrasonic.BuildConfig
+import org.moire.ultrasonic.R
+import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
+import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
+import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
+import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
+import org.moire.ultrasonic.data.ServerSetting
+import org.moire.ultrasonic.service.SubsonicRESTException
+import org.moire.ultrasonic.util.Constants
+import org.moire.ultrasonic.util.ErrorDialog
+import org.moire.ultrasonic.util.ModalBackgroundTask
+import org.moire.ultrasonic.util.Util
+import retrofit2.Response
+
+/**
+ * This Activity provides a Form which can be used to edit the properties of a Server Setting.
+ * It can also be used to create a Server Setting from scratch.
+ * Contains functions for testing the configured Server Setting
+ */
+internal class EditServerActivity : AppCompatActivity() {
+
+ companion object {
+ private val TAG = EditServerActivity::class.simpleName
+ const val EDIT_SERVER_INTENT_INDEX = "index"
+ }
+
+ private val serverSettingsModel: ServerSettingsModel by viewModel()
+ private var currentServerSetting: ServerSetting? = null
+
+ private var serverNameEditText: TextInputLayout? = null
+ private var serverAddressEditText: TextInputLayout? = null
+ private var userNameEditText: TextInputLayout? = null
+ private var passwordEditText: TextInputLayout? = null
+ private var selfSignedSwitch: SwitchMaterial? = null
+ private var ldapSwitch: SwitchMaterial? = null
+ private var jukeboxSwitch: SwitchMaterial? = null
+ private var saveButton: Button? = null
+ private var testButton: Button? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ applyTheme()
+ if (savedInstanceState == null) configureActionBar()
+
+ setContentView(R.layout.server_edit)
+
+ serverNameEditText = findViewById(R.id.edit_server_name)
+ serverAddressEditText = findViewById(R.id.edit_server_address)
+ userNameEditText = findViewById(R.id.edit_server_username)
+ passwordEditText = findViewById(R.id.edit_server_password)
+ selfSignedSwitch = findViewById(R.id.edit_self_signed)
+ ldapSwitch = findViewById(R.id.edit_ldap)
+ jukeboxSwitch = findViewById(R.id.edit_jukebox)
+ saveButton = findViewById(R.id.edit_save)
+ testButton = findViewById(R.id.edit_test)
+
+ val index = intent.getIntExtra(EDIT_SERVER_INTENT_INDEX, -1)
+
+ if (index != -1) {
+ // Editing an existing server
+ setTitle(R.string.server_editor_label)
+ val serverSetting = serverSettingsModel.getServerSetting(index)
+ serverSetting.observe(
+ this,
+ Observer { t ->
+ if (t != null) {
+ currentServerSetting = t
+ setFields()
+ }
+ }
+ )
+ saveButton!!.setOnClickListener {
+ if (currentServerSetting != null) {
+ if (getFields()) {
+ serverSettingsModel.updateItem(currentServerSetting)
+ finish()
+ }
+ }
+ }
+ } else {
+ // Creating a new server
+ setTitle(R.string.server_editor_new_label)
+ currentServerSetting = ServerSetting()
+ saveButton!!.setOnClickListener {
+ if (getFields()) {
+ serverSettingsModel.saveNewItem(currentServerSetting)
+ finish()
+ }
+ }
+ }
+
+ testButton!!.setOnClickListener {
+ if (getFields()) {
+ testConnection()
+ }
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ finishActivity()
+ return true
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onBackPressed() {
+ finishActivity()
+ }
+
+ private fun applyTheme() {
+ val theme = Util.getTheme(this)
+ if (
+ "dark".equals(theme, ignoreCase = true) ||
+ "fullscreen".equals(theme, ignoreCase = true)
+ ) {
+ setTheme(R.style.UltraSonicTheme)
+ } else if (
+ "light".equals(theme, ignoreCase = true) ||
+ "fullscreenlight".equals(theme, ignoreCase = true)
+ ) {
+ setTheme(R.style.UltraSonicTheme_Light)
+ }
+ }
+
+ private fun configureActionBar() {
+ val actionBar: ActionBar? = supportActionBar
+ if (actionBar != null) {
+ actionBar.setDisplayShowHomeEnabled(true)
+ actionBar.setDisplayHomeAsUpEnabled(true)
+ }
+ }
+
+ /**
+ * Sets the values of the Form from the current Server Setting instance
+ */
+ private fun setFields() {
+ if (currentServerSetting == null) return
+
+ serverNameEditText!!.editText?.setText(currentServerSetting!!.name)
+ serverAddressEditText!!.editText?.setText(currentServerSetting!!.url)
+ userNameEditText!!.editText?.setText(currentServerSetting!!.userName)
+ passwordEditText!!.editText?.setText(currentServerSetting!!.password)
+ selfSignedSwitch!!.isChecked = currentServerSetting!!.allowSelfSignedCertificate
+ ldapSwitch!!.isChecked = currentServerSetting!!.ldapSupport
+ jukeboxSwitch!!.isChecked = currentServerSetting!!.jukeboxByDefault
+ }
+
+ /**
+ * Retrieves the values in the Form to the current Server Setting instance
+ * This function also does some basic validation on the fields
+ */
+ private fun getFields(): Boolean {
+ if (currentServerSetting == null) return false
+ var isValid = true
+ var url: URL? = null
+
+ if (serverAddressEditText!!.editText?.text.isNullOrBlank()) {
+ serverAddressEditText!!.error = getString(R.string.server_editor_required)
+ isValid = false
+ } else {
+ try {
+ val urlString = serverAddressEditText!!.editText?.text.toString()
+ url = URL(urlString)
+ if (
+ urlString != urlString.trim(' ') ||
+ urlString.contains("@") ||
+ url.host.isNullOrBlank()
+ ) {
+ throw MalformedURLException()
+ }
+ serverAddressEditText!!.error = null
+ } catch (exception: MalformedURLException) {
+ serverAddressEditText!!.error = getString(R.string.settings_invalid_url)
+ isValid = false
+ }
+ }
+
+ if (serverNameEditText!!.editText?.text.isNullOrBlank()) {
+ if (isValid && url != null) {
+ serverNameEditText!!.editText?.setText(url.host)
+ }
+ }
+
+ if (userNameEditText!!.editText?.text.isNullOrBlank()) {
+ userNameEditText!!.error = getString(R.string.server_editor_required)
+ isValid = false
+ } else {
+ userNameEditText!!.error = null
+ }
+
+ if (isValid) {
+ currentServerSetting!!.name = serverNameEditText!!.editText?.text.toString()
+ currentServerSetting!!.url = serverAddressEditText!!.editText?.text.toString()
+ currentServerSetting!!.userName = userNameEditText!!.editText?.text.toString()
+ currentServerSetting!!.password = passwordEditText!!.editText?.text.toString()
+ currentServerSetting!!.allowSelfSignedCertificate = selfSignedSwitch!!.isChecked
+ currentServerSetting!!.ldapSupport = ldapSwitch!!.isChecked
+ currentServerSetting!!.jukeboxByDefault = jukeboxSwitch!!.isChecked
+ }
+
+ return isValid
+ }
+
+ /**
+ * Checks whether any value in the fields are changed according to their original values.
+ */
+ private fun areFieldsChanged(): Boolean {
+ if (currentServerSetting == null) {
+ return !serverNameEditText!!.editText?.text!!.isBlank() ||
+ serverAddressEditText!!.editText?.text.toString() != "http://" ||
+ !userNameEditText!!.editText?.text!!.isBlank() ||
+ !passwordEditText!!.editText?.text!!.isBlank()
+ }
+
+ return currentServerSetting!!.name != serverNameEditText!!.editText?.text.toString() ||
+ currentServerSetting!!.url != serverAddressEditText!!.editText?.text.toString() ||
+ currentServerSetting!!.userName != userNameEditText!!.editText?.text.toString() ||
+ currentServerSetting!!.password != passwordEditText!!.editText?.text.toString() ||
+ currentServerSetting!!.allowSelfSignedCertificate != selfSignedSwitch!!.isChecked ||
+ currentServerSetting!!.ldapSupport != ldapSwitch!!.isChecked ||
+ currentServerSetting!!.jukeboxByDefault != jukeboxSwitch!!.isChecked
+ }
+
+ /**
+ * Tests if the network connection to the entered Server Settings can be made
+ */
+ private fun testConnection() {
+ val task: ModalBackgroundTask = object : ModalBackgroundTask(
+ this,
+ false
+ ) {
+
+ @Throws(Throwable::class)
+ override fun doInBackground(): Boolean {
+ updateProgress(R.string.settings_testing_connection)
+ val configuration = SubsonicClientConfiguration(
+ currentServerSetting!!.url,
+ currentServerSetting!!.userName,
+ currentServerSetting!!.password,
+ SubsonicAPIVersions.getClosestKnownClientApiVersion(
+ Constants.REST_PROTOCOL_VERSION
+ ),
+ Constants.REST_CLIENT_ID,
+ currentServerSetting!!.allowSelfSignedCertificate,
+ currentServerSetting!!.ldapSupport,
+ BuildConfig.DEBUG
+ )
+ val subsonicApiClient = SubsonicAPIClient(configuration)
+ val pingResponse = subsonicApiClient.api.ping().execute()
+ checkResponseSuccessful(pingResponse)
+
+ val licenseResponse = subsonicApiClient.api.getLicense().execute()
+ checkResponseSuccessful(licenseResponse)
+ return licenseResponse.body()!!.license.valid
+ }
+
+ override fun done(licenseValid: Boolean) {
+ if (licenseValid) {
+ Util.toast(activity, R.string.settings_testing_ok)
+ } else {
+ Util.toast(activity, R.string.settings_testing_unlicensed)
+ }
+ }
+
+ override fun error(error: Throwable) {
+ Log.w(TAG, error.toString(), error)
+ ErrorDialog(
+ activity,
+ String.format(
+ "%s %s",
+ resources.getString(R.string.settings_connection_failure),
+ getErrorMessage(error)
+ ),
+ false
+ )
+ }
+ }
+ task.execute()
+ }
+
+ /**
+ * Checks the Subsonic Response for application specific errors
+ */
+ private fun checkResponseSuccessful(response: Response) {
+ if (
+ response.isSuccessful &&
+ response.body()!!.status === SubsonicResponse.Status.OK
+ ) {
+ return
+ }
+ if (!response.isSuccessful) {
+ throw IOException("Server error, code: " + response.code())
+ } else if (
+ response.body()!!.status === SubsonicResponse.Status.ERROR &&
+ response.body()!!.error != null
+ ) {
+ throw SubsonicRESTException(response.body()!!.error!!)
+ } else {
+ throw IOException("Failed to perform request: " + response.code())
+ }
+ }
+
+ /**
+ * Finishes the Activity, after confirmation from the user if needed
+ */
+ private fun finishActivity() {
+ if (areFieldsChanged()) {
+ AlertDialog.Builder(this)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.common_confirm)
+ .setMessage(R.string.server_editor_leave_confirmation)
+ .setPositiveButton(R.string.common_ok) { dialog, _ ->
+ dialog.dismiss()
+ finish()
+ }
+ .setNegativeButton(R.string.common_cancel) { dialog, _ ->
+ dialog.dismiss()
+ }
+ .show()
+ } else {
+ finish()
+ }
+ }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerRowAdapter.kt
new file mode 100644
index 00000000..f0baf6ca
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerRowAdapter.kt
@@ -0,0 +1,202 @@
+package org.moire.ultrasonic.activity
+
+import android.content.Context
+import android.graphics.Color
+import android.os.Build
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.PopupMenu
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import org.moire.ultrasonic.R
+import org.moire.ultrasonic.data.ActiveServerProvider
+import org.moire.ultrasonic.data.ServerSetting
+import org.moire.ultrasonic.util.Util
+
+/**
+ * Row Adapter to be used in the Server List
+ * Converts a Server Setting into a displayable Row, and sets up the Row's context menu
+ * @param manageMode: set to True if the default action by clicking the row is to edit the server
+ * In Manage Mode the "Offline" setting is not visible, and the servers can be edited by
+ * clicking the row.
+ */
+internal class ServerRowAdapter(
+ private var context: Context,
+ private var data: Array,
+ private val model: ServerSettingsModel,
+ private val activeServerProvider: ActiveServerProvider,
+ private val manageMode: Boolean,
+ private val serverDeletedCallback: ((Int) -> Unit),
+ private val serverEditRequestedCallback: ((Int) -> Unit)
+) : BaseAdapter() {
+
+ companion object {
+ private const val MENU_ID_EDIT = 1
+ private const val MENU_ID_DELETE = 2
+ private const val MENU_ID_UP = 3
+ private const val MENU_ID_DOWN = 4
+ }
+
+ var inflater: LayoutInflater =
+ context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+
+ fun setData(data: Array) {
+ this.data = data
+ notifyDataSetChanged()
+ }
+
+ override fun getCount(): Int {
+ return if (manageMode) data.size - 1 else data.size
+ }
+
+ override fun getItem(position: Int): Any {
+ return data[position]
+ }
+
+ override fun getItemId(position: Int): Long {
+ return position.toLong()
+ }
+
+ /**
+ * Creates the Row representation of a Server Setting
+ */
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
+ var index = position
+ // Skip "Offline" in manage mode
+ if (manageMode) index++
+
+ var vi: View? = convertView
+ if (vi == null) vi = inflater.inflate(R.layout.server_row, parent, false)
+
+ val text = vi?.findViewById(R.id.server_name)
+ val description = vi?.findViewById(R.id.server_description)
+ val layout = vi?.findViewById(R.id.server_layout)
+ val image = vi?.findViewById(R.id.server_image)
+ val serverMenu = vi?.findViewById(R.id.server_menu)
+
+ text?.text = data.single { setting -> setting.index == index }.name
+ description?.text = data.single { setting -> setting.index == index }.url
+
+ // Provide icons for the row
+ if (index == 0) {
+ serverMenu?.visibility = View.INVISIBLE
+ image?.setImageDrawable(Util.getDrawableFromAttribute(context, R.attr.screen_on_off))
+ } else {
+ image?.setImageDrawable(Util.getDrawableFromAttribute(context, R.attr.podcasts))
+ }
+
+ // Highlight the Active Server's row by changing its background
+ if (index == activeServerProvider.getActiveServer().index) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ layout?.background = ContextCompat.getDrawable(context, R.drawable.select_ripple)
+ } else {
+ layout?.setBackgroundResource(
+ Util.getResourceFromAttribute(context, R.attr.list_selector_holo_selected)
+ )
+ }
+ } else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ layout?.background = ContextCompat.getDrawable(context, R.drawable.default_ripple)
+ } else {
+ layout?.setBackgroundResource(
+ Util.getResourceFromAttribute(context, R.attr.list_selector_holo)
+ )
+ }
+ }
+
+ // Add the context menu for the row
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ serverMenu?.background = ContextCompat.getDrawable(
+ context,
+ R.drawable.select_ripple_circle
+ )
+ } else {
+ serverMenu?.setBackgroundColor(Color.TRANSPARENT)
+ }
+
+ serverMenu?.setOnClickListener { view -> serverMenuClick(view, index) }
+
+ return vi
+ }
+
+ /**
+ * Builds the Context Menu of a row when the "more" icon is clicked
+ */
+ private fun serverMenuClick(view: View, position: Int) {
+ val menu = PopupMenu(context, view)
+ val firstServer = 1
+ var lastServer = count - 1
+
+ if (!manageMode) {
+ menu.menu.add(
+ Menu.NONE,
+ MENU_ID_EDIT,
+ Menu.NONE,
+ context.getString(R.string.server_menu_edit)
+ )
+ } else {
+ lastServer++
+ }
+
+ menu.menu.add(
+ Menu.NONE,
+ MENU_ID_DELETE,
+ Menu.NONE,
+ context.getString(R.string.server_menu_delete)
+ )
+
+ if (position != firstServer) {
+ menu.menu.add(
+ Menu.NONE,
+ MENU_ID_UP,
+ Menu.NONE,
+ context.getString(R.string.server_menu_move_up)
+ )
+ }
+
+ if (position != lastServer) {
+ menu.menu.add(
+ Menu.NONE,
+ MENU_ID_DOWN,
+ Menu.NONE,
+ context.getString(R.string.server_menu_move_down)
+ )
+ }
+
+ menu.show()
+
+ menu.setOnMenuItemClickListener { menuItem -> popupMenuItemClick(menuItem, position) }
+ }
+
+ /**
+ * Handles the click on a context menu item
+ */
+ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean {
+ when (menuItem.itemId) {
+ MENU_ID_EDIT -> {
+ serverEditRequestedCallback.invoke(position)
+ return true
+ }
+ MENU_ID_DELETE -> {
+ serverDeletedCallback.invoke(position)
+ return true
+ }
+ MENU_ID_UP -> {
+ model.moveItemUp(position)
+ return true
+ }
+ MENU_ID_DOWN -> {
+ model.moveItemDown(position)
+ return true
+ }
+ else -> return false
+ }
+ }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerSelectorActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerSelectorActivity.kt
new file mode 100644
index 00000000..434392a3
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerSelectorActivity.kt
@@ -0,0 +1,186 @@
+package org.moire.ultrasonic.activity
+
+import android.app.AlertDialog
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.MenuItem
+import android.widget.AdapterView
+import android.widget.ListView
+import androidx.appcompat.app.ActionBar
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+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.EditServerActivity.Companion.EDIT_SERVER_INTENT_INDEX
+import org.moire.ultrasonic.data.ActiveServerProvider
+import org.moire.ultrasonic.service.MediaPlayerController
+import org.moire.ultrasonic.util.Util
+
+/**
+ * This Activity can be used to display all the configured Server Setting items.
+ * It also contains a FAB to add a new server.
+ * It has a Manage Mode and a Select Mode. In Select Mode, clicking the List Items will select
+ * the server, and a server can be edited using the context menu. In Manage Mode the default
+ * action when a List Item is clicked is to edit the server.
+ */
+internal class ServerSelectorActivity : AppCompatActivity() {
+
+ companion object {
+ private val TAG = ServerSelectorActivity::class.simpleName
+ const val SERVER_SELECTOR_MANAGE_MODE = "manageMode"
+ }
+
+ private var listView: ListView? = null
+ private val serverSettingsModel: ServerSettingsModel by viewModel()
+ private val service: MediaPlayerController by inject()
+ private val activeServerProvider: ActiveServerProvider by inject()
+ private var serverRowAdapter: ServerRowAdapter? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ applyTheme()
+ if (savedInstanceState == null) configureActionBar()
+
+ setContentView(R.layout.server_selector)
+
+ val manageMode = intent.getBooleanExtra(SERVER_SELECTOR_MANAGE_MODE, false)
+ if (manageMode) {
+ setTitle(R.string.settings_server_manage_servers)
+ } else {
+ setTitle(R.string.server_selector_label)
+ }
+
+ listView = findViewById(R.id.server_list)
+ serverRowAdapter = ServerRowAdapter(
+ this,
+ arrayOf(),
+ serverSettingsModel,
+ activeServerProvider,
+ manageMode,
+ {
+ i ->
+ onServerDeleted(i)
+ },
+ {
+ i ->
+ editServer(i)
+ }
+ )
+
+ listView?.adapter = serverRowAdapter
+
+ listView?.onItemClickListener = AdapterView.OnItemClickListener {
+ _, _, position, _ ->
+ if (manageMode) {
+ editServer(position + 1)
+ } else {
+ setActiveServer(position)
+ finish()
+ }
+ }
+
+ val fab = findViewById(R.id.server_add_fab)
+ fab.setOnClickListener {
+ editServer(-1)
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ finish()
+ return true
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val serverList = serverSettingsModel.getServerList()
+ serverList.observe(
+ this,
+ Observer { t ->
+ serverRowAdapter!!.setData(t.toTypedArray())
+ }
+ )
+ }
+ private fun applyTheme() {
+ val theme = Util.getTheme(this)
+ if (
+ "dark".equals(theme, ignoreCase = true) ||
+ "fullscreen".equals(theme, ignoreCase = true)
+ ) {
+ setTheme(R.style.UltraSonicTheme)
+ } else if (
+ "light".equals(theme, ignoreCase = true) ||
+ "fullscreenlight".equals(theme, ignoreCase = true)
+ ) {
+ setTheme(R.style.UltraSonicTheme_Light)
+ }
+ }
+
+ private fun configureActionBar() {
+ val actionBar: ActionBar? = supportActionBar
+ if (actionBar != null) {
+ actionBar.setDisplayShowHomeEnabled(true)
+ actionBar.setDisplayHomeAsUpEnabled(true)
+ }
+ }
+
+ /**
+ * Sets the active server when a list item is clicked
+ */
+ private fun setActiveServer(index: Int) {
+ // TODO this is still a blocking call - we shouldn't leave this activity before the active server is updated.
+ // Maybe this can be refactored by using LiveData, or this can be made more user friendly with a ProgressDialog
+ runBlocking {
+ withContext(Dispatchers.IO) {
+ if (activeServerProvider.getActiveServer().index != index) {
+ service.clearIncomplete()
+ activeServerProvider.setActiveServerByIndex(index)
+ }
+ service.isJukeboxEnabled = activeServerProvider.getActiveServer().jukeboxByDefault
+ }
+ }
+ Log.i(TAG, "Active server was set to: $index")
+ }
+
+ /**
+ * This Callback handles the deletion of a Server Setting
+ */
+ private fun onServerDeleted(index: Int) {
+ AlertDialog.Builder(this)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.server_menu_delete)
+ .setMessage(R.string.server_selector_delete_confirmation)
+ .setPositiveButton(R.string.common_delete) { dialog, _ ->
+ dialog.dismiss()
+
+ val activeServerIndex = activeServerProvider.getActiveServer().index
+ // If the currently active server is deleted, go offline
+ if (index == activeServerIndex) setActiveServer(-1)
+
+ serverSettingsModel.deleteItem(index)
+ Log.i(TAG, "Server deleted: $index")
+ }
+ .setNegativeButton(R.string.common_cancel) { dialog, _ ->
+ dialog.dismiss()
+ }
+ .show()
+ }
+
+ /**
+ * Starts the Edit Server Activity to edit the details of a server
+ */
+ private fun editServer(index: Int) {
+ val intent = Intent(this, EditServerActivity::class.java)
+ intent.putExtra(EDIT_SERVER_INTENT_INDEX, index)
+ startActivityForResult(intent, 0)
+ }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerSettingsModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerSettingsModel.kt
new file mode 100644
index 00000000..78df5476
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/ServerSettingsModel.kt
@@ -0,0 +1,234 @@
+package org.moire.ultrasonic.activity
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.preference.PreferenceManager
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.moire.ultrasonic.R
+import org.moire.ultrasonic.data.ActiveServerProvider
+import org.moire.ultrasonic.data.ServerSetting
+import org.moire.ultrasonic.data.ServerSettingDao
+
+/**
+ * ViewModel to be used in Activities which will handle Server Settings
+ */
+class ServerSettingsModel(
+ private val repository: ServerSettingDao,
+ private val activeServerProvider: ActiveServerProvider,
+ private val context: Context
+) : ViewModel() {
+ private var serverList: MutableLiveData> = MutableLiveData()
+
+ companion object {
+ private val TAG = ServerSettingsModel::class.simpleName
+ // These constants were removed from Constants.java as they are deprecated and only used here
+ private const val PREFERENCES_KEY_JUKEBOX_BY_DEFAULT = "jukeboxEnabled"
+ private const val PREFERENCES_KEY_SERVER_NAME = "serverName"
+ private const val PREFERENCES_KEY_SERVER_URL = "serverUrl"
+ private const val PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers"
+ private const val PREFERENCES_KEY_USERNAME = "username"
+ private const val PREFERENCES_KEY_PASSWORD = "password"
+ private const val PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate"
+ private const val PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport"
+ private const val PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"
+ }
+
+ /**
+ * This function will try and convert settings from the Preferences to the Database
+ * @return True, if the migration was executed, False otherwise
+ */
+ fun migrateFromPreferences(): Boolean {
+ var migrated = true
+
+ runBlocking {
+ val rowCount = repository.count()
+
+ if (rowCount == null || rowCount == 0) {
+ // First time load up the server settings from the Preferences
+ val dbServerList = mutableListOf()
+ val settings = PreferenceManager.getDefaultSharedPreferences(context)
+ val serverNum = settings.getInt(PREFERENCES_KEY_ACTIVE_SERVERS, 0)
+
+ if (serverNum != 0) {
+ var index = 1
+ for (x in 1 until serverNum + 1) {
+ val newServerSetting = loadServerSettingFromPreferences(x, index, settings)
+ if (newServerSetting != null) {
+ dbServerList.add(newServerSetting)
+ repository.insert(newServerSetting)
+ index++
+ Log.i(
+ TAG,
+ "Imported server from Preferences to Database:" +
+ " ${newServerSetting.name}"
+ )
+ }
+ }
+ } else {
+ migrated = false
+ }
+ }
+ }
+
+ return migrated
+ }
+
+ /**
+ * Retrieves the list of the configured servers from the database.
+ * This function is asynchronous, uses LiveData to provide the Setting.
+ */
+ fun getServerList(): LiveData> {
+ viewModelScope.launch {
+ val dbServerList = repository.loadAllServerSettings().toMutableList()
+
+ dbServerList.add(0, ServerSetting(context.getString(R.string.main_offline), ""))
+ serverList.value = dbServerList
+ }
+ return serverList
+ }
+
+ /**
+ * Retrieves a single Server Setting by its index
+ * This function is asynchronous, uses LiveData to provide the Setting.
+ */
+ fun getServerSetting(index: Int): LiveData {
+ val result = MutableLiveData()
+ viewModelScope.launch {
+ val dbServer = repository.findByIndex(index)
+ result.value = dbServer
+ Log.d(TAG, "getServerSetting($index) returning $dbServer")
+ }
+ return result
+ }
+
+ /**
+ * Moves a Setting up in the Server List by decreasing its index
+ */
+ fun moveItemUp(index: Int) {
+ if (index == 1) return
+
+ val itemToBeMoved = serverList.value?.single { setting -> setting.index == index }
+ val previousItem = serverList.value?.single { setting -> setting.index == index - 1 }
+
+ itemToBeMoved?.index = previousItem!!.index
+ previousItem.index = index
+
+ viewModelScope.launch {
+ repository.update(itemToBeMoved!!, previousItem)
+ }
+
+ activeServerProvider.invalidateCache()
+ // Notify the observers of the changed values
+ serverList.value = serverList.value
+ }
+
+ /**
+ * Moves a Setting down in the Server List by increasing its index
+ */
+ fun moveItemDown(index: Int) {
+ if (index == (serverList.value!!.size - 1)) return
+
+ val itemToBeMoved = serverList.value?.single { setting -> setting.index == index }
+ val nextItem = serverList.value?.single { setting -> setting.index == index + 1 }
+
+ itemToBeMoved?.index = nextItem!!.index
+ nextItem.index = index
+
+ viewModelScope.launch {
+ repository.update(itemToBeMoved!!, nextItem)
+ }
+
+ activeServerProvider.invalidateCache()
+ // Notify the observers of the changed values
+ serverList.value = serverList.value
+ }
+
+ /**
+ * Removes a Setting from the database
+ */
+ fun deleteItem(index: Int) {
+ if (index == 0) return
+
+ val newList = serverList.value!!.toMutableList()
+ val itemToBeDeleted = newList.single { setting -> setting.index == index }
+ newList.remove(itemToBeDeleted)
+
+ for (x in index + 1 until newList.size + 1) {
+ newList.single { setting -> setting.index == x }.index--
+ }
+
+ viewModelScope.launch {
+ repository.delete(itemToBeDeleted)
+ for (x in index until newList.size) {
+ repository.update(newList.single { setting -> setting.index == x })
+ }
+ }
+
+ activeServerProvider.invalidateCache()
+ serverList.value = newList
+ Log.d(TAG, "deleteItem deleted index: $index")
+ }
+
+ /**
+ * Updates a Setting in the database
+ */
+ fun updateItem(serverSetting: ServerSetting?) {
+ if (serverSetting == null) return
+
+ viewModelScope.launch {
+ repository.update(serverSetting)
+ activeServerProvider.invalidateCache()
+ Log.d(TAG, "updateItem updated server setting: $serverSetting")
+ }
+ }
+
+ /**
+ * Inserts a new Setting into the database
+ */
+ fun saveNewItem(serverSetting: ServerSetting?) {
+ if (serverSetting == null) return
+
+ viewModelScope.launch {
+ serverSetting.index = (repository.count() ?: 0) + 1
+ serverSetting.id = serverSetting.index
+ repository.insert(serverSetting)
+ Log.d(TAG, "saveNewItem saved server setting: $serverSetting")
+ }
+ }
+
+ /**
+ * Reads up a Server Setting stored in the obsolete Preferences
+ */
+ private fun loadServerSettingFromPreferences(
+ preferenceId: Int,
+ serverId: Int,
+ settings: SharedPreferences
+ ): ServerSetting? {
+ val url = settings.getString(PREFERENCES_KEY_SERVER_URL + preferenceId, "")
+ val userName = settings.getString(PREFERENCES_KEY_USERNAME + preferenceId, "")
+
+ if (url.isNullOrEmpty() || userName.isNullOrEmpty()) return null
+
+ return ServerSetting(
+ preferenceId,
+ serverId,
+ settings.getString(PREFERENCES_KEY_SERVER_NAME + preferenceId, "")!!,
+ url,
+ userName,
+ settings.getString(PREFERENCES_KEY_PASSWORD + preferenceId, "")!!,
+ settings.getBoolean(PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + preferenceId, false),
+ settings.getBoolean(
+ PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + preferenceId,
+ false
+ ),
+ settings.getBoolean(PREFERENCES_KEY_LDAP_SUPPORT + preferenceId, false),
+ settings.getString(PREFERENCES_KEY_MUSIC_FOLDER_ID + preferenceId, null)
+ )
+ }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt
index fe97c592..8a9e1af0 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt
@@ -1,8 +1,10 @@
package org.moire.ultrasonic.app
import androidx.multidex.MultiDexApplication
-import org.koin.android.ext.android.startKoin
-import org.moire.ultrasonic.di.DiProperties
+import org.koin.android.ext.koin.androidContext
+import org.koin.android.ext.koin.androidLogger
+import org.koin.core.context.startKoin
+import org.koin.core.logger.Level
import org.moire.ultrasonic.di.appPermanentStorage
import org.moire.ultrasonic.di.baseNetworkModule
import org.moire.ultrasonic.di.directoriesModule
@@ -14,19 +16,21 @@ class UApp : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
- startKoin(
- this,
- listOf(
+ startKoin {
+ // Use Koin Android Logger
+ // TODO Current version of Koin has a bug, which forces the usage of Level.ERROR
+ androidLogger(Level.ERROR)
+ // declare Android context
+ androidContext(this@UApp)
+ // declare modules to use
+ modules(
directoriesModule,
appPermanentStorage,
baseNetworkModule,
featureFlagsModule,
musicServiceModule,
mediaPlayerModule
- ),
- extraProperties = mapOf(
- DiProperties.APP_CONTEXT to applicationContext
)
- )
+ }
}
}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt
new file mode 100644
index 00000000..feb1fa19
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt
@@ -0,0 +1,171 @@
+package org.moire.ultrasonic.data
+
+import android.content.Context
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.moire.ultrasonic.R
+import org.moire.ultrasonic.service.MusicServiceFactory.resetMusicService
+import org.moire.ultrasonic.util.Constants
+import org.moire.ultrasonic.util.Util
+
+/**
+ * This class can be used to retrieve the properties of the Active Server
+ * It caches the settings read up from the DB to improve performance.
+ */
+class ActiveServerProvider(
+ private val repository: ServerSettingDao,
+ private val context: Context
+) {
+ private var cachedServer: ServerSetting? = null
+
+ /**
+ * Get the settings of the current Active Server
+ * @return The Active Server Settings
+ */
+ fun getActiveServer(): ServerSetting {
+ val serverId = getActiveServerId(context)
+
+ if (serverId > 0) {
+ if (cachedServer != null && cachedServer!!.id == serverId) return cachedServer!!
+
+ // Ideally this is the only call where we block the thread while using the repository
+ runBlocking {
+ withContext(Dispatchers.IO) {
+ cachedServer = repository.findById(serverId)
+ }
+ Log.d(
+ TAG,
+ "getActiveServer retrieved from DataBase, id: $serverId; " +
+ "cachedServer: $cachedServer"
+ )
+ }
+
+ if (cachedServer != null) return cachedServer!!
+ setActiveServerId(context, 0)
+ }
+
+ return ServerSetting(
+ id = -1,
+ index = 0,
+ name = context.getString(R.string.main_offline),
+ url = "http://localhost",
+ userName = "",
+ password = "",
+ jukeboxByDefault = false,
+ allowSelfSignedCertificate = false,
+ ldapSupport = false,
+ musicFolderId = ""
+ )
+ }
+
+ /**
+ * Sets the Active Server by the Server Index in the Server Selector List
+ * @param index: The index of the Active Server in the Server Selector List
+ */
+ fun setActiveServerByIndex(index: Int) {
+ Log.d(TAG, "setActiveServerByIndex $index")
+ if (index < 1) {
+ // Offline mode is selected
+ setActiveServerId(context, 0)
+ return
+ }
+
+ GlobalScope.launch(Dispatchers.IO) {
+ val serverId = repository.findByIndex(index)!!.id
+ setActiveServerId(context, serverId)
+ }
+ }
+
+ /**
+ * Invalidates the Active Server Setting cache
+ * This should be called when the Active Server or one of its properties changes
+ */
+ fun invalidateCache() {
+ Log.d(TAG, "Cache is invalidated")
+ cachedServer = null
+ }
+
+ /**
+ * Gets the Rest Url of the Active Server
+ * @param method: The Rest resource to use
+ * @return The Rest Url of the method on the server
+ */
+ fun getRestUrl(method: String?): String? {
+ val builder = StringBuilder(8192)
+ val activeServer = getActiveServer()
+ val serverUrl: String = activeServer.url
+ val username: String = activeServer.userName
+ var password: String = activeServer.password
+
+ // Slightly obfuscate password
+ password = "enc:" + Util.utf8HexEncode(password)
+ builder.append(serverUrl)
+ if (builder[builder.length - 1] != '/') {
+ builder.append('/')
+ }
+ builder.append("rest/").append(method).append(".view")
+ builder.append("?u=").append(username)
+ builder.append("&p=").append(password)
+ builder.append("&v=").append(Constants.REST_PROTOCOL_VERSION)
+ builder.append("&c=").append(Constants.REST_CLIENT_ID)
+ return builder.toString()
+ }
+
+ companion object {
+ private val TAG = ActiveServerProvider::class.simpleName
+
+ /**
+ * Queries if the Active Server is the "Offline" mode of Ultrasonic
+ * @return True, if the "Offline" mode is selected
+ */
+ fun isOffline(context: Context?): Boolean {
+ return context == null || getActiveServerId(context) < 1
+ }
+
+ /**
+ * Queries the Id of the Active Server
+ */
+ fun getActiveServerId(context: Context): Int {
+ val preferences = Util.getPreferences(context)
+ return preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, -1)
+ }
+
+ /**
+ * Sets the Id of the Active Server
+ */
+ fun setActiveServerId(context: Context, serverId: Int) {
+ resetMusicService()
+
+ val preferences = Util.getPreferences(context)
+ val editor = preferences.edit()
+ editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, serverId)
+ editor.apply()
+ }
+
+ /**
+ * Queries if Scrobbling is enabled
+ */
+ fun isScrobblingEnabled(context: Context): Boolean {
+ if (isOffline(context)) {
+ return false
+ }
+ val preferences = Util.getPreferences(context)
+ return preferences.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false)
+ }
+
+ /**
+ * Queries if Server Scaling is enabled
+ */
+ fun isServerScalingEnabled(context: Context): Boolean {
+ if (isOffline(context)) {
+ return false
+ }
+ val preferences = Util.getPreferences(context)
+ return preferences.getBoolean(Constants.PREFERENCES_KEY_SERVER_SCALING, false)
+ }
+ }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt
new file mode 100644
index 00000000..f9704f26
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt
@@ -0,0 +1,16 @@
+package org.moire.ultrasonic.data
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+
+/**
+ * Room Database to be used to store data for Ultrasonic
+ */
+@Database(entities = [ServerSetting::class], version = 1)
+abstract class AppDatabase : RoomDatabase() {
+
+ /**
+ * Retrieves the Server Settings DAO for the Database
+ */
+ abstract fun serverSettingDao(): ServerSettingDao
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt
new file mode 100644
index 00000000..5c337142
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt
@@ -0,0 +1,39 @@
+package org.moire.ultrasonic.data
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+/**
+ * Contains the data model of a Server Setting
+ * @param id: Unique Id of the Server Setting
+ * @param index: The index of the Server Setting on the Selection Activity
+ * @param name: The user readable Name of the Server
+ * @param url: The Url of the server
+ * @param userName: The UserName that can be used to connect to the server
+ * @param password: The Password of the User
+ * @param jukeboxByDefault: True if the JukeBox mode should be turned on for the server
+ * @param allowSelfSignedCertificate: True if the server uses self-signed certificate
+ * @param ldapSupport: True if the server authenticates the user using old Ldap-like way
+ * @param musicFolderId: The Id of the MusicFolder to be used with the server
+ */
+@Entity
+data class ServerSetting(
+ @PrimaryKey var id: Int,
+ @ColumnInfo(name = "index") var index: Int,
+ @ColumnInfo(name = "name") var name: String,
+ @ColumnInfo(name = "url") var url: String,
+ @ColumnInfo(name = "userName") var userName: String,
+ @ColumnInfo(name = "password") var password: String,
+ @ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean,
+ @ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean,
+ @ColumnInfo(name = "ldapSupport") var ldapSupport: Boolean,
+ @ColumnInfo(name = "musicFolderId") var musicFolderId: String?
+) {
+ constructor() : this (
+ -1, 0, "", "", "", "", false, false, false, null
+ )
+ constructor(name: String, url: String) : this(
+ -1, 0, name, url, "", "", false, false, false, null
+ )
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSettingDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSettingDao.kt
new file mode 100644
index 00000000..65c365dd
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSettingDao.kt
@@ -0,0 +1,57 @@
+package org.moire.ultrasonic.data
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+
+/**
+ * Room Dao for the Server Setting table
+ */
+@Dao
+interface ServerSettingDao {
+
+ /**
+ *Inserts a new Server Setting to the table
+ */
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(vararg serverSetting: ServerSetting)
+
+ /**
+ * Deletes a Server Setting from the table
+ */
+ @Delete
+ suspend fun delete(serverSetting: ServerSetting)
+
+ /**
+ * Updates an existing Server Setting in the table
+ */
+ @Update
+ suspend fun update(vararg serverSetting: ServerSetting)
+
+ /**
+ * Loads all Server Settings from the table
+ */
+ @Query("SELECT * FROM serverSetting")
+ suspend fun loadAllServerSettings(): Array
+
+ /**
+ * Finds a Server Setting by its unique Id
+ */
+ @Query("SELECT * FROM serverSetting WHERE [id] = :id")
+ suspend fun findById(id: Int): ServerSetting?
+
+ /**
+ * Finds a Server Setting by its Index in the Select List
+ */
+ @Query("SELECT * FROM serverSetting WHERE [index] = :index")
+ suspend fun findByIndex(index: Int): ServerSetting?
+
+ /**
+ * Retrieves the count of rows in the table
+ */
+ @Query("SELECT COUNT(*) FROM serverSetting")
+ suspend fun count(): Int?
+}
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 fb9c9fb9..34ae53f2 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt
@@ -1,10 +1,31 @@
package org.moire.ultrasonic.di
-import org.koin.dsl.module.module
+import androidx.room.Room
+import org.koin.android.ext.koin.androidContext
+import org.koin.android.viewmodel.dsl.viewModel
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+import org.moire.ultrasonic.activity.ServerSettingsModel
+import org.moire.ultrasonic.data.ActiveServerProvider
+import org.moire.ultrasonic.data.AppDatabase
import org.moire.ultrasonic.util.Util
const val SP_NAME = "Default_SP"
val appPermanentStorage = module {
- single(name = SP_NAME) { Util.getPreferences(getProperty(DiProperties.APP_CONTEXT)) }
+ single(named(SP_NAME)) { Util.getPreferences(androidContext()) }
+
+ single {
+ Room.databaseBuilder(
+ androidContext(),
+ AppDatabase::class.java,
+ "ultrasonic-database"
+ ).build()
+ }
+
+ single { get().serverSettingDao() }
+
+ viewModel { ServerSettingsModel(get(), get(), androidContext()) }
+
+ single { ActiveServerProvider(get(), androidContext()) }
}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt
index 7bf4ee73..65287cf8 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt
@@ -1,7 +1,7 @@
package org.moire.ultrasonic.di
import okhttp3.OkHttpClient
-import org.koin.dsl.module.module
+import org.koin.dsl.module
/**
* Provides base network dependencies.
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DiProperties.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DiProperties.kt
deleted file mode 100644
index ac4748be..00000000
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DiProperties.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.moire.ultrasonic.di
-
-object DiProperties {
- const val APP_CONTEXT = "app_context"
-}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt
index 577568ec..231d5f19 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt
@@ -1,6 +1,7 @@
package org.moire.ultrasonic.di
-import org.koin.dsl.module.module
+import org.koin.dsl.bind
+import org.koin.dsl.module
import org.moire.ultrasonic.cache.AndroidDirectories
import org.moire.ultrasonic.cache.Directories
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/FeatureFlagsModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/FeatureFlagsModule.kt
index 5d8d484b..16c715e8 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/FeatureFlagsModule.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/FeatureFlagsModule.kt
@@ -1,8 +1,9 @@
package org.moire.ultrasonic.di
-import org.koin.dsl.module.module
+import org.koin.android.ext.koin.androidContext
+import org.koin.dsl.module
import org.moire.ultrasonic.featureflags.FeatureStorage
val featureFlagsModule = module {
- factory { FeatureStorage(getProperty(DiProperties.APP_CONTEXT)) }
+ factory { FeatureStorage(androidContext()) }
}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt
index 8553bc2c..bec8a8c0 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt
@@ -1,7 +1,7 @@
package org.moire.ultrasonic.di
import org.koin.android.ext.koin.androidContext
-import org.koin.dsl.module.module
+import org.koin.dsl.module
import org.moire.ultrasonic.service.AudioFocusHandler
import org.moire.ultrasonic.service.DownloadQueueSerializer
import org.moire.ultrasonic.service.Downloader
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 69f38a86..f522925d 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt
@@ -1,16 +1,16 @@
@file:JvmName("MusicServiceModule")
package org.moire.ultrasonic.di
-import android.content.SharedPreferences
-import android.util.Log
import kotlin.math.abs
-import org.koin.dsl.module.module
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
import org.moire.ultrasonic.BuildConfig
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
-import org.moire.ultrasonic.api.subsonic.di.subsonicApiModule
import org.moire.ultrasonic.cache.PermanentFileStorage
+import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.service.CachedMusicService
import org.moire.ultrasonic.service.MusicService
import org.moire.ultrasonic.service.OfflineMusicService
@@ -18,99 +18,51 @@ import org.moire.ultrasonic.service.RESTMusicService
import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader
import org.moire.ultrasonic.util.Constants
-internal const val MUSIC_SERVICE_CONTEXT = "CurrentMusicService"
internal const val ONLINE_MUSIC_SERVICE = "OnlineMusicService"
internal const val OFFLINE_MUSIC_SERVICE = "OfflineMusicService"
-private const val DEFAULT_SERVER_INSTANCE = 1
-private const val UNKNOWN_SERVER_URL = "not-exists"
-private const val LOG_TAG = "MusicServiceModule"
-val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) {
- subsonicApiModule()
+val musicServiceModule = module {
- single(name = "ServerInstance") {
- return@single get(SP_NAME).getInt(
- Constants.PREFERENCES_KEY_SERVER_INSTANCE,
- DEFAULT_SERVER_INSTANCE
- )
+ single(named("ServerInstance")) {
+ return@single ActiveServerProvider.getActiveServerId(androidContext())
}
- single(name = "ServerID") {
- val serverInstance = get(name = "ServerInstance")
- val sp: SharedPreferences = get(SP_NAME)
- val serverUrl = sp.getString(
- Constants.PREFERENCES_KEY_SERVER_URL + serverInstance,
- null
- )
- return@single if (serverUrl == null) {
- UNKNOWN_SERVER_URL
- } else {
- abs("$serverUrl$serverInstance".hashCode()).toString()
- }
+ single(named("ServerID")) {
+ val serverInstance = get(named("ServerInstance"))
+ val serverUrl = get().getActiveServer().url
+ return@single abs("$serverUrl$serverInstance".hashCode()).toString()
}
single {
- val serverId = get(name = "ServerID")
+ val serverId = get(named("ServerID"))
return@single PermanentFileStorage(get(), serverId, BuildConfig.DEBUG)
}
single {
- val instance = get(name = "ServerInstance")
- val sp: SharedPreferences = get(SP_NAME)
- val serverUrl = sp.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null)
- val username = sp.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null)
- val password = sp.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null)
- val allowSelfSignedCertificate = sp.getBoolean(
- Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance,
- false
+ return@single SubsonicClientConfiguration(
+ baseUrl = get().getActiveServer().url,
+ username = get().getActiveServer().userName,
+ password = get().getActiveServer().password,
+ minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
+ Constants.REST_PROTOCOL_VERSION
+ ),
+ clientID = Constants.REST_CLIENT_ID,
+ allowSelfSignedCertificate = get()
+ .getActiveServer().allowSelfSignedCertificate,
+ enableLdapUserSupport = get().getActiveServer().ldapSupport,
+ debug = BuildConfig.DEBUG
)
- val enableLdapUserSupport = sp.getBoolean(
- Constants.PREFERENCES_KEY_LDAP_SUPPORT + instance,
- false
- )
-
- if (serverUrl == null ||
- username == null ||
- password == null
- ) {
- Log.i(LOG_TAG, "Server credentials is not available")
- return@single SubsonicClientConfiguration(
- baseUrl = "http://localhost",
- username = "",
- password = "",
- minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
- Constants.REST_PROTOCOL_VERSION
- ),
- clientID = Constants.REST_CLIENT_ID,
- allowSelfSignedCertificate = allowSelfSignedCertificate,
- enableLdapUserSupport = enableLdapUserSupport,
- debug = BuildConfig.DEBUG
- )
- } else {
- return@single SubsonicClientConfiguration(
- baseUrl = serverUrl,
- username = username,
- password = password,
- minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
- Constants.REST_PROTOCOL_VERSION
- ),
- clientID = Constants.REST_CLIENT_ID,
- allowSelfSignedCertificate = allowSelfSignedCertificate,
- enableLdapUserSupport = enableLdapUserSupport,
- debug = BuildConfig.DEBUG
- )
- }
}
single { SubsonicAPIClient(get()) }
- single(name = ONLINE_MUSIC_SERVICE) {
+ single(named(ONLINE_MUSIC_SERVICE)) {
CachedMusicService(RESTMusicService(get(), get()))
}
- single(name = OFFLINE_MUSIC_SERVICE) {
+ single(named(OFFLINE_MUSIC_SERVICE)) {
OfflineMusicService(get(), get())
}
- single { SubsonicImageLoader(getProperty(DiProperties.APP_CONTEXT), get()) }
+ single { SubsonicImageLoader(androidContext(), get()) }
}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt
index fa10ec1e..f5644eb5 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt
@@ -19,23 +19,25 @@
package org.moire.ultrasonic.service
import android.content.Context
-import org.koin.standalone.KoinComponent
-import org.koin.standalone.get
-import org.koin.standalone.release
+import org.koin.core.KoinComponent
+import org.koin.core.context.loadKoinModules
+import org.koin.core.context.unloadKoinModules
+import org.koin.core.get
+import org.koin.core.qualifier.named
import org.moire.ultrasonic.cache.Directories
-import org.moire.ultrasonic.di.MUSIC_SERVICE_CONTEXT
+import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.di.OFFLINE_MUSIC_SERVICE
import org.moire.ultrasonic.di.ONLINE_MUSIC_SERVICE
-import org.moire.ultrasonic.util.Util
+import org.moire.ultrasonic.di.musicServiceModule
@Deprecated("Use DI way to get MusicService")
object MusicServiceFactory : KoinComponent {
@JvmStatic
fun getMusicService(context: Context): MusicService {
- return if (Util.isOffline(context)) {
- get(OFFLINE_MUSIC_SERVICE)
+ return if (ActiveServerProvider.isOffline(context)) {
+ get(named(OFFLINE_MUSIC_SERVICE))
} else {
- get(ONLINE_MUSIC_SERVICE)
+ get(named(ONLINE_MUSIC_SERVICE))
}
}
@@ -45,11 +47,12 @@ object MusicServiceFactory : KoinComponent {
*/
@JvmStatic
fun resetMusicService() {
- release(MUSIC_SERVICE_CONTEXT)
+ unloadKoinModules(musicServiceModule)
+ loadKoinModules(musicServiceModule)
}
@JvmStatic
- fun getServerId() = get(name = "ServerID")
+ fun getServerId() = get(named("ServerID"))
@JvmStatic
fun getDirectories() = get()
diff --git a/ultrasonic/src/main/res/drawable-hdpi/ic_add_white.png b/ultrasonic/src/main/res/drawable-hdpi/ic_add_white.png
new file mode 100644
index 00000000..276717fc
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-hdpi/ic_add_white.png differ
diff --git a/ultrasonic/src/main/res/drawable-hdpi/ic_more_vert_dark.png b/ultrasonic/src/main/res/drawable-hdpi/ic_more_vert_dark.png
new file mode 100644
index 00000000..2af8f46f
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-hdpi/ic_more_vert_dark.png differ
diff --git a/ultrasonic/src/main/res/drawable-hdpi/ic_more_vert_light.png b/ultrasonic/src/main/res/drawable-hdpi/ic_more_vert_light.png
new file mode 100644
index 00000000..859e9f07
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-hdpi/ic_more_vert_light.png differ
diff --git a/ultrasonic/src/main/res/drawable-ldpi/ic_add_white.png b/ultrasonic/src/main/res/drawable-ldpi/ic_add_white.png
new file mode 100644
index 00000000..9832384a
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-ldpi/ic_add_white.png differ
diff --git a/ultrasonic/src/main/res/drawable-ldpi/ic_more_vert_dark.png b/ultrasonic/src/main/res/drawable-ldpi/ic_more_vert_dark.png
new file mode 100644
index 00000000..bfeb3e5e
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-ldpi/ic_more_vert_dark.png differ
diff --git a/ultrasonic/src/main/res/drawable-ldpi/ic_more_vert_light.png b/ultrasonic/src/main/res/drawable-ldpi/ic_more_vert_light.png
new file mode 100644
index 00000000..bbc17d7b
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-ldpi/ic_more_vert_light.png differ
diff --git a/ultrasonic/src/main/res/drawable-mdpi/ic_add_white.png b/ultrasonic/src/main/res/drawable-mdpi/ic_add_white.png
new file mode 100644
index 00000000..d83a0e1e
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-mdpi/ic_add_white.png differ
diff --git a/ultrasonic/src/main/res/drawable-mdpi/ic_more_vert_dark.png b/ultrasonic/src/main/res/drawable-mdpi/ic_more_vert_dark.png
new file mode 100644
index 00000000..1adb28df
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-mdpi/ic_more_vert_dark.png differ
diff --git a/ultrasonic/src/main/res/drawable-mdpi/ic_more_vert_light.png b/ultrasonic/src/main/res/drawable-mdpi/ic_more_vert_light.png
new file mode 100644
index 00000000..7c319e5a
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-mdpi/ic_more_vert_light.png differ
diff --git a/ultrasonic/src/main/res/drawable-xhdpi/ic_add_white.png b/ultrasonic/src/main/res/drawable-xhdpi/ic_add_white.png
new file mode 100644
index 00000000..9ac0b6c1
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-xhdpi/ic_add_white.png differ
diff --git a/ultrasonic/src/main/res/drawable-xhdpi/ic_more_vert_dark.png b/ultrasonic/src/main/res/drawable-xhdpi/ic_more_vert_dark.png
new file mode 100644
index 00000000..5244b31c
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-xhdpi/ic_more_vert_dark.png differ
diff --git a/ultrasonic/src/main/res/drawable-xhdpi/ic_more_vert_light.png b/ultrasonic/src/main/res/drawable-xhdpi/ic_more_vert_light.png
new file mode 100644
index 00000000..4667234f
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-xhdpi/ic_more_vert_light.png differ
diff --git a/ultrasonic/src/main/res/drawable-xxhdpi/ic_add_white.png b/ultrasonic/src/main/res/drawable-xxhdpi/ic_add_white.png
new file mode 100644
index 00000000..fa20f29a
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-xxhdpi/ic_add_white.png differ
diff --git a/ultrasonic/src/main/res/drawable-xxhdpi/ic_more_vert_dark.png b/ultrasonic/src/main/res/drawable-xxhdpi/ic_more_vert_dark.png
new file mode 100644
index 00000000..ae482b3e
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-xxhdpi/ic_more_vert_dark.png differ
diff --git a/ultrasonic/src/main/res/drawable-xxhdpi/ic_more_vert_light.png b/ultrasonic/src/main/res/drawable-xxhdpi/ic_more_vert_light.png
new file mode 100644
index 00000000..ebe3065e
Binary files /dev/null and b/ultrasonic/src/main/res/drawable-xxhdpi/ic_more_vert_light.png differ
diff --git a/ultrasonic/src/main/res/drawable/default_ripple.xml b/ultrasonic/src/main/res/drawable/default_ripple.xml
new file mode 100644
index 00000000..90bf9914
--- /dev/null
+++ b/ultrasonic/src/main/res/drawable/default_ripple.xml
@@ -0,0 +1,19 @@
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/ultrasonic/src/main/res/drawable/list_selector_holo_dark_selected.xml b/ultrasonic/src/main/res/drawable/list_selector_holo_dark_selected.xml
new file mode 100644
index 00000000..778ac9de
--- /dev/null
+++ b/ultrasonic/src/main/res/drawable/list_selector_holo_dark_selected.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ultrasonic/src/main/res/drawable/list_selector_holo_light_selected.xml b/ultrasonic/src/main/res/drawable/list_selector_holo_light_selected.xml
new file mode 100644
index 00000000..9436f1b0
--- /dev/null
+++ b/ultrasonic/src/main/res/drawable/list_selector_holo_light_selected.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ultrasonic/src/main/res/drawable/select_ripple.xml b/ultrasonic/src/main/res/drawable/select_ripple.xml
new file mode 100644
index 00000000..746466c4
--- /dev/null
+++ b/ultrasonic/src/main/res/drawable/select_ripple.xml
@@ -0,0 +1,19 @@
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/ultrasonic/src/main/res/drawable/select_ripple_circle.xml b/ultrasonic/src/main/res/drawable/select_ripple_circle.xml
new file mode 100644
index 00000000..2a270deb
--- /dev/null
+++ b/ultrasonic/src/main/res/drawable/select_ripple_circle.xml
@@ -0,0 +1,19 @@
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/ultrasonic/src/main/res/layout/server_edit.xml b/ultrasonic/src/main/res/layout/server_edit.xml
new file mode 100644
index 00000000..a8f32ccc
--- /dev/null
+++ b/ultrasonic/src/main/res/layout/server_edit.xml
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ultrasonic/src/main/res/layout/server_row.xml b/ultrasonic/src/main/res/layout/server_row.xml
new file mode 100644
index 00000000..0a4c1ed5
--- /dev/null
+++ b/ultrasonic/src/main/res/layout/server_row.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ultrasonic/src/main/res/layout/server_selector.xml b/ultrasonic/src/main/res/layout/server_selector.xml
new file mode 100644
index 00000000..ba6de341
--- /dev/null
+++ b/ultrasonic/src/main/res/layout/server_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml
index 75c7ad43..9174a9d4 100644
--- a/ultrasonic/src/main/res/values-de/strings.xml
+++ b/ultrasonic/src/main/res/values-de/strings.xml
@@ -101,7 +101,7 @@
Mit Stern
Titel
Filme
- Willkommen bei UltraSonic! Die App ist zurzeit unkonfiguriert. Nachdem du deinen Server konfiguriert hast (siehe subsonic.org), bitte Server hinzufügen in den Einstellungen klicken um die Verbindun herzustellen.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
Willkommen
Über
Allgemein
@@ -277,7 +277,7 @@
Album Cover über Bluetooth
Wiedergabe-Benachrichtigungen über Bluetooth senden
Bluetooth-Benachrichtigung
- Server hinzufügen
+ Manage Servers
Server Adresse
Name
Kennwort
@@ -419,6 +419,19 @@
Use default
Available drives:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- 1 Titel
- %d Titel
diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml
index 7d6b8ba8..669dabd5 100644
--- a/ultrasonic/src/main/res/values-es/strings.xml
+++ b/ultrasonic/src/main/res/values-es/strings.xml
@@ -101,7 +101,7 @@
Me gusta
Canciones
Vídeos
- Te damos la bienvenida a UltraSonic! La aplicación no está configurada actualmente. Despues de que hayas configurado tu servidor personal (disponible desde subsonic.org), por favor haz click en Añadir servidor en Configuración para conectarte con él.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
¡Saludos!
Acerca de
Común
@@ -277,7 +277,7 @@
Carátula del Álbum vía Bluetooth
Enviar notificaciones de reproducción vía Bluetooth
Enviar notificaciones Bluetooth
- Añadir servidor
+ Manage Servers
Dirección del servidor
Nombre
Contraseña
@@ -420,6 +420,19 @@
Usar predeterminado
Unidades disponibles:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- 1 canción
- %d canciones
diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml
index 6754c260..6ebadc50 100644
--- a/ultrasonic/src/main/res/values-fr/strings.xml
+++ b/ultrasonic/src/main/res/values-fr/strings.xml
@@ -101,7 +101,7 @@
Favoris
Titres
Vidéos
- Bienvenue dans UltraSonic ! L\'application n\'est pas configurée. Après avoir configuré votre serveur personnel (disponible à partir de subsonic.org), veuillez accéder aux Paramètres et modifier la configuration pour vous y connecter.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
Bienvenue!
À propos
Général
@@ -277,7 +277,7 @@
Pochette de l\'album via Bluetooth
Envoyer des notifications lecture via Bluetooth
Envoyer une notification Bluetooth
- Ajouter un serveur
+ Manage Servers
Adresse du serveur
Nom
Mot de passe
@@ -420,6 +420,19 @@
Use default
Available drives:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- Un titre
- %d titres
diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml
index e5ab13a9..d859fc9f 100644
--- a/ultrasonic/src/main/res/values-hu/strings.xml
+++ b/ultrasonic/src/main/res/values-hu/strings.xml
@@ -101,7 +101,7 @@
Csillaggal megjelölt
Dalok
Videók
- Üdvözli az UltraSonic! Az alkalmazás még nincs beállítva. Miután konfigurálta saját kiszolgálóját (elérhető: subsonic.org), húzza balról jobbra az oldalsávot, lépjen be a Beállítások menüpontba, és adja meg csatlakozási adatokat!
+ Üdvözli az UltraSonic! Az alkalmazás még nincs beállítva. Miután konfigurálta saját kiszolgálóját (elérhető: subsonic.org), húzza balról jobbra az oldalsávot, lépjen be a Beállítások menüpontba, és adja meg csatlakozási adatokat!
Üdvözlet!
Névjegy
Általános
@@ -277,7 +277,7 @@
Albumborító Bluetooth-on
Lejátszási értesítések küldése Bluetooth-on.
Bluetooth értesítések küldése
- Kiszolgáló hozzáadása
+ Kiszolgálók kezelése
Kiszolgáló címe
Név
Jelszó
@@ -420,6 +420,19 @@
Alapért.
Elérhető tárolók:
+ Beállított szerverek
+ Biztosan törölni szeretnéd a szervert?
+ Szerver szerkesztése
+ Szerver létrehozása
+ Biztosan visszalépsz és elveszíted a változtatásokat?
+ Kötelező mező
+ Szerkeszt
+ Töröl
+ Feljebb mozgat
+ Lejjebb mozgat
+ Bejelentkezés
+ Haladó beállítások
+
- 1 dal
- %d dal
diff --git a/ultrasonic/src/main/res/values-nl/strings.xml b/ultrasonic/src/main/res/values-nl/strings.xml
index 70fbbe5f..022b5c74 100644
--- a/ultrasonic/src/main/res/values-nl/strings.xml
+++ b/ultrasonic/src/main/res/values-nl/strings.xml
@@ -101,7 +101,7 @@
Favorieten
Nummers
Video\'s
- Welkom bij UltraSonic! De app is nog niet ingesteld. Nadat je je persoonlijke server hebt opgezet (beschikbaar op subsonic.org), kun je naar de Instellingen gaan en drukken op Server toevoegen.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
Welkom!
Over
Algemeen
@@ -277,7 +277,7 @@
Albumhoezen versturen via Bluetooth
Afspeelmeldingen versturen via Bluetooth
Bluetooth-melding versturen
- Server toevoegen
+ Manage Servers
Serveradres
Naam
Wachtwoord
@@ -420,6 +420,19 @@
Use default
Available drives:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- 1 nummer
- %d nummers
diff --git a/ultrasonic/src/main/res/values-pl/strings.xml b/ultrasonic/src/main/res/values-pl/strings.xml
index 06595852..22d6ca33 100644
--- a/ultrasonic/src/main/res/values-pl/strings.xml
+++ b/ultrasonic/src/main/res/values-pl/strings.xml
@@ -101,7 +101,7 @@
Ulubione
Utwory
Klipy wideo
- Witaj w UltraSonic! Obecnie aplikacja nie jest skonfigurowana. Jeśli masz uruchomiony własny serwer (dostępny na subsonic.org), proszę wybrać Dodaj serwer w Ustawieniach aby się z nim połączyć.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
Witaj!
O aplikacji
Wspólne
@@ -277,7 +277,7 @@
Okładki przez Bluetooth
Wysyła powiadomienia o odtwarzaniu przez Bluetooth
Wysyłaj powiadomienia Bluetooth
- Dodaj serwer
+ Manage Servers
Adres serwera
Nazwa
Hasło
@@ -420,6 +420,19 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
Use default
Available drives:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- 1 utwór
- %d utwory
diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml
index 1f331143..62349fdb 100644
--- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml
+++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml
@@ -101,7 +101,7 @@
Favoritas
Músicas
Vídeos
- Bem-vindo ao UltraSonic! O aplicativo ainda não está configurado. Após configurar seu servidor pessoal (disponível em subsonic.org), clique em Adicionar Servidor em Configurações para a conexão.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
Bem-vindo!
Sobre
Comum
@@ -277,7 +277,7 @@
Arte do Álbum via Bluetooth
Envia notificações de reprodução via Bluetooth
Notificações via Bluetooth
- Adicionar Servidor
+ Manage Servers
Endereço do Servidor
Nome
Senha
@@ -420,6 +420,19 @@
Use default
Available drives:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- 1 música
- %d músicas
diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml
index 68199729..74ca954d 100644
--- a/ultrasonic/src/main/res/values-pt/strings.xml
+++ b/ultrasonic/src/main/res/values-pt/strings.xml
@@ -101,7 +101,7 @@
Favoritas
Músicas
Vídeos
- Bem-vindo ao UltraSonic! O aplicativo ainda não está configurado. Após configurar seu servidor pessoal (disponível em subsonic.org), clique em Adicionar Servidor em Configurações para a conexão.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
Bem-vindo!
Sobre
Comum
@@ -277,7 +277,7 @@
Arte do Álbum via Bluetooth
Envia notificações de reprodução via Bluetooth
Notificações via Bluetooth
- Adicionar Servidor
+ Manage Servers
Endereço do Servidor
Nome
Senha
@@ -420,6 +420,19 @@
Use default
Available drives:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- %d música
- %d músicas
diff --git a/ultrasonic/src/main/res/values/colors.xml b/ultrasonic/src/main/res/values/colors.xml
index 3479317c..15cf3fb8 100644
--- a/ultrasonic/src/main/res/values/colors.xml
+++ b/ultrasonic/src/main/res/values/colors.xml
@@ -9,4 +9,6 @@
#80000000
#ff000000
#fff3f3f3
+ #424242
+ #B1B1B1
\ No newline at end of file
diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml
index b5bfb427..18b8b528 100644
--- a/ultrasonic/src/main/res/values/strings.xml
+++ b/ultrasonic/src/main/res/values/strings.xml
@@ -101,7 +101,7 @@
Starred
Songs
Videos
- Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Add Server in Settings to connect to it.
+ Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it.
Welcome!
About
Common
@@ -279,7 +279,7 @@
Album Art Over Bluetooth
Send playback notifications via Bluetooth
Send Bluetooth Notification
- Add Server
+ Manage Servers
Server Address
Name
Password
@@ -424,6 +424,19 @@
Use default
Available drives:
+ Configured servers
+ Are you sure you want to delete the server?
+ Editing server
+ Create server
+ Are you sure you want to leave and lose your changes?
+ This field is required
+ Edit
+ Delete
+ Move up
+ Move down
+ Authentication
+ Advanced settings
+
- 1 song
- %d songs
diff --git a/ultrasonic/src/main/res/values/styles.xml b/ultrasonic/src/main/res/values/styles.xml
index ed9fae4c..6629d3bc 100644
--- a/ultrasonic/src/main/res/values/styles.xml
+++ b/ultrasonic/src/main/res/values/styles.xml
@@ -1,6 +1,5 @@
-
@@ -93,11 +92,14 @@
+
-
+
+
+
diff --git a/ultrasonic/src/main/res/values/themes.xml b/ultrasonic/src/main/res/values/themes.xml
index 0280d683..84a154ba 100644
--- a/ultrasonic/src/main/res/values/themes.xml
+++ b/ultrasonic/src/main/res/values/themes.xml
@@ -2,6 +2,7 @@
\ No newline at end of file
diff --git a/ultrasonic/src/main/res/xml/server_settings.xml b/ultrasonic/src/main/res/xml/server_settings.xml
deleted file mode 100644
index 6990d52a..00000000
--- a/ultrasonic/src/main/res/xml/server_settings.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file