diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a289d53..d36037c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,12 +1,11 @@ - - - + + - + - + - - + + - + - - - - + + + + + - + - + diff --git a/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt index 578c1f5..ca8f0a5 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt @@ -1,8 +1,8 @@ package com.github.apognu.otter.activities -import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.github.apognu.otter.R import com.github.apognu.otter.fragments.LoginDialog @@ -19,33 +19,29 @@ import kotlinx.coroutines.launch data class FwCredentials(val token: String) class LoginActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_login) + } + override fun onResume() { super.onResume() - getSharedPreferences(AppContext.PREFS_CREDENTIALS, Context.MODE_PRIVATE).apply { - when (contains("access_token")) { - true -> Intent(this@LoginActivity, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NO_ANIMATION - - startActivity(this) - } - - false -> setContentView(R.layout.activity_login) - } - } - login?.setOnClickListener { - val hostname = hostname.text.toString().trim() + var hostname = hostname.text.toString().trim() val username = username.text.toString() val password = password.text.toString() try { if (hostname.isEmpty()) throw Exception(getString(R.string.login_error_hostname)) - val url = Uri.parse(hostname) + Uri.parse(hostname).apply { + if (scheme == "http") { + throw Exception(getString(R.string.login_error_hostname_https)) + } - if (url.scheme != "https") { - throw Exception(getString(R.string.login_error_hostname_https)) + if (scheme == null) hostname = "https://${hostname}" } } catch (e: Exception) { val message = diff --git a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt index e97d4f6..a1b918e 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt @@ -19,6 +19,7 @@ import com.github.apognu.otter.fragments.BrowseFragment import com.github.apognu.otter.fragments.QueueFragment import com.github.apognu.otter.playback.MediaControlsManager import com.github.apognu.otter.playback.PlayerService +import com.github.apognu.otter.repositories.FavoritedRepository import com.github.apognu.otter.repositories.FavoritesRepository import com.github.apognu.otter.repositories.Repository import com.github.apognu.otter.utils.* @@ -32,7 +33,12 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { + enum class ResultCode(val code: Int) { + LOGOUT(1001) + } + private val favoriteRepository = FavoritesRepository(this) + private val favoriteCheckRepository = FavoritedRepository(this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -127,12 +133,25 @@ class MainActivity : AppCompatActivity() { R.id.nav_queue -> launchDialog(QueueFragment()) R.id.nav_search -> startActivity(Intent(this, SearchActivity::class.java)) - R.id.settings -> startActivity(Intent(this, SettingsActivity::class.java)) + R.id.settings -> startActivityForResult(Intent(this, SettingsActivity::class.java), 0) } return true } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (resultCode == ResultCode.LOGOUT.code) { + Intent(this, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + + startActivity(this) + finish() + } + } + } + private fun launchFragment(fragment: Fragment) { supportFragmentManager.fragments.lastOrNull()?.also { oldFragment -> oldFragment.enterTransition = null @@ -235,11 +254,10 @@ class MainActivity : AppCompatActivity() { .centerCrop() .into(now_playing_details_cover) - favoriteRepository.fetch().untilNetwork(IO) { favorites -> + favoriteCheckRepository.fetch().untilNetwork(IO) { favorites -> GlobalScope.launch(Main) { - val favorites = favorites.map { it.track.id } - track.favorite = favorites.contains(track.id) + when (track.favorite) { true -> now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite)) false -> now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground)) diff --git a/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt index 8138344..0ea059b 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt @@ -56,12 +56,10 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP .setPositiveButton(android.R.string.yes) { _, _ -> PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).clear() - Intent(context, LoginActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + context.cacheDir.deleteRecursively() - startActivity(this) - activity?.finish() - } + activity?.setResult(MainActivity.ResultCode.LOGOUT.code) + activity?.finish() } .setNegativeButton(android.R.string.no, null) .show() diff --git a/app/src/main/java/com/github/apognu/otter/activities/SplashActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SplashActivity.kt new file mode 100644 index 0000000..7c6b30a --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/activities/SplashActivity.kt @@ -0,0 +1,29 @@ +package com.github.apognu.otter.activities + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.github.apognu.otter.utils.AppContext + +class SplashActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + getSharedPreferences(AppContext.PREFS_CREDENTIALS, Context.MODE_PRIVATE).apply { + when (contains("access_token")) { + true -> Intent(this@SplashActivity, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NO_ANIMATION + + startActivity(this) + } + + false -> Intent(this@SplashActivity, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NO_ANIMATION + + startActivity(this) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt index 308e641..193a5b7 100644 --- a/app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt +++ b/app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt @@ -18,7 +18,7 @@ import jp.wasabeef.picasso.transformations.RoundedCornersTransformation import kotlinx.android.synthetic.main.row_track.view.* import java.util.* -class FavoritesAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener, val fromQueue: Boolean = false) : FunkwhaleAdapter() { +class FavoritesAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener, val fromQueue: Boolean = false) : FunkwhaleAdapter() { interface OnFavoriteListener { fun onToggleFavorite(id: Int, state: Boolean) } @@ -28,7 +28,7 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen override fun getItemCount() = data.size override fun getItemId(position: Int): Long { - return data[position].track.id.toLong() + return data[position].id.toLong() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -44,14 +44,14 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen val favorite = data[position] Picasso.get() - .maybeLoad(maybeNormalizeUrl(favorite.track.album.cover.original)) + .maybeLoad(maybeNormalizeUrl(favorite.album.cover.original)) .fit() .placeholder(R.drawable.cover) .transform(RoundedCornersTransformation(16, 0)) .into(holder.cover) - holder.title.text = favorite.track.title - holder.artist.text = favorite.track.artist.name + holder.title.text = favorite.title + holder.artist.text = favorite.artist.name Build.VERSION_CODES.P.onApi( { @@ -64,19 +64,19 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen }) - if (favorite.track == currentTrack || favorite.track.current) { + if (favorite == currentTrack || favorite.current) { holder.title.setTypeface(holder.title.typeface, Typeface.BOLD) holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD) } context?.let { - when (favorite.track.favorite) { + when (favorite.favorite) { true -> holder.favorite.setColorFilter(context.getColor(R.color.colorFavorite)) false -> holder.favorite.setColorFilter(context.getColor(R.color.colorSelected)) } holder.favorite.setOnClickListener { - favoriteListener.onToggleFavorite(favorite.track.id, !favorite.track.favorite) + favoriteListener.onToggleFavorite(favorite.id, !favorite.favorite) data.remove(favorite) notifyItemRemoved(holder.adapterPosition) @@ -90,9 +90,9 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen setOnMenuItemClickListener { when (it.itemId) { - R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(favorite.track))) - R.id.track_play_next -> CommandBus.send(Command.PlayNext(favorite.track)) - R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(favorite.track)) + R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(favorite))) + R.id.track_play_next -> CommandBus.send(Command.PlayNext(favorite)) + R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(favorite)) } true @@ -132,7 +132,7 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen true -> CommandBus.send(Command.PlayTrack(layoutPosition)) false -> { data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply { - CommandBus.send(Command.ReplaceQueue(this.map { it.track })) + CommandBus.send(Command.ReplaceQueue(this)) context.toast("All tracks were added to your queue") } diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt index dc72bfe..ef1c815 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -class FavoritesFragment : FunkwhaleFragment() { +class FavoritesFragment : FunkwhaleFragment() { override val viewRes = R.layout.fragment_favorites override val recycler: RecyclerView get() = favorites @@ -22,7 +22,6 @@ class FavoritesFragment : FunkwhaleFragment() { adapter = FavoritesAdapter(context, FavoriteListener()) repository = FavoritesRepository(context) - favoritesRepository = FavoritesRepository(context) watchEventBus() } @@ -38,7 +37,7 @@ class FavoritesFragment : FunkwhaleFragment() { } play.setOnClickListener { - CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled().map { it.track })) + CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled())) } } diff --git a/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt index b5ae2fd..9446970 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt @@ -12,16 +12,16 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.runBlocking import java.io.BufferedReader -class FavoritesRepository(override val context: Context?) : Repository() { - override val cacheId = "favorites" - override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/favorites/tracks?playable=true", object : TypeToken() {}.type) +class FavoritesRepository(override val context: Context?) : Repository() { + override val cacheId = "favorites.v2" + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks?favorites=true&playable=true", object : TypeToken() {}.type) - override fun cache(data: List) = FavoritesCache(data) - override fun uncache(reader: BufferedReader) = gsonDeserializerOf(FavoritesCache::class.java).deserialize(reader) + override fun cache(data: List) = TracksCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) - override fun onDataFetched(data: List) = data.map { + override fun onDataFetched(data: List) = data.map { it.apply { - it.track.favorite = true + it.favorite = true } } @@ -53,3 +53,11 @@ class FavoritesRepository(override val context: Context?) : Repository() { + override val cacheId = "favorited" + override val upstream = HttpUpstream>(HttpUpstream.Behavior.Single, "/api/v1/favorites/tracks/all?playable=true", object : TypeToken() {}.type) + + override fun cache(data: List) = FavoritedCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(FavoritedCache::class.java).deserialize(reader) +} diff --git a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt index 21197bf..7c56844 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt @@ -34,7 +34,7 @@ class HttpUpstream>(private val behavior: Beha } override fun fetch(data: List): Channel>? { - if (behavior == Behavior.Single && data.isNotEmpty()) return null + if (behavior == Behavior.Single && data.isNotEmpty()) return null val page = ceil(data.size / AppContext.PAGE_SIZE.toDouble()).toInt() + 1 diff --git a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt index 18786a2..f3ce397 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt @@ -1,7 +1,10 @@ package com.github.apognu.otter.repositories import android.content.Context -import com.github.apognu.otter.utils.* +import com.github.apognu.otter.utils.FunkwhaleResponse +import com.github.apognu.otter.utils.PlaylistTrack +import com.github.apognu.otter.utils.PlaylistTracksCache +import com.github.apognu.otter.utils.PlaylistTracksResponse import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.gson.reflect.TypeToken import kotlinx.coroutines.runBlocking @@ -15,17 +18,10 @@ class PlaylistTracksRepository(override val context: Context?, playlistId: Int) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistTracksCache::class.java).deserialize(reader) override fun onDataFetched(data: List): List = runBlocking { - val favorites = FavoritesRepository(context).fetch(Origin.Network.origin).receive().data - - log(favorites.toString()) + val favorites = FavoritedRepository(context).fetch(Origin.Network.origin).receive().data data.map { track -> - val favorite = favorites.find { it.track.id == track.track.id } - - if (favorite != null) { - track.track.favorite = true - } - + track.track.favorite = favorites.contains(track.track.id) track } } diff --git a/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt index df51ea8..f8cb44d 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt @@ -18,15 +18,10 @@ class SearchRepository(override val context: Context?, query: String) : Reposito override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) override fun onDataFetched(data: List): List = runBlocking { - val favorites = FavoritesRepository(context).fetch(Origin.Network.origin).receive().data + val favorites = FavoritedRepository(context).fetch(Origin.Network.origin).receive().data data.map { track -> - val favorite = favorites.find { it.track.id == track.id } - - if (favorite != null) { - track.favorite = true - } - + track.favorite = favorites.contains(track.id) track } } diff --git a/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt index a6f16e8..7ebe720 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt @@ -18,15 +18,10 @@ class TracksRepository(override val context: Context?, albumId: Int) : Repositor override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) override fun onDataFetched(data: List): List = runBlocking { - val favorites = FavoritesRepository(context).fetch(Origin.Network.origin).receive().data + val favorites = FavoritedRepository(context).fetch(Origin.Network.origin).receive().data data.map { track -> - val favorite = favorites.find { it.track.id == track.id } - - if (favorite != null) { - track.favorite = true - } - + track.favorite = favorites.contains(track.id) track } } diff --git a/app/src/main/java/com/github/apognu/otter/utils/Models.kt b/app/src/main/java/com/github/apognu/otter/utils/Models.kt index d34ff52..90362f5 100644 --- a/app/src/main/java/com/github/apognu/otter/utils/Models.kt +++ b/app/src/main/java/com/github/apognu/otter/utils/Models.kt @@ -8,7 +8,7 @@ class AlbumsCache(data: List) : CacheItem(data) class TracksCache(data: List) : CacheItem(data) class PlaylistsCache(data: List) : CacheItem(data) class PlaylistTracksCache(data: List) : CacheItem(data) -class FavoritesCache(data: List) : CacheItem(data) +class FavoritedCache(data: List) : CacheItem(data) class QueueCache(data: List) : CacheItem(data) abstract class FunkwhaleResponse { @@ -18,6 +18,10 @@ abstract class FunkwhaleResponse { abstract fun getData(): List } +data class UserResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { + override fun getData() = results +} + data class ArtistsResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { override fun getData() = results } @@ -30,8 +34,8 @@ data class TracksResponse(override val count: Int, override val next: String?, v override fun getData() = results } -data class FavoritesResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { - override fun getData() = results +data class FavoritedResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { + override fun getData() = results.map { it.track } } data class PlaylistsResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { @@ -100,7 +104,7 @@ data class Track( } } -data class Favorite(val id: Int, val track: Track) +data class Favorited(val track: Int) data class Playlist( val id: Int, diff --git a/app/src/main/res/drawable/brightness.xml b/app/src/main/res/drawable/brightness.xml new file mode 100644 index 0000000..bc3cdec --- /dev/null +++ b/app/src/main/res/drawable/brightness.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/logout.xml b/app/src/main/res/drawable/logout.xml new file mode 100644 index 0000000..6f40d77 --- /dev/null +++ b/app/src/main/res/drawable/logout.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/quality.xml b/app/src/main/res/drawable/quality.xml new file mode 100644 index 0000000..1d683ba --- /dev/null +++ b/app/src/main/res/drawable/quality.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/storage.xml b/app/src/main/res/drawable/storage.xml new file mode 100644 index 0000000..f9ad72d --- /dev/null +++ b/app/src/main/res/drawable/storage.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 6a65f12..681af59 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -33,6 +33,7 @@ android:hint="@string/login_hostname" android:textColorHint="@drawable/login_input" app:boxStrokeColor="@drawable/login_input" + app:errorTextAppearance="@style/AppTheme.ErrorStyle" app:hintTextColor="@drawable/login_input"> @@ -94,5 +97,5 @@ android:layout_height="wrap_content" android:backgroundTint="@color/colorAccent" android:text="@string/login_submit" - android:textColor="@android:color/white" /> + android:textColor="@color/whiteWhileLight" /> diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index adf3fb2..dc8cb14 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -3,12 +3,16 @@ #121212 #283f4e - #99440c + #f1b44f #525252 + #eba999 + #caffffff #caffffff + #53bce7 - #327eae + #000000 + #ffffff \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d07417e..aa9d2b8 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,6 +6,7 @@ #327eae #3d3e40 #d35400 + #b94705 #dadada #e17055 @@ -14,4 +15,7 @@ @color/colorPrimary @color/colorPrimary + + #ffffff + #000000 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index fa2b4c7..45f5eee 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -27,8 +27,9 @@ @@ -74,4 +76,8 @@ @android:color/transparent + + diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 240407f..bffd0c9 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -8,11 +8,13 @@ android:defaultValue="quality" android:entries="@array/media_qualities" android:entryValues="@array/media_qualities_values" + android:icon="@drawable/quality" android:key="media_quality" android:title="@string/settings_media_quality" />