feat: added the ability for the user to add a local server address and use that address when available

This commit is contained in:
CappielloAntonio 2024-06-01 15:23:40 +02:00
parent aa5290c7ee
commit f6b176a357
11 changed files with 1179 additions and 36 deletions

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@ import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.cappielloantonio.tempo.github.Github;
@ -70,18 +71,12 @@ public class App extends Application {
return preferences;
}
private static Subsonic getSubsonicClient() {
String server = Preferences.getServer();
String username = Preferences.getUser();
String password = Preferences.getPassword();
String token = Preferences.getToken();
String salt = Preferences.getSalt();
boolean isLowSecurity = Preferences.isLowScurity();
public static void refreshSubsonicClient() {
subsonic = getSubsonicClient();
}
SubsonicPreferences preferences = new SubsonicPreferences();
preferences.setServerUrl(server);
preferences.setUsername(username);
preferences.setAuthentication(password, token, salt, isLowSecurity);
private static Subsonic getSubsonicClient() {
SubsonicPreferences preferences = getSubsonicPreferences();
if (preferences.getAuthentication() != null) {
if (preferences.getAuthentication().getPassword() != null)
@ -94,4 +89,21 @@ public class App extends Application {
return new Subsonic(preferences);
}
@NonNull
private static SubsonicPreferences getSubsonicPreferences() {
String server = Preferences.getInUseServerAddress();
String username = Preferences.getUser();
String password = Preferences.getPassword();
String token = Preferences.getToken();
String salt = Preferences.getSalt();
boolean isLowSecurity = Preferences.isLowScurity();
SubsonicPreferences preferences = new SubsonicPreferences();
preferences.setServerUrl(server);
preferences.setUsername(username);
preferences.setAuthentication(password, token, salt, isLowSecurity);
return preferences;
}
}

View file

@ -1,5 +1,6 @@
package com.cappielloantonio.tempo.database;
import androidx.media3.common.util.UnstableApi;
import androidx.room.AutoMigration;
import androidx.room.Database;
import androidx.room.Room;
@ -23,10 +24,11 @@ import com.cappielloantonio.tempo.model.RecentSearch;
import com.cappielloantonio.tempo.model.Server;
import com.cappielloantonio.tempo.model.SessionMediaItem;
@UnstableApi
@Database(
version = 8,
version = 9,
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class},
autoMigrations = {@AutoMigration(from = 7, to = 8)}
autoMigrations = {@AutoMigration(from = 8, to = 9)}
)
@TypeConverters({DateConverters.class})
public abstract class AppDatabase extends RoomDatabase {

View file

@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.model
import android.os.Parcelable
import androidx.annotation.Keep
import androidx.annotation.Nullable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@ -27,6 +28,9 @@ data class Server(
@ColumnInfo(name = "address")
val address: String,
@ColumnInfo(name = "local_address")
val localAddress: String?,
@ColumnInfo(name = "timestamp")
val timestamp: Long,

View file

@ -1,5 +1,7 @@
package com.cappielloantonio.tempo.repository;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
@ -59,6 +61,8 @@ public class SystemRepository {
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null) {
pingResult.postValue(response.body().getSubsonicResponse());
} else {
pingResult.postValue(null);
}
}

View file

@ -6,6 +6,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
@ -18,6 +19,7 @@ import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.BuildConfig;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.broadcast.receiver.ConnectivityStatusBroadcastReceiver;
@ -42,7 +44,7 @@ import java.util.concurrent.ExecutionException;
@UnstableApi
public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivity";
private static final String TAG = "MainActivityLogs";
public ActivityMainBinding bind;
private MainViewModel mainViewModel;
@ -305,10 +307,11 @@ public class MainActivity extends BaseActivity {
Preferences.setToken(null);
Preferences.setPassword(null);
Preferences.setServer(null);
Preferences.setLocalAddress(null);
Preferences.setUser(null);
// TODO Enter all settings to be reset
Preferences.setServerId(null);
Preferences.setOpenSubsonic(false);
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
Preferences.setSkipSilenceMode(false);
Preferences.setDataSavingMode(false);
@ -338,17 +341,37 @@ public class MainActivity extends BaseActivity {
}
private void pingServer() {
if (Preferences.getToken() != null) {
mainViewModel.ping().observe(this, subsonicResponse -> {
if (subsonicResponse == null && Preferences.showServerUnreachableDialog()) {
ServerUnreachableDialog dialog = new ServerUnreachableDialog();
dialog.show(getSupportFragmentManager(), null);
}
if (Preferences.getToken() == null) return;
if (subsonicResponse != null) {
if (Preferences.isInUseServerAddressLocal()) {
mainViewModel.ping().observe(this, subsonicResponse -> {
if (subsonicResponse == null) {
Preferences.setServerSwitchableTimer();
Preferences.switchInUseServerAddress();
App.refreshSubsonicClient();
pingServer();
} else {
Preferences.setOpenSubsonic(subsonicResponse.getOpenSubsonic() != null && subsonicResponse.getOpenSubsonic());
}
});
} else {
if (Preferences.isServerSwitchable()) {
Preferences.setServerSwitchableTimer();
Preferences.switchInUseServerAddress();
App.refreshSubsonicClient();
pingServer();
} else {
mainViewModel.ping().observe(this, subsonicResponse -> {
if (subsonicResponse == null) {
if (Preferences.showServerUnreachableDialog()) {
ServerUnreachableDialog dialog = new ServerUnreachableDialog();
dialog.show(getSupportFragmentManager(), null);
}
} else {
Preferences.setOpenSubsonic(subsonicResponse.getOpenSubsonic() != null && subsonicResponse.getOpenSubsonic());
}
});
}
}
}

View file

@ -28,6 +28,7 @@ public class ServerSignupDialog extends DialogFragment {
private String username;
private String password;
private String server;
private String localAddress;
private boolean lowSecurity = false;
@NonNull
@ -69,6 +70,7 @@ public class ServerSignupDialog extends DialogFragment {
bind.usernameTextView.setText(loginViewModel.getServerToEdit().getUsername());
bind.passwordTextView.setText("");
bind.serverTextView.setText(loginViewModel.getServerToEdit().getAddress());
bind.localAddressTextView.setText(loginViewModel.getServerToEdit().getLocalAddress());
bind.lowSecurityCheckbox.setChecked(loginViewModel.getServerToEdit().isLowSecurity());
}
} else {
@ -97,6 +99,7 @@ public class ServerSignupDialog extends DialogFragment {
username = Objects.requireNonNull(bind.usernameTextView.getText()).toString().trim();
password = bind.lowSecurityCheckbox.isChecked() ? MusicUtil.passwordHexEncoding(Objects.requireNonNull(bind.passwordTextView.getText()).toString()) : Objects.requireNonNull(bind.passwordTextView.getText()).toString();
server = Objects.requireNonNull(bind.serverTextView.getText()).toString().trim();
localAddress = Objects.requireNonNull(bind.localAddressTextView.getText()).toString().trim();
lowSecurity = bind.lowSecurityCheckbox.isChecked();
if (TextUtils.isEmpty(serverName)) {
@ -114,6 +117,11 @@ public class ServerSignupDialog extends DialogFragment {
return false;
}
if (!TextUtils.isEmpty(localAddress) && !localAddress.matches("^https?://(.*)")) {
bind.localAddressTextView.setError(getString(R.string.error_server_prefix));
return false;
}
if (!server.matches("^https?://(.*)")) {
bind.serverTextView.setError(getString(R.string.error_server_prefix));
return false;
@ -124,6 +132,6 @@ public class ServerSignupDialog extends DialogFragment {
private void saveServerPreference() {
String serverID = loginViewModel.getServerToEdit() != null ? loginViewModel.getServerToEdit().getServerId() : UUID.randomUUID().toString();
loginViewModel.addServer(new Server(serverID, this.serverName, this.username, this.password, this.server, System.currentTimeMillis(), this.lowSecurity));
loginViewModel.addServer(new Server(serverID, this.serverName, this.username, this.password, this.server, this.localAddress, System.currentTimeMillis(), this.lowSecurity));
}
}

View file

@ -117,7 +117,7 @@ public class LoginFragment extends Fragment implements ClickCallback {
@Override
public void onServerClick(Bundle bundle) {
Server server = bundle.getParcelable("server_object");
saveServerPreference(server.getServerId(), server.getAddress(), server.getUsername(), server.getPassword(), server.isLowSecurity());
saveServerPreference(server.getServerId(), server.getAddress(), server.getLocalAddress(), server.getUsername(), server.getPassword(), server.isLowSecurity());
SystemRepository systemRepository = new SystemRepository();
systemRepository.checkUserCredential(new SystemCallback() {
@ -141,9 +141,10 @@ public class LoginFragment extends Fragment implements ClickCallback {
dialog.show(activity.getSupportFragmentManager(), null);
}
private void saveServerPreference(String serverId, String server, String user, String password, boolean isLowSecurity) {
private void saveServerPreference(String serverId, String server, String localAddress, String user, String password, boolean isLowSecurity) {
Preferences.setServerId(serverId);
Preferences.setServer(server);
Preferences.setLocalAddress(localAddress);
Preferences.setUser(user);
Preferences.setPassword(password);
Preferences.setLowSecurity(isLowSecurity);

View file

@ -1,13 +1,12 @@
package com.cappielloantonio.tempo.util
import android.util.Log
import com.cappielloantonio.tempo.App
import com.cappielloantonio.tempo.model.HomeSector
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension
import com.google.gson.Gson
object Preferences {
const val THEME = "theme"
private const val SERVER = "server"
@ -20,6 +19,9 @@ object Preferences {
private const val SERVER_ID = "server_id"
private const val OPEN_SUBSONIC = "open_subsonic"
private const val OPEN_SUBSONIC_EXTENSIONS = "open_subsonic_extensions"
private const val LOCAL_ADDRESS = "local_address"
private const val IN_USE_SERVER_ADDRESS = "in_use_server_address"
private const val NEXT_SERVER_SWITCH = "next_server_switch"
private const val PLAYBACK_SPEED = "playback_speed"
private const val SKIP_SILENCE = "skip_silence"
private const val IMAGE_CACHE_SIZE = "image_cache_size"
@ -64,6 +66,7 @@ object Preferences {
@JvmStatic
fun getServer(): String? {
Log.d("Preferences++", "getServer()")
return App.getInstance().preferences.getString(SERVER, null)
}
@ -152,6 +155,44 @@ object Preferences {
App.getInstance().preferences.edit().putString(OPEN_SUBSONIC_EXTENSIONS, Gson().toJson(extension)).apply()
}
@JvmStatic
fun getLocalAddress(): String? {
return App.getInstance().preferences.getString(LOCAL_ADDRESS, null)
}
@JvmStatic
fun setLocalAddress(address: String?) {
App.getInstance().preferences.edit().putString(LOCAL_ADDRESS, address).apply()
}
@JvmStatic
fun getInUseServerAddress(): String? {
return App.getInstance().preferences.getString(IN_USE_SERVER_ADDRESS, getServer())
}
@JvmStatic
fun isInUseServerAddressLocal(): Boolean {
return getInUseServerAddress() == getLocalAddress()
}
@JvmStatic
fun switchInUseServerAddress() {
val inUseAddress = if (getInUseServerAddress() == getServer()) getLocalAddress() else getServer()
App.getInstance().preferences.edit().putString(IN_USE_SERVER_ADDRESS, inUseAddress).apply()
}
@JvmStatic
fun isServerSwitchable(): Boolean {
return App.getInstance().preferences.getLong(
NEXT_SERVER_SWITCH, 0
) + 600000 < System.currentTimeMillis()
}
@JvmStatic
fun setServerSwitchableTimer() {
App.getInstance().preferences.edit().putLong(NEXT_SERVER_SWITCH, System.currentTimeMillis()).apply()
}
@JvmStatic
fun askForOptimization(): Boolean {
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
@ -230,7 +271,7 @@ object Preferences {
@JvmStatic
fun setDataSavingMode(isDataSavingModeEnabled: Boolean) {
App.getInstance().preferences.edit().putBoolean(DATA_SAVING_MODE, isDataSavingModeEnabled)
.apply()
.apply()
}
@JvmStatic
@ -241,14 +282,14 @@ object Preferences {
@JvmStatic
fun setStarredSyncEnabled(isStarredSyncEnabled: Boolean) {
App.getInstance().preferences.edit().putBoolean(
SYNC_STARRED_TRACKS_FOR_OFFLINE_USE, isStarredSyncEnabled
SYNC_STARRED_TRACKS_FOR_OFFLINE_USE, isStarredSyncEnabled
).apply()
}
@JvmStatic
fun showServerUnreachableDialog(): Boolean {
return App.getInstance().preferences.getLong(
SERVER_UNREACHABLE, 0
SERVER_UNREACHABLE, 0
) + 86400000 < System.currentTimeMillis()
}
@ -333,24 +374,24 @@ object Preferences {
@JvmStatic
fun setDownloadStoragePreference(storagePreference: Int) {
return App.getInstance().preferences.edit().putString(
DOWNLOAD_STORAGE,
storagePreference.toString()
DOWNLOAD_STORAGE,
storagePreference.toString()
).apply()
}
@JvmStatic
fun getDefaultDownloadViewType(): String {
return App.getInstance().preferences.getString(
DEFAULT_DOWNLOAD_VIEW_TYPE,
Constants.DOWNLOAD_TYPE_TRACK
DEFAULT_DOWNLOAD_VIEW_TYPE,
Constants.DOWNLOAD_TYPE_TRACK
)!!
}
@JvmStatic
fun setDefaultDownloadViewType(viewType: String) {
return App.getInstance().preferences.edit().putString(
DEFAULT_DOWNLOAD_VIEW_TYPE,
viewType
DEFAULT_DOWNLOAD_VIEW_TYPE,
viewType
).apply()
}

View file

@ -102,6 +102,26 @@
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/local_address_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_local_address"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<CheckBox
android:id="@+id/low_security_checkbox"
android:layout_width="match_parent"

View file

@ -229,6 +229,7 @@
<string name="search_title_artist">Artists</string>
<string name="search_title_song">Songs</string>
<string name="server_signup_dialog_action_low_security">Low security</string>
<string name="server_signup_dialog_hint_local_address">Local URL</string>
<string name="server_signup_dialog_hint_name">Server Name</string>
<string name="server_signup_dialog_hint_password">Password</string>
<string name="server_signup_dialog_hint_url">Server URL</string>