Several improvements in UI (better colors for night mode, added icons).

Better handling of startup (login activity would reset if put in the background).
Allow use of schemeless hostname for login.
Destroy main activity and clear cache on logout.
Change of endpoint for favorites retrieval for one with much better performance.
This commit is contained in:
Antoine POPINEAU 2019-10-23 20:21:18 +02:00
parent 78468167ca
commit e84455390b
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
22 changed files with 200 additions and 97 deletions

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.apognu.otter">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<application
android:name="com.github.apognu.otter.Otter"
@ -14,31 +13,39 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:screenOrientation="portrait"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- <meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider"/> -->
<activity android:name="com.github.apognu.otter.activities.LoginActivity" android:noHistory="true" android:launchMode="singleInstance">
<activity
android:name="com.github.apognu.otter.activities.SplashActivity"
android:launchMode="singleInstance"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.github.apognu.otter.activities.MainActivity"/>
<activity android:name="com.github.apognu.otter.activities.SearchActivity" android:launchMode="singleTop"/>
<activity android:name="com.github.apognu.otter.activities.SettingsActivity"/>
<activity android:name="com.github.apognu.otter.activities.LicencesActivity"/>
<activity
android:name="com.github.apognu.otter.activities.LoginActivity"
android:launchMode="singleInstance" />
<activity android:name="com.github.apognu.otter.activities.MainActivity" />
<activity
android:name="com.github.apognu.otter.activities.SearchActivity"
android:launchMode="singleTop" />
<activity android:name="com.github.apognu.otter.activities.SettingsActivity" />
<activity android:name="com.github.apognu.otter.activities.LicencesActivity" />
<service android:name="com.github.apognu.otter.playback.PlayerService"/>
<service android:name="com.github.apognu.otter.playback.PlayerService" />
<receiver android:name="com.github.apognu.otter.playback.MediaControlActionReceiver"/>
<receiver android:name="com.github.apognu.otter.playback.MediaControlActionReceiver" />
</application>

View File

@ -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 =

View File

@ -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))

View File

@ -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()

View File

@ -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)
}
}
}
}
}

View File

@ -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<Favorite, FavoritesAdapter.ViewHolder>() {
class FavoritesAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener, val fromQueue: Boolean = false) : FunkwhaleAdapter<Track, FavoritesAdapter.ViewHolder>() {
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")
}

View File

@ -11,7 +11,7 @@ import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class FavoritesFragment : FunkwhaleFragment<Favorite, FavoritesAdapter>() {
class FavoritesFragment : FunkwhaleFragment<Track, FavoritesAdapter>() {
override val viewRes = R.layout.fragment_favorites
override val recycler: RecyclerView get() = favorites
@ -22,7 +22,6 @@ class FavoritesFragment : FunkwhaleFragment<Favorite, FavoritesAdapter>() {
adapter = FavoritesAdapter(context, FavoriteListener())
repository = FavoritesRepository(context)
favoritesRepository = FavoritesRepository(context)
watchEventBus()
}
@ -38,7 +37,7 @@ class FavoritesFragment : FunkwhaleFragment<Favorite, FavoritesAdapter>() {
}
play.setOnClickListener {
CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled().map { it.track }))
CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled()))
}
}

View File

@ -12,16 +12,16 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.runBlocking
import java.io.BufferedReader
class FavoritesRepository(override val context: Context?) : Repository<Favorite, FavoritesCache>() {
override val cacheId = "favorites"
override val upstream = HttpUpstream<Favorite, FunkwhaleResponse<Favorite>>(HttpUpstream.Behavior.AtOnce, "/api/v1/favorites/tracks?playable=true", object : TypeToken<FavoritesResponse>() {}.type)
class FavoritesRepository(override val context: Context?) : Repository<Track, TracksCache>() {
override val cacheId = "favorites.v2"
override val upstream = HttpUpstream<Track, FunkwhaleResponse<Track>>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks?favorites=true&playable=true", object : TypeToken<TracksResponse>() {}.type)
override fun cache(data: List<Favorite>) = FavoritesCache(data)
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(FavoritesCache::class.java).deserialize(reader)
override fun cache(data: List<Track>) = TracksCache(data)
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader)
override fun onDataFetched(data: List<Favorite>) = data.map {
override fun onDataFetched(data: List<Track>) = data.map {
it.apply {
it.track.favorite = true
it.favorite = true
}
}
@ -53,3 +53,11 @@ class FavoritesRepository(override val context: Context?) : Repository<Favorite,
}
}
}
class FavoritedRepository(override val context: Context?) : Repository<Int, FavoritedCache>() {
override val cacheId = "favorited"
override val upstream = HttpUpstream<Int, FunkwhaleResponse<Int>>(HttpUpstream.Behavior.Single, "/api/v1/favorites/tracks/all?playable=true", object : TypeToken<FavoritedResponse>() {}.type)
override fun cache(data: List<Int>) = FavoritedCache(data)
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(FavoritedCache::class.java).deserialize(reader)
}

View File

@ -34,7 +34,7 @@ class HttpUpstream<D : Any, R : FunkwhaleResponse<D>>(private val behavior: Beha
}
override fun fetch(data: List<D>): Channel<Repository.Response<D>>? {
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

View File

@ -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<PlaylistTrack>): List<PlaylistTrack> = 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
}
}

View File

@ -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<Track>): List<Track> = 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
}
}

View File

@ -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<Track>): List<Track> = 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
}
}

View File

@ -8,7 +8,7 @@ class AlbumsCache(data: List<Album>) : CacheItem<Album>(data)
class TracksCache(data: List<Track>) : CacheItem<Track>(data)
class PlaylistsCache(data: List<Playlist>) : CacheItem<Playlist>(data)
class PlaylistTracksCache(data: List<PlaylistTrack>) : CacheItem<PlaylistTrack>(data)
class FavoritesCache(data: List<Favorite>) : CacheItem<Favorite>(data)
class FavoritedCache(data: List<Int>) : CacheItem<Int>(data)
class QueueCache(data: List<Track>) : CacheItem<Track>(data)
abstract class FunkwhaleResponse<D : Any> {
@ -18,6 +18,10 @@ abstract class FunkwhaleResponse<D : Any> {
abstract fun getData(): List<D>
}
data class UserResponse(override val count: Int, override val next: String?, val results: List<Artist>) : FunkwhaleResponse<Artist>() {
override fun getData() = results
}
data class ArtistsResponse(override val count: Int, override val next: String?, val results: List<Artist>) : FunkwhaleResponse<Artist>() {
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<Favorite>) : FunkwhaleResponse<Favorite>() {
override fun getData() = results
data class FavoritedResponse(override val count: Int, override val next: String?, val results: List<Favorited>) : FunkwhaleResponse<Int>() {
override fun getData() = results.map { it.track }
}
data class PlaylistsResponse(override val count: Int, override val next: String?, val results: List<Playlist>) : FunkwhaleResponse<Playlist>() {
@ -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,

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-0.89,0 -1.74,-0.2 -2.5,-0.55C11.56,16.5 13,14.42 13,12s-1.44,-4.5 -3.5,-5.45C10.26,6.2 11.11,6 12,6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM12,8h-2L10,4h2v4zM15,8h-2L13,4h2v4zM18,8h-2L16,4h2v4z"/>
</vector>

View File

@ -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">
<com.google.android.material.textfield.TextInputEditText
@ -54,6 +55,7 @@
android:hint="@string/login_username"
android:textColorHint="@drawable/login_input"
app:boxStrokeColor="@drawable/login_input"
app:errorTextAppearance="@style/AppTheme.ErrorStyle"
app:hintTextColor="@drawable/login_input">
<com.google.android.material.textfield.TextInputEditText
@ -75,6 +77,7 @@
android:hint="@string/login_password"
android:textColorHint="@drawable/login_input"
app:boxStrokeColor="@drawable/login_input"
app:errorTextAppearance="@style/AppTheme.ErrorStyle"
app:hintTextColor="@drawable/login_input"
app:passwordToggleEnabled="true">
@ -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" />
</LinearLayout>

View File

@ -3,12 +3,16 @@
<color name="surface">#121212</color>
<color name="colorPrimary">#283f4e</color>
<color name="colorAccent">#99440c</color>
<color name="colorAccent">#f1b44f</color>
<color name="colorSelected">#525252</color>
<color name="colorFavorite">#eba999</color>
<color name="itemTitle">#caffffff</color>
<color name="controlForeground">#caffffff</color>
<color name="controlColor">#53bce7</color>
<color name="controlColor">#327eae</color>
<color name="whiteWhileLight">#000000</color>
<color name="blackWhileLight">#ffffff</color>
</resources>

View File

@ -6,6 +6,7 @@
<color name="colorPrimary">#327eae</color>
<color name="colorPrimaryDark">#3d3e40</color>
<color name="colorAccent">#d35400</color>
<color name="colorError">#b94705</color>
<color name="colorSelected">#dadada</color>
<color name="colorFavorite">#e17055</color>
@ -14,4 +15,7 @@
<color name="controlForeground">@color/colorPrimary</color>
<color name="controlColor">@color/colorPrimary</color>
<color name="whiteWhileLight">#ffffff</color>
<color name="blackWhileLight">#000000</color>
</resources>

View File

@ -27,8 +27,9 @@
<style name="AppTheme.Title">
<item name="android:fontFamily">sans-serif-light</item>
<item name="android:textSize">28sp</item>
<item name="android:textSize">24sp</item>
<item name="android:textColor">@color/itemTitle</item>
<item name="android:textStyle">bold</item>
</style>
<style name="AppTheme.ItemTitle">
@ -44,6 +45,7 @@
<style name="AppTheme.Preference" parent="PreferenceThemeOverlay">
<item name="android:textColor">@color/itemTitle</item>
<item name="android:tint">@color/blackWhileLight</item>
<item name="preferenceCategoryStyle">@style/AppTheme.PreferenceCategory</item>
</style>
@ -74,4 +76,8 @@
<item name="android:background">@android:color/transparent</item>
</style>
<style name="AppTheme.ErrorStyle" parent="@android:style/TextAppearance">
<item name="android:textColor">@color/colorError</item>
</style>
</resources>

View File

@ -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" />
<SeekBarPreference
android:defaultValue="1"
android:icon="@drawable/storage"
android:key="media_cache_size"
android:max="5"
android:min="0"
@ -28,14 +30,17 @@
android:defaultValue="system"
android:entries="@array/night_mode"
android:entryValues="@array/night_mode_values"
android:icon="@drawable/brightness"
android:key="night_mode"
android:title="@string/settings_night_mode" />
<Preference
android:icon="@drawable/favorite"
android:key="oss_licences"
android:title="@string/title_oss_licences" />
<Preference
android:icon="@drawable/logout"
android:key="logout"
android:title="@string/settings_logout" />