Skip to content

Commit

Permalink
Optimise startup, change main layout and font...
Browse files Browse the repository at this point in the history
- Greatly reduce app startup time
- Added radio builder button to the top bar
- Move pinned items and artist layouts
- Tweak minimised now playing design
- Handle Discord rate limit (untested)
- Invalidate cache on language changed
- Use HC Maru Gothic font for all locales
  • Loading branch information
toasterofbread committed May 6, 2023
1 parent f9b743e commit b48e0a1
Show file tree
Hide file tree
Showing 126 changed files with 1,169 additions and 797 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import com.my.kizzyrpc.model.Metadata
import com.my.kizzyrpc.model.Timestamps
import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.exception.KordInitializationException
import dev.kord.rest.NamedFile
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.service.ChannelService
import io.ktor.client.request.forms.*
import io.ktor.utils.io.jvm.javaio.*
import kotlinx.coroutines.delay
import java.io.ByteArrayOutputStream

actual class DiscordStatus actual constructor(
Expand Down Expand Up @@ -93,7 +95,25 @@ actual class DiscordStatus actual constructor(
check(bot_token != null)
check(custom_images_channel_id != null)

val kord = Kord(bot_token)
var kord: Kord
try {
kord = Kord(bot_token)
}
catch (e: KordInitializationException) {
val message = e.message ?: throw e

val start = message.indexOf("retry_after")
if (start == -1) {
throw e
}

val retry_after = message.substring(start + 14, message.indexOf("\"", start + 14)).toFloatOrNull()
?: throw NotImplementedError(message)

delay((retry_after * 1000L).toLong())
kord = Kord(bot_token)
}

val result = with(kord.rest.channel) {
val channel = Snowflake(custom_images_channel_id)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ import android.view.Window
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import com.spectre7.spmp.model.YoutubeMusicAuthInfo
import com.spectre7.spmp.ui.layout.OverlayPage
import com.spectre7.spmp.ui.layout.PlayerViewContext
import com.spectre7.utils.getStringTemp
import com.spectre7.utils.getStringTODO
import com.spectre7.utils.getString

private const val ERROR_NOTIFICATION_CHANNEL_ID = "download_error_channel"
Expand Down Expand Up @@ -174,10 +171,10 @@ actual class PlatformContext(context: Context) {
clipboard.setText(AnnotatedString(getText()))

if (name != null) {
sendToast(getStringTemp("Copied {name} to clipboard").replace("{name}", name))
sendToast(getStringTODO("Copied {name} to clipboard").replace("{name}", name))
}
else {
sendToast(getStringTemp("Copied to clipboard"))
sendToast(getStringTODO("Copied to clipboard"))
}
}) {
Icon(Icons.Filled.ContentCopy, null, Modifier.size(20.dp))
Expand Down
74 changes: 29 additions & 45 deletions shared/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ import com.spectre7.spmp.ui.layout.PlayerView
import com.spectre7.spmp.ui.theme.ApplicationTheme
import com.spectre7.spmp.ui.theme.Theme
import com.spectre7.utils.*
import java.io.File
import java.util.*
import kotlin.concurrent.thread
import kotlin.math.roundToInt

expect fun getPlatformName(): String

object SpMp {

lateinit var context: PlatformContext
private val LANGUAGES = listOf("af", "am", "ar", "as", "az", "be", "bg", "bn", "bs", "ca", "cs", "da", "de", "el", "en-GB", "en-IN", "en", "es", "es-419", "es-US", "et", "eu", "fa", "fi", "fil", "fr-CA", "fr", "gl", "gu", "hi", "hr", "hu", "hy", "id", "is", "it", "iw", "ja", "ka", "kk", "km", "kn", "ko", "ky", "lo", "lt", "lv", "mk", "ml", "mn", "mr", "ms", "my", "no", "ne", "nl", "or", "pa", "pl", "pt", "pt-PT", "ro", "ru", "si", "sk", "sl", "sq", "sr-Latn", "sr", "sv", "sw", "ta", "te", "th", "tr", "uk", "ur", "uz", "vi", "zh-CN", "zh-HK", "zh-TW", "zu")

lateinit var context: PlatformContext
lateinit var error_manager: ErrorManager
lateinit var languages: Map<String, Map<String, String>>

private var _yt_ui_translation: YoutubeUITranslation? = null
val yt_ui_translation: YoutubeUITranslation get() = _yt_ui_translation!!
Expand All @@ -63,33 +63,21 @@ object SpMp {
private val prefs_change_listener =
object : ProjectPreferences.Listener {
override fun onChanged(prefs: ProjectPreferences, key: String) {
if (key == Settings.KEY_LANG_UI.name) {
updateLanguage(Settings.get(Settings.KEY_LANG_UI))
}
}
}

private val low_memory_listeners: MutableList<() -> Unit> = mutableListOf()

val ui_language: String get() = languages.keys.elementAt(Settings.get(Settings.KEY_LANG_UI))
val data_language: String get() = languages.keys.elementAt(Settings.get(Settings.KEY_LANG_DATA))
fun getLanguageCode(index: Int): String = LANGUAGES[index]
fun getLanguageIndex(language_code: String): Int = LANGUAGES.indexOf(language_code)
fun getLanguageCount(): Int = LANGUAGES.size

val ui_language: String get() = getLanguageCode(Settings.get(Settings.KEY_LANG_UI))
val data_language: String get() = getLanguageCode(Settings.get(Settings.KEY_LANG_DATA))

fun init(context: PlatformContext) {
this.context = context

context.getPrefs().addListener(prefs_change_listener)
error_manager = ErrorManager(context)
languages = loadLanguages(context)
_yt_ui_translation = YoutubeUITranslation(languages.keys)

val ui_lang: Int = Settings.get(Settings.KEY_LANG_UI)
initResources(languages.keys.elementAt(ui_lang), context)
updateLanguage(ui_lang)

Cache.init(context)
DataApi.initialise()
MediaItem.init(Settings.prefs)

// Thread.setDefaultUncaughtExceptionHandler { _: Thread, error: Throwable ->
// error.printStackTrace()
//
Expand All @@ -99,6 +87,20 @@ object SpMp {
// })
// }

context.getPrefs().addListener(prefs_change_listener)
error_manager = ErrorManager(context)

thread {
MediaItem.init(Settings.prefs)
}

val ui_lang: Int = Settings.get(Settings.KEY_LANG_UI)
initResources(LANGUAGES.elementAt(ui_lang), context)

_yt_ui_translation = YoutubeUITranslation(LANGUAGES)
Cache.init(context)
DataApi.initialise()

service_host = PlayerServiceHost.instance ?: PlayerServiceHost()
service_started = false
}
Expand Down Expand Up @@ -138,22 +140,8 @@ object SpMp {
low_memory_listeners.forEach { it.invoke() }
}

private fun loadLanguages(context: PlatformContext): MutableMap<String, Map<String, String>> {
val data = context.openResourceFile("languages.json").bufferedReader()
val ret = mutableMapOf<String, Map<String, String>>()
for (item in Klaxon().parseJsonObject(data).entries) {
val map = mutableMapOf<String, String>()
for (subitem in (item.value as JsonObject).entries) {
map[subitem.key] = subitem.value.toString()
}
ret[item.key] = map
}
data.close()
return ret
}

private fun getFontFamily(context: PlatformContext): FontFamily {
val locale = languages.keys.elementAt(Settings.get(Settings.KEY_LANG_UI))
val locale = ui_language
val font_dirs = context.listResourceFiles("")!!.filter { it.length > 4 && it.startsWith("font") }

var font_dir: String? = font_dirs.firstOrNull { it.endsWith("-$locale") }
Expand All @@ -169,10 +157,6 @@ object SpMp {
return FontFamily(context.loadFontFromFile("$font_name/regular.ttf"))
}

private fun updateLanguage(lang: Int) {
// TODO
}

val app_name: String get() = getString("app_name")
}

Expand Down Expand Up @@ -271,12 +255,12 @@ class ErrorManager(private val context: PlatformContext) {
}

FilledTonalButton(dismiss) {
Text(getStringTemp("Dismiss"))
Text(getStringTODO("Dismiss"))
}
}
},
title = {
WidthShrinkText(getStringTemp("{errors} error(s) occurred").replace("{errors}", errors.size.toString()))
WidthShrinkText(getStringTODO("{errors} error(s) occurred").replace("{errors}", errors.size.toString()))
},
text = {
var expanded_error by remember { mutableStateOf(-1) }
Expand Down Expand Up @@ -323,17 +307,17 @@ class ErrorManager(private val context: PlatformContext) {
Text(index.toString(), Modifier.align(Alignment.Center))
}

Text(error.message ?: getStringTemp("No message"))
Text(error.message ?: getStringTODO("No message"))
}

AnimatedVisibility(expanded, enter = expandVertically(), exit = shrinkVertically()) {
Column {
Text(error.stackTraceToString(), softWrap = false)

Row(horizontalArrangement = Arrangement.End) {
context.CopyShareButtons(getStringTemp("error")) { error.stackTraceToString() }
context.CopyShareButtons(getStringTODO("error")) { error.stackTraceToString() }
FilledTonalButton(onClick = { throw error }) {
Text(getStringTemp("Throw"))
Text(getStringTODO("Throw"))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,14 @@ class SettingsItemSlider(
try {
val value: Float = if (is_int) text.toInt().toFloat() else text.toFloat()
if (!range.contains(value)) {
error = getStringTemp("Value is out of range ($range)")
error = getStringTODO("Value is out of range ($range)")
return@OutlinedTextField
}

error = null
}
catch(_: NumberFormatException) {
error = if (is_int) getStringTemp("Value is not an integer") else getStringTemp("Value is not a float")
error = if (is_int) getStringTODO("Value is not an integer") else getStringTODO("Value is not a float")
}
},
singleLine = true
Expand Down
68 changes: 44 additions & 24 deletions shared/src/commonMain/kotlin/com/spectre7/spmp/api/DataApi.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.spectre7.spmp.api

import com.beust.klaxon.*
import com.spectre7.spmp.model.Cache
import com.spectre7.spmp.model.MediaItem
import com.spectre7.spmp.platform.ProjectPreferences
import com.spectre7.spmp.model.Settings
Expand All @@ -19,6 +20,7 @@ import java.time.Duration
import java.util.logging.Level
import java.util.logging.Logger
import java.util.zip.GZIPInputStream
import kotlin.concurrent.thread
import org.schabi.newpipe.extractor.downloader.Request as NewPipeRequest
import org.schabi.newpipe.extractor.downloader.Response as NewPipeResponse

Expand Down Expand Up @@ -179,13 +181,17 @@ class DataApi {
private lateinit var youtubei_context_alt: JsonObject
private lateinit var youtubei_context_android: JsonObject

private lateinit var youtubei_headers: Headers
private var youtubei_headers: Headers? = null
private var header_update_thread: Thread? = null

private val prefs_change_listener = object : ProjectPreferences.Listener {
override fun onChanged(prefs: ProjectPreferences, key: String) {
when (key) {
Settings.KEY_YTM_AUTH.name -> updateYtmHeaders()
Settings.KEY_LANG_DATA.name -> updateYtmContext()
Settings.KEY_LANG_DATA.name -> {
updateYtmContext()
Cache.reset()
}
}
}
}
Expand Down Expand Up @@ -227,31 +233,41 @@ class DataApi {
)
}

private fun updateYtmHeaders() {
val headers_builder = Headers.Builder().add("user-agent", user_agent)

val ytm_auth = YoutubeMusicAuthInfo(Settings.get(Settings.KEY_YTM_AUTH))
if (ytm_auth.initialised) {
headers_builder["cookie"] = ytm_auth.cookie
for (header in ytm_auth.headers) {
headers_builder[header.key] = header.value
@Synchronized
private fun updateYtmHeaders(): Thread {
header_update_thread?.also { thread ->
if (thread.isAlive) {
return thread
}
}
else {
val headers = getStringArray("ytm_headers")
var i = 0
while (i < headers.size) {
val key = headers[i++]
val value = headers[i++]
headers_builder[key] = value

header_update_thread = thread {
val headers_builder = Headers.Builder().add("user-agent", user_agent)

val ytm_auth = Settings.get<Set<String>>(Settings.KEY_YTM_AUTH).let { if (it is YoutubeMusicAuthInfo) it else YoutubeMusicAuthInfo(it) }
if (ytm_auth.initialised) {
headers_builder["cookie"] = ytm_auth.cookie
for (header in ytm_auth.headers) {
headers_builder[header.key] = header.value
}
}
else {
val headers = getStringArray("ytm_headers")
var i = 0
while (i < headers.size) {
val key = headers[i++]
val value = headers[i++]
headers_builder[key] = value
}
}
}

headers_builder["accept-encoding"] = "gzip, deflate"
headers_builder["content-encoding"] = "gzip"
headers_builder["user-agent"] = user_agent
headers_builder["accept-encoding"] = "gzip, deflate"
headers_builder["content-encoding"] = "gzip"
headers_builder["user-agent"] = user_agent

youtubei_headers = headers_builder.build()
youtubei_headers = headers_builder.build()
}
return header_update_thread!!
}

fun initialise() {
Expand Down Expand Up @@ -299,13 +315,17 @@ class DataApi {
}

internal fun Request.Builder.addYtHeaders(plain: Boolean = false): Request.Builder {
if (youtubei_headers == null) {
header_update_thread!!.join()
}

if (plain) {
for (header in listOf("accept-language", "user-agent", "accept-encoding", "content-encoding")) {
header(header, youtubei_headers[header]!!)
header(header, youtubei_headers!![header]!!)
}
}
else {
headers(youtubei_headers)
headers(youtubei_headers!!)
}
return this
}
Expand Down
15 changes: 9 additions & 6 deletions shared/src/commonMain/kotlin/com/spectre7/spmp/api/HomeFeed.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlin.concurrent.thread

private val CACHE_LIFETIME = Duration.ofDays(1)

fun getHomeFeed(min_rows: Int = -1, allow_cached: Boolean = true, params: String? = null, continuation: String? = null): Result<Triple<List<MediaItemLayout>, String?, List<String>?>> {
fun getHomeFeed(min_rows: Int = -1, allow_cached: Boolean = true, params: String? = null, continuation: String? = null): Result<Triple<List<MediaItemLayout>, String?, List<Pair<Int, String>>?>> {

fun postRequest(ctoken: String?): Result<InputStreamReader> {
val endpoint = "/youtubei/v1/browse"
Expand Down Expand Up @@ -54,9 +54,9 @@ fun getHomeFeed(min_rows: Int = -1, allow_cached: Boolean = true, params: String
}

val chips = Cache.get(chips_cache_key)?.run {
val chips: List<String> = DataApi.klaxon.parseArray(this)!!
val chips: List<List<Any>> = DataApi.klaxon.parseArray(this)!!
close()
chips
chips.map { Pair(it[0] as Int, it[1] as String) }
}

return Result.success(Triple(rows, ctoken, chips))
Expand Down Expand Up @@ -100,7 +100,7 @@ fun getHomeFeed(min_rows: Int = -1, allow_cached: Boolean = true, params: String
if (continuation == null) {
Cache.set(rows_cache_key, DataApi.klaxon.toJsonString(rows).reader(), CACHE_LIFETIME)
Cache.set(ctoken_cache_key, ctoken?.reader(), CACHE_LIFETIME)
Cache.set(chips_cache_key, chips?.let { DataApi.klaxon.toJsonString(it).reader() }, CACHE_LIFETIME)
Cache.set(chips_cache_key, chips?.let { DataApi.klaxon.toJsonString(it.map { chip -> listOf(chip.first, chip.second) }).reader() }, CACHE_LIFETIME)
}

return Result.success(Triple(rows, ctoken, chips))
Expand Down Expand Up @@ -264,9 +264,12 @@ data class YoutubeiBrowseResponse(
else contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents ?: emptyList()
}

fun getHeaderChips(): List<String>? =
fun getHeaderChips(): List<Pair<Int, String>>? =
contents?.singleColumnBrowseResultsRenderer?.tabs?.first()?.tabRenderer?.content?.sectionListRenderer?.header?.chipCloudRenderer?.chips?.map {
it.chipCloudChipRenderer.navigationEndpoint.browseEndpoint!!.params!!
Pair(
LocalisedYoutubeString.filterChip(it.chipCloudChipRenderer.text.first_text) ?: throw NotImplementedError(it.chipCloudChipRenderer.text.first_text),
it.chipCloudChipRenderer.navigationEndpoint.browseEndpoint!!.params!!
)
}

data class Contents(val singleColumnBrowseResultsRenderer: SingleColumnBrowseResultsRenderer)
Expand Down
Loading

0 comments on commit b48e0a1

Please sign in to comment.