From f6b176a357767725a8078bd5076eab01f84faa16 Mon Sep 17 00:00:00 2001 From: CappielloAntonio Date: Sat, 1 Jun 2024 15:23:40 +0200 Subject: [PATCH] feat: added the ability for the user to add a local server address and use that address when available --- .../9.json | 1027 +++++++++++++++++ .../java/com/cappielloantonio/tempo/App.java | 34 +- .../tempo/database/AppDatabase.java | 6 +- .../cappielloantonio/tempo/model/Server.kt | 4 + .../tempo/repository/SystemRepository.java | 4 + .../tempo/ui/activity/MainActivity.java | 41 +- .../tempo/ui/dialog/ServerSignupDialog.java | 10 +- .../tempo/ui/fragment/LoginFragment.java | 5 +- .../tempo/util/Preferences.kt | 63 +- .../main/res/layout/dialog_server_signup.xml | 20 + app/src/main/res/values/strings.xml | 1 + 11 files changed, 1179 insertions(+), 36 deletions(-) create mode 100644 app/schemas/com.cappielloantonio.tempo.database.AppDatabase/9.json diff --git a/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/9.json b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/9.json new file mode 100644 index 00000000..0bdcae2f --- /dev/null +++ b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/9.json @@ -0,0 +1,1027 @@ +{ + "formatVersion": 1, + "database": { + "version": 9, + "identityHash": "237a704eed556782438a6493deadaed7", + "entities": [ + { + "tableName": "queue", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trackOrder", + "columnName": "track_order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastPlay", + "columnName": "last_play", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playingChanged", + "columnName": "playing_changed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "streamId", + "columnName": "stream_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parentId", + "columnName": "parent_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDir", + "columnName": "is_dir", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverArtId", + "columnName": "cover_art_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "suffix", + "columnName": "suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedContentType", + "columnName": "transcoding_content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedSuffix", + "columnName": "transcoded_suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isVideo", + "columnName": "is_video", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userRating", + "columnName": "user_rating", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "averageRating", + "columnName": "average_rating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "playCount", + "columnName": "play_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "discNumber", + "columnName": "disc_number", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artist_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bookmarkPosition", + "columnName": "bookmark_position", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalWidth", + "columnName": "original_width", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalHeight", + "columnName": "original_height", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "track_order" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "server", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `local_address` TEXT, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverName", + "columnName": "server_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "localAddress", + "columnName": "local_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isLowSecurity", + "columnName": "low_security", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recent_search", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))", + "fields": [ + { + "fieldPath": "search", + "columnName": "search", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "search" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "download", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `download_uri` TEXT DEFAULT '', `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlist_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playlistName", + "columnName": "playlist_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadState", + "columnName": "download_state", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "downloadUri", + "columnName": "download_uri", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "''" + }, + { + "fieldPath": "parentId", + "columnName": "parent_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDir", + "columnName": "is_dir", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverArtId", + "columnName": "cover_art_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "suffix", + "columnName": "suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedContentType", + "columnName": "transcoding_content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedSuffix", + "columnName": "transcoded_suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isVideo", + "columnName": "is_video", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userRating", + "columnName": "user_rating", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "averageRating", + "columnName": "average_rating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "playCount", + "columnName": "play_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "discNumber", + "columnName": "disc_number", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artist_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bookmarkPosition", + "columnName": "bookmark_position", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalWidth", + "columnName": "original_width", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalHeight", + "columnName": "original_height", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "chronology", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "server", + "columnName": "server", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parentId", + "columnName": "parent_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDir", + "columnName": "is_dir", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverArtId", + "columnName": "cover_art_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "suffix", + "columnName": "suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedContentType", + "columnName": "transcoding_content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedSuffix", + "columnName": "transcoded_suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isVideo", + "columnName": "is_video", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userRating", + "columnName": "user_rating", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "averageRating", + "columnName": "average_rating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "playCount", + "columnName": "play_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "discNumber", + "columnName": "disc_number", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artist_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bookmarkPosition", + "columnName": "bookmark_position", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalWidth", + "columnName": "original_width", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalHeight", + "columnName": "original_height", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favorite", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))", + "fields": [ + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "toStar", + "columnName": "toStar", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "timestamp" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "session_media_item", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`index` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, `stream_id` TEXT, `stream_url` TEXT, `timestamp` INTEGER)", + "fields": [ + { + "fieldPath": "index", + "columnName": "index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parentId", + "columnName": "parent_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDir", + "columnName": "is_dir", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverArtId", + "columnName": "cover_art_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "suffix", + "columnName": "suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedContentType", + "columnName": "transcoding_content_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedSuffix", + "columnName": "transcoded_suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isVideo", + "columnName": "is_video", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userRating", + "columnName": "user_rating", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "averageRating", + "columnName": "average_rating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "playCount", + "columnName": "play_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "discNumber", + "columnName": "disc_number", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artist_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bookmarkPosition", + "columnName": "bookmark_position", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalWidth", + "columnName": "original_width", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "originalHeight", + "columnName": "original_height", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "streamId", + "columnName": "stream_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "streamUrl", + "columnName": "stream_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "index" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '237a704eed556782438a6493deadaed7')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/App.java b/app/src/main/java/com/cappielloantonio/tempo/App.java index 3cf71793..40105eea 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/App.java +++ b/app/src/main/java/com/cappielloantonio/tempo/App.java @@ -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; + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java b/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java index f656b3c4..c47aa987 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java +++ b/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java @@ -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 { diff --git a/app/src/main/java/com/cappielloantonio/tempo/model/Server.kt b/app/src/main/java/com/cappielloantonio/tempo/model/Server.kt index 8b9fcb37..78bfa6ee 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/model/Server.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/model/Server.kt @@ -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, diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java index d4b0f8aa..1e0a37ea 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java +++ b/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java @@ -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 call, @NonNull Response response) { if (response.isSuccessful() && response.body() != null) { pingResult.postValue(response.body().getSubsonicResponse()); + } else { + pingResult.postValue(null); } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java index 45d7d9d5..085b2d17 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java @@ -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()); + } + }); + } } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/ServerSignupDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/ServerSignupDialog.java index c12f8a8e..2fa7ac9d 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/ServerSignupDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/ServerSignupDialog.java @@ -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)); } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java index 63f6fe3b..c73a2bbf 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java @@ -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); diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt index 4781b940..688d3b3b 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt @@ -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() } diff --git a/app/src/main/res/layout/dialog_server_signup.xml b/app/src/main/res/layout/dialog_server_signup.xml index 499ee078..69597d3e 100644 --- a/app/src/main/res/layout/dialog_server_signup.xml +++ b/app/src/main/res/layout/dialog_server_signup.xml @@ -102,6 +102,26 @@ android:textCursorDrawable="@null" /> + + + + + Artists Songs Low security + Local URL Server Name Password Server URL