diff --git a/.github/workflows/Build-Linux.yml b/.github/workflows/Build-Linux.yml
index d0d4ee0..5e930c3 100644
--- a/.github/workflows/Build-Linux.yml
+++ b/.github/workflows/Build-Linux.yml
@@ -40,6 +40,13 @@ jobs:
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
run: "./gradlew packageReleaseUberJarForCurrentOS"
+ - name: "Print and save sha384 for binaries"
+ run: find ./build/compose -type f \( -iname Styx\*linux\*.jar -o -iname \*.deb -o -iname \*.rpm \) -exec shasum -a 384 {} \; | tee checksums.sha384
+ - name: "Upload checksum file"
+ uses: actions/upload-artifact@v4
+ with:
+ name: checksums.sha384
+ path: checksums.sha384
- name: "Upload binaries to FTP"
uses: "SamKirkland/FTP-Deploy-Action@v4.3.4"
with:
diff --git a/.github/workflows/Build-Windows.yml b/.github/workflows/Build-Windows.yml
index cbc9df9..1fb1711 100644
--- a/.github/workflows/Build-Windows.yml
+++ b/.github/workflows/Build-Windows.yml
@@ -38,6 +38,18 @@ jobs:
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
run: ./gradlew.bat packageReleaseUberJarForCurrentOS
+ - name: "Print and save sha384 for binaries"
+ shell: pwsh
+ run: |
+ Get-ChildItem -Path .\build\compose -Recurse -File -Include Styx*windows*.jar,*.msi | ForEach-Object {
+ $hash = Get-FileHash -Path $_.FullName -Algorithm SHA384
+ "$($hash.Hash) $($_.Name)"
+ } | Tee-Object -FilePath checksums.sha384
+ - name: "Upload checksum file"
+ uses: actions/upload-artifact@v4
+ with:
+ name: checksums.sha384
+ path: checksums.sha384
- name: "Upload binaries to FTP"
uses: "SamKirkland/FTP-Deploy-Action@v4.3.4"
with:
diff --git a/.gitignore b/.gitignore
index 488efad..69ee301 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
local.properties
+checksums.sha384
### IntelliJ IDEA ###
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index fe63bb6..c224ad5 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c049206..2f5ee3b 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,8 +1,7 @@
-
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..931b96c
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 620c30e..bc2fe4b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,15 +1,15 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
- kotlin("jvm") version "1.9.23"
- id("org.jetbrains.compose") version "1.6.1"
- id("org.jetbrains.kotlin.plugin.serialization") version "1.9.23"
- id("com.github.gmazzo.buildconfig") version "5.3.5"
- id("org.ajoberstar.grgit") version "5.2.1"
+ alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.compose)
+ alias(libs.plugins.compose.compiler)
+ alias(libs.plugins.buildconfig)
}
group = "moe.styx"
-version = "0.0.7"
+version = "0.1.0"
repositories {
google()
@@ -30,18 +30,19 @@ dependencies {
implementation(compose.materialIconsExtended)
// Misc
- implementation("org.slf4j:slf4j-simple:2.0.9")
- implementation("net.lingala.zip4j:zip4j:2.11.5")
- implementation("com.github.caoimhebyrne:KDiscordIPC:0.2.2")
- implementation("com.squareup.okio:okio:3.9.0")
+ implementation(libs.slf4j.simple)
+ implementation(libs.zip4j)
+ implementation(libs.kdiscord.ipc)
+ implementation(libs.okio)
// Styx
- implementation("moe.styx:styx-common-compose-jvm:0.0.5")
+ implementation(libs.styx.common.compose)
}
compose.desktop {
application {
mainClass = "moe.styx.MainKt"
+ jvmArgs += listOf("-Xmx1250M", "-Xms300M")
buildTypes.release.proguard {
configurationFiles.from(project.file("proguard.rules"))
}
@@ -62,11 +63,13 @@ compose.desktop {
vendor = "Vodes & Styx contributors"
licenseFile.set(project.file("LICENSE"))
windows {
+ packageVersion = project.version.toString().split("-")[0]
menuGroup = "Styx"
upgradeUuid = System.getenv("STYX_APP_GUID")
iconFile.set(project.file("src/main/resources/icons/icon.ico"))
}
linux {
+ packageVersion = project.version.toString().split("-")[0]
iconFile.set(project.file("src/main/resources/icons/icon.png"))
menuGroup = "AudioVideo;Video"
shortcut = true
@@ -74,7 +77,7 @@ compose.desktop {
macOS {
packageVersion = project.version.toString().let {
if (it.startsWith("0.")) it.replaceFirst("0.", "1.") else it
- }
+ }.split("-")[0]
appStore = false
iconFile.set(project.file("src/main/resources/icons/icon.icns"))
}
@@ -92,8 +95,8 @@ buildConfig {
buildConfigField("IMAGE_URL", System.getenv("STYX_IMAGEURL")) // Example: https://images.company.com
buildConfigField("SITE", siteURL.split("https://").getOrElse(1) { siteURL })
buildConfigField("BUILD_TIME", (System.currentTimeMillis() / 1000))
- buildConfigField("VERSION_CHECK_URL", "https://raw.githubusercontent.com/Vodes/Styx-2/master/build.gradle.kts")
- buildConfigField("DISCORD_CLIENT_ID", "")//System.getenv("STYX_DISCORDCLIENT"))
+ buildConfigField("VERSION_CHECK_URL", "https://api.github.com/repos/Vodes/Styx-2/tags")
+ buildConfigField("DISCORD_CLIENT_ID", "686174250259709983")
}
kotlin {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..fac9186
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,23 @@
+[versions]
+kotlin = "2.0.21"
+compose = "1.7.1"
+styx-common-compose = "0.1.1"
+buildconfig = "5.4.0"
+slf4j-simple = "2.0.16"
+okio = "3.9.0"
+kdiscord-ipc = "0.2.2"
+zip4j = "2.11.5"
+
+[plugins]
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+buildconfig = { id = "com.github.gmazzo.buildconfig", version.ref = "buildconfig" }
+
+[libraries]
+zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" }
+kdiscord-ipc = { module = "com.github.caoimhebyrne:KDiscordIPC", version.ref = "kdiscord-ipc" }
+okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
+slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j-simple" }
+styx-common-compose = { module = "moe.styx:styx-common-compose-jvm", version.ref = "styx-common-compose" }
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 9b96589..843c99a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -4,7 +4,9 @@ pluginManagement {
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
-
+}
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
}
rootProject.name = "Styx 2"
diff --git a/src/main/kotlin/moe/styx/Main.kt b/src/main/kotlin/moe/styx/Main.kt
index e019292..f13ec01 100644
--- a/src/main/kotlin/moe/styx/Main.kt
+++ b/src/main/kotlin/moe/styx/Main.kt
@@ -4,35 +4,39 @@ import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material.Surface
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.SlideTransition
+import com.dokar.sonner.ToastWidthPolicy
+import com.dokar.sonner.Toaster
+import com.dokar.sonner.rememberToasterState
import com.russhwolf.settings.get
import io.kamel.image.config.LocalKamelConfig
import kotlinx.coroutines.delay
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
-import moe.styx.Main.isUiModeDark
-import moe.styx.Main.setupLogFile
-import moe.styx.Styx__.BuildConfig
+import moe.styx.Styx_2.BuildConfig
import moe.styx.common.compose.AppConfig
-import moe.styx.common.compose.appConfig
+import moe.styx.common.compose.AppContextImpl.appConfig
import moe.styx.common.compose.extensions.kamelConfig
-import moe.styx.common.compose.http.*
+import moe.styx.common.compose.http.Endpoints
+import moe.styx.common.compose.http.sendObject
import moe.styx.common.compose.settings
import moe.styx.common.compose.threads.DownloadQueue
import moe.styx.common.compose.threads.Heartbeats
import moe.styx.common.compose.threads.RequestQueue
import moe.styx.common.compose.utils.LocalGlobalNavigator
-import moe.styx.common.compose.utils.ServerStatus
+import moe.styx.common.compose.utils.LocalToaster
import moe.styx.common.extension.formattedStrFile
import moe.styx.common.http.getHttpClient
import moe.styx.common.util.Log
@@ -41,14 +45,14 @@ import moe.styx.logic.DiscordRPC
import moe.styx.logic.Files
import moe.styx.logic.runner.currentPlayer
import moe.styx.theme.*
-import moe.styx.views.login.LoginView
-import moe.styx.views.login.OfflineView
-import moe.styx.views.other.LoadingView
+import moe.styx.views.anime.AnimeOverview
import java.io.File
import java.io.PrintStream
object Main {
var isUiModeDark: MutableState = mutableStateOf(true)
+ var useMonoFont: MutableState = mutableStateOf(false)
+ var densityScale: MutableState = mutableStateOf(1f)
var wasLaunchedInDebug = false
fun setupLogFile() {
@@ -59,14 +63,18 @@ object Main {
val stream = PrintStream(file.outputStream())
System.setOut(stream)
System.setErr(stream)
+ if (settings["enable-debug-logs", false])
+ Log.debugEnabled = true
}
}
fun main(args: Array) = application {
if (!args.contains("-debug"))
- setupLogFile()
- else
+ Main.setupLogFile()
+ else {
Main.wasLaunchedInDebug = true
+ Log.debugEnabled = true
+ }
getHttpClient("${BuildConfig.APP_NAME} - ${BuildConfig.APP_VERSION}")
appConfig = {
AppConfig(
@@ -76,17 +84,19 @@ fun main(args: Array) = application {
BuildConfig.IMAGE_URL,
null,
Files.getCacheDir().absolutePath,
- Files.getDataDir().absolutePath
+ Files.getDataDir().absolutePath,
+ BuildConfig.VERSION_CHECK_URL
)
}
if (settings["discord-rpc", true]) {
DiscordRPC.start()
}
- RequestQueue.start()
- Heartbeats.start()
- DownloadQueue.start()
launchGlobal {
+ Heartbeats.start()
+ delay(10000)
+ RequestQueue.start()
+ DownloadQueue.start()
while (true) {
delay(3000)
DiscordRPC.updateActivity()
@@ -94,9 +104,11 @@ fun main(args: Array) = application {
Heartbeats.mediaActivity = null
}
}
-
- isUiModeDark.value = settings["darkmode", true]
- val darkMode by remember { isUiModeDark }
+ Main.useMonoFont.value = settings["mono-font", false]
+ Main.isUiModeDark.value = settings["darkmode", true]
+ Main.densityScale.value = settings["density-scale", 1f]
+ val darkMode by remember { Main.isUiModeDark }
+ val monoFont by remember { Main.useMonoFont }
Window(
onCloseRequest = { onClose() },
@@ -108,28 +120,28 @@ fun main(args: Array) = application {
Log.i { "Compose window initialized with: ${this.window.renderApi}" }
Log.i { "Starting ${BuildConfig.APP_NAME} v${BuildConfig.APP_VERSION}" }
Surface(modifier = Modifier.fillMaxSize()) {
- MaterialTheme(
- colorScheme = (if (darkMode) DarkColorScheme else LightColorScheme).transition(),
- typography = AppTypography,
- shapes = AppShapes
- ) {
- val view = if (isLoggedIn()) {
- Log.i { "Logged in as: ${login?.name}" }
- LoadingView()
- } else {
- if (ServerStatus.lastKnown !in listOf(ServerStatus.ONLINE, ServerStatus.UNAUTHORIZED))
- OfflineView()
- else
- LoginView()
- }
- Navigator(view) { navigator ->
- CompositionLocalProvider(LocalGlobalNavigator provides navigator, LocalKamelConfig provides kamelConfig) {
- SlideTransition(
- navigator, animationSpec = spring(
- stiffness = Spring.StiffnessMedium,
- visibilityThreshold = IntOffset.VisibilityThreshold
+ val currentDensity = LocalDensity.current
+ CompositionLocalProvider(LocalDensity provides Density(currentDensity.density * Main.densityScale.value)) {
+ val toasterState = rememberToasterState()
+ MaterialTheme(
+ colorScheme = if (darkMode) darkScheme else lightScheme,
+ typography = if (monoFont) AppFont.JetbrainsMono.Typography else AppFont.OpenSans.Typography,
+ shapes = AppShapes
+ ) {
+ Toaster(toasterState, darkTheme = darkMode, richColors = true, widthPolicy = { ToastWidthPolicy(0.dp, 450.dp) })
+ Navigator(AnimeOverview()) { navigator ->
+ CompositionLocalProvider(
+ LocalGlobalNavigator provides navigator,
+ LocalKamelConfig provides kamelConfig,
+ LocalToaster provides toasterState
+ ) {
+ SlideTransition(
+ navigator, animationSpec = spring(
+ stiffness = Spring.StiffnessMedium,
+ visibilityThreshold = IntOffset.VisibilityThreshold
+ )
)
- )
+ }
}
}
}
diff --git a/src/main/kotlin/moe/styx/components/anime/AnimeDetailItems.kt b/src/main/kotlin/moe/styx/components/anime/AnimeDetailItems.kt
index 0396d4d..71a8d0e 100644
--- a/src/main/kotlin/moe/styx/components/anime/AnimeDetailItems.kt
+++ b/src/main/kotlin/moe/styx/components/anime/AnimeDetailItems.kt
@@ -14,17 +14,18 @@ import io.kamel.core.Resource
import io.kamel.image.KamelImage
@Composable
-fun BigScalingCardImage(image: Resource, modifier: Modifier = Modifier) {
+fun BigScalingCardImage(image: Resource?, modifier: Modifier = Modifier) {
Column(modifier) {
ElevatedCard(
Modifier.align(Alignment.Start).padding(12.dp).requiredHeightIn(150.dp, 500.dp).aspectRatio(0.71F),
) {
- KamelImage(
- image,
- contentDescription = "Anime",
- modifier = Modifier.padding(2.dp).clip(RoundedCornerShape(4.dp)),
- contentScale = ContentScale.FillBounds
- )
+ if (image != null)
+ KamelImage(
+ { image },
+ contentDescription = "Anime",
+ modifier = Modifier.padding(2.dp).clip(RoundedCornerShape(4.dp)),
+ contentScale = ContentScale.FillBounds
+ )
}
}
}
diff --git a/src/main/kotlin/moe/styx/components/anime/AnimeDialogs.kt b/src/main/kotlin/moe/styx/components/anime/AnimeDialogs.kt
index e08f714..c9dc20a 100644
--- a/src/main/kotlin/moe/styx/components/anime/AnimeDialogs.kt
+++ b/src/main/kotlin/moe/styx/components/anime/AnimeDialogs.kt
@@ -6,6 +6,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import moe.styx.common.data.MediaEntry
+import moe.styx.logic.runner.MpvFinishStatus
import moe.styx.logic.runner.launchMPV
@Composable
@@ -30,8 +31,7 @@ fun AppendDialog(
modifier: Modifier = Modifier,
buttonModifier: Modifier = Modifier,
onDismiss: () -> Unit = {},
- execUpdate: () -> Unit = {},
- onFail: (String) -> Unit = {}
+ onResult: (MpvFinishStatus) -> Unit = {}
) {
AlertDialog(
{ onDismiss() },
@@ -40,13 +40,13 @@ fun AppendDialog(
text = { Text("Do you want to start playing now or append to the current playlist?") },
dismissButton = {
Button({
- launchMPV(mediaEntry, false, { onFail(it) }, execUpdate = execUpdate)
+ launchMPV(mediaEntry, false, onResult)
onDismiss()
}, modifier = buttonModifier) { Text("Play now") }
},
confirmButton = {
Button({
- launchMPV(mediaEntry, true, { onFail(it) }, execUpdate = execUpdate)
+ launchMPV(mediaEntry, true, onResult)
onDismiss()
}, modifier = buttonModifier) { Text("Append") }
}
diff --git a/src/main/kotlin/moe/styx/components/overviews/MediaListing.kt b/src/main/kotlin/moe/styx/components/overviews/MediaListing.kt
index 9b47cc6..52c31d2 100644
--- a/src/main/kotlin/moe/styx/components/overviews/MediaListing.kt
+++ b/src/main/kotlin/moe/styx/components/overviews/MediaListing.kt
@@ -1,40 +1,49 @@
package moe.styx.components.overviews
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.grid.*
import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import moe.styx.common.compose.components.anime.AnimeCard
import moe.styx.common.compose.components.anime.AnimeListItem
-import moe.styx.common.compose.files.Storage
-import moe.styx.common.compose.files.collectWithEmptyInitial
import moe.styx.common.compose.utils.LocalGlobalNavigator
+import moe.styx.common.compose.viewmodels.ListPosViewModel
+import moe.styx.common.compose.viewmodels.MainDataViewModelStorage
import moe.styx.common.data.Media
+import moe.styx.common.extension.eqI
import moe.styx.logic.utils.pushMediaView
-@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun MediaGrid(media: List, showUnseen: Boolean = false) {
+fun MediaGrid(storage: MainDataViewModelStorage, mediaList: List, listPosViewModel: ListPosViewModel, showUnseen: Boolean = false) {
val nav = LocalGlobalNavigator.current
+ val listState = rememberLazyGridState(listPosViewModel.scrollIndex, listPosViewModel.scrollOffset)
+ LaunchedEffect(listState.isScrollInProgress) {
+ if (!listState.isScrollInProgress) {
+ listPosViewModel.scrollIndex = listState.firstVisibleItemIndex
+ listPosViewModel.scrollOffset = listState.firstVisibleItemScrollOffset
+ }
+ }
if (showUnseen) {
- val entryList by Storage.stores.entryStore.collectWithEmptyInitial()
- val watchedList by Storage.stores.watchedStore.collectWithEmptyInitial()
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp),
contentPadding = PaddingValues(10.dp, 7.dp),
+ state = listState
) {
- items(media, key = { it.GUID }) {
- Row(modifier = Modifier.animateItemPlacement()) {
- AnimeCard(it, showUnseen, entryList = entryList, watchedEntries = watchedList) { nav.pushMediaView(it) }
+ items(mediaList, key = { it.GUID }) {
+ Row(modifier = Modifier.animateItem()) {
+ AnimeCard(
+ it to storage.imageList.find { img -> img.GUID eqI it.thumbID },
+ true,
+ entryList = storage.entryList,
+ watchedEntries = storage.watchedList
+ ) { nav.pushMediaView(it) }
}
}
}
@@ -42,24 +51,31 @@ fun MediaGrid(media: List, showUnseen: Boolean = false) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp),
contentPadding = PaddingValues(10.dp, 7.dp),
+ state = listState
) {
- items(media, key = { it.GUID }) {
- Row(modifier = Modifier.animateItemPlacement()) {
- AnimeCard(it, showUnseen) { nav.pushMediaView(it) }
+ items(mediaList, key = { it.GUID }) {
+ Row(modifier = Modifier.animateItem()) {
+ AnimeCard(it to storage.imageList.find { img -> img.GUID eqI it.thumbID }) { nav.pushMediaView(it) }
}
}
}
}
}
-@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun MediaList(media: List) {
+fun MediaList(storage: MainDataViewModelStorage, mediaList: List, listPosViewModel: ListPosViewModel) {
val nav = LocalGlobalNavigator.current
- LazyColumn {
- items(media, key = { it.GUID }) {
- Row(Modifier.animateItemPlacement().padding(3.dp)) {
- AnimeListItem(it) { nav.pushMediaView(it) }
+ val listState = rememberLazyListState(listPosViewModel.scrollIndex, listPosViewModel.scrollOffset)
+ LaunchedEffect(listState.isScrollInProgress) {
+ if (!listState.isScrollInProgress) {
+ listPosViewModel.scrollIndex = listState.firstVisibleItemIndex
+ listPosViewModel.scrollOffset = listState.firstVisibleItemScrollOffset
+ }
+ }
+ LazyColumn(state = listState) {
+ items(mediaList, key = { it.GUID }) {
+ Row(Modifier.animateItem().padding(3.dp)) {
+ AnimeListItem(it, storage.imageList.find { img -> img.GUID eqI it.thumbID }) { nav.pushMediaView(it) }
}
}
}
diff --git a/src/main/kotlin/moe/styx/logic/DiscordRPC.kt b/src/main/kotlin/moe/styx/logic/DiscordRPC.kt
index abedf9b..e231525 100644
--- a/src/main/kotlin/moe/styx/logic/DiscordRPC.kt
+++ b/src/main/kotlin/moe/styx/logic/DiscordRPC.kt
@@ -7,15 +7,14 @@ import dev.cbyrne.kdiscordipc.core.event.impl.ErrorEvent
import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent
import dev.cbyrne.kdiscordipc.data.activity.*
import io.github.xxfast.kstore.extensions.getOrEmpty
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import moe.styx.Styx__.BuildConfig
-import moe.styx.common.compose.extensions.getThumb
+import moe.styx.Styx_2.BuildConfig
import moe.styx.common.compose.extensions.getURL
-import moe.styx.common.compose.files.Storage
+import moe.styx.common.compose.files.Stores
import moe.styx.common.compose.settings
import moe.styx.common.data.MediaActivity
-import moe.styx.common.extension.currentUnixSeconds
import moe.styx.common.extension.eqI
import moe.styx.common.extension.toBoolean
import moe.styx.common.util.Log
@@ -43,6 +42,7 @@ object DiscordRPC {
ipc!!.on {
errored = false
Log.i { "Discord-RPC initialized." }
+ delay(5000)
updateActivity()
}.start()
ipc!!.connect()
@@ -67,8 +67,9 @@ object DiscordRPC {
val mediaActivity = if (currentPlayer != null && MpvStatus.current.file.isNotEmpty() && MpvStatus.current.percentage > -1)
MediaActivity(MpvStatus.current.file, MpvStatus.current.seconds.toLong(), !MpvStatus.current.paused)
else null
- val entry = mediaActivity?.let { act -> runBlocking { Storage.stores.entryStore.getOrEmpty() }.find { it.GUID eqI act.mediaEntry } }
- val media = entry?.let { ent -> runBlocking { Storage.stores.mediaStore.getOrEmpty() }.find { it.GUID eqI ent.mediaID } }
+ val entry = mediaActivity?.let { act -> runBlocking { Stores.entryStore.getOrEmpty() }.find { it.GUID eqI act.mediaEntry } }
+ val media = entry?.let { ent -> runBlocking { Stores.mediaStore.getOrEmpty() }.find { it.GUID eqI ent.mediaID } }
+ val image = media?.let { ent -> runBlocking { Stores.imageStore.getOrEmpty() }.find { it.GUID eqI ent.thumbID } }
ipc!!.scope.launch {
if (errored)
return@launch
@@ -90,9 +91,8 @@ object DiscordRPC {
media.name + if (media.isSeries.toBoolean()) " - ${entry.entryNumber}" else ""
) {
button("View on GitHub", "https://github.com/Vodes?tab=repositories&q=Styx&language=kotlin")
- val image = media.getThumb()
if (mediaActivity.playing)
- timestamps(currentUnixSeconds(), currentUnixSeconds() + MpvStatus.current.timeRemaining)
+ timestamps(1, 1 + MpvStatus.current.seconds.toLong())
if (image == null) {
largeImage("styx", "v${BuildConfig.APP_VERSION}")
} else {
diff --git a/src/main/kotlin/moe/styx/logic/Files.kt b/src/main/kotlin/moe/styx/logic/Files.kt
index 67c1bec..410d9bf 100644
--- a/src/main/kotlin/moe/styx/logic/Files.kt
+++ b/src/main/kotlin/moe/styx/logic/Files.kt
@@ -1,5 +1,6 @@
package moe.styx.logic
+import moe.styx.common.extension.containsAny
import moe.styx.common.extension.currentUnixSeconds
import java.io.File
@@ -64,8 +65,8 @@ object Files {
val currentMillis = currentUnixSeconds() * 1000
val installerFiles = getAppDir().listFiles()?.toList() ?: emptyList()
installerFiles
- .filter { it.name.contains(".msi", true) }
- .filter { (it.lastModified() - 30000) > currentMillis }
+ .filter { it.name.containsAny("msi", "rpm", "deb") }
+ .filter { it.lastModified() < (currentMillis - 30000) }
.forEach {
runCatching { it.delete() }
}
diff --git a/src/main/kotlin/moe/styx/logic/runner/MPVRunner.kt b/src/main/kotlin/moe/styx/logic/runner/MPVRunner.kt
index 94ac725..f1b0a9d 100644
--- a/src/main/kotlin/moe/styx/logic/runner/MPVRunner.kt
+++ b/src/main/kotlin/moe/styx/logic/runner/MPVRunner.kt
@@ -8,6 +8,7 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import moe.styx.Main
import moe.styx.common.compose.files.Storage
+import moe.styx.common.compose.files.getBlocking
import moe.styx.common.compose.http.Endpoints
import moe.styx.common.compose.http.login
import moe.styx.common.compose.settings
@@ -28,27 +29,33 @@ import java.io.*
var currentPlayer: MpvInstance? = null
-fun launchMPV(entry: MediaEntry, append: Boolean, onFail: (String) -> Unit = {}, execUpdate: () -> Unit = {}) {
+data class MpvFinishStatus(val statusCode: Int, val message: String = "") {
+ val isOK: Boolean = statusCode == 0
+}
+
+fun launchMPV(entry: MediaEntry, append: Boolean, onClose: (MpvFinishStatus) -> Unit = {}) {
if (currentPlayer == null) {
currentPlayer = MpvInstance()
- currentPlayer!!.start(entry, onFail, execUpdate) { processCode ->
+ currentPlayer!!.start(entry, onClose) { processCode ->
currentPlayer = null
if (processCode > 0) {
- onFail("Playback ended with a bad status code:\n$processCode")
+ onClose(MpvFinishStatus(processCode, "Playback ended with a bad status code: $processCode"))
} else {
val currentEntry =
runBlocking { Storage.stores.entryStore.getOrEmpty() }.find { MpvStatus.current.file eqI it.GUID }
if (MpvStatus.current.file.isNotBlank() && currentEntry != null && MpvStatus.current.seconds > 5) {
- val watched = MediaWatched(
- currentEntry.GUID,
- login?.userID ?: "",
- currentUnixSeconds(),
- MpvStatus.current.seconds.toLong(),
- MpvStatus.current.percentage.toFloat(),
- MpvStatus.current.percentage.toFloat()
- )
- RequestQueue.updateWatched(watched)
- execUpdate()
+ launchThreaded {
+ val watched = MediaWatched(
+ currentEntry.GUID,
+ login?.userID ?: "",
+ currentUnixSeconds(),
+ MpvStatus.current.seconds.toLong(),
+ MpvStatus.current.percentage.toFloat(),
+ MpvStatus.current.percentage.toFloat()
+ )
+ RequestQueue.updateWatched(watched).first.join()
+ onClose(MpvFinishStatus(0))
+ }
}
}
MpvStatus.current = MpvStatus.current.copy(file = "", paused = true)
@@ -56,7 +63,7 @@ fun launchMPV(entry: MediaEntry, append: Boolean, onFail: (String) -> Unit = {},
} else {
val result = currentPlayer!!.play(entry, append)
if (!result)
- onFail("Failed to add episode to the queue!")
+ onClose(MpvFinishStatus(-1, "Failed to add episode to the queue!"))
}
}
@@ -66,7 +73,7 @@ class MpvInstance {
private val tryFlatpak = settings["mpv-flatpak", false]
private val instanceJob = Job()
private var firstPrint = true
- val execUpdate: () -> Unit = {}
+ var onClose: (MpvFinishStatus) -> Unit = {}
private fun openRandomAccessFile(): RandomAccessFile {
val socket = File(if (isWindows) "\\\\.\\pipe\\styx-mpvsocket" else "/tmp/styx-mpvsocket")
@@ -92,7 +99,8 @@ class MpvInstance {
return CoroutineScope(instanceJob)
}
- fun start(mediaEntry: MediaEntry, onFail: (String) -> Unit = {}, execUpdate: () -> Unit = {}, onFinish: (Int) -> Unit = {}): Boolean {
+ fun start(mediaEntry: MediaEntry, onClose: (MpvFinishStatus) -> Unit = {}, onFinish: (Int) -> Unit): Boolean {
+ this.onClose = onClose
val systemMpv = settings["mpv-system", !isWindows]
val useConfigRegardless = settings["mpv-system-styx-conf", !isWindows]
val mpvExecutable = if (systemMpv || !isWindows) {
@@ -104,14 +112,14 @@ class MpvInstance {
File(Files.getMpvDir(), "mpv.exe")
if (mpvExecutable == null || !mpvExecutable.exists()) {
- onFail("MPV could not be found.")
+ onClose(MpvFinishStatus(404, "MPV executable not found!"))
currentPlayer = null
return false
}
val downloadedEntry = runBlocking { Storage.stores.downloadedStore.getOrEmpty() }.find { it.entryID eqI mediaEntry.GUID }
- val uri = downloadedEntry?.path ?: "${Endpoints.WATCH.url}/${mediaEntry.GUID}?token=${login?.watchToken}"
+ val uri = downloadedEntry?.path ?: "${Endpoints.WATCH.url()}/${mediaEntry.GUID}?token=${login?.watchToken}"
if ((login == null || login!!.watchToken.isBlank()) && downloadedEntry == null) {
- onFail("You are not logged in right now.")
+ onClose(MpvFinishStatus(403, "You are not logged in or online and don't have this downloaded!"))
currentPlayer = null
return false
}
@@ -187,7 +195,7 @@ class MpvInstance {
val current = MpvStatus.current.copy()
val downloadedEntry = runBlocking { Storage.stores.downloadedStore.getOrEmpty() }.find { it.entryID eqI mediaEntry.GUID }
val uri = downloadedEntry?.okioPath?.normalized()?.toString()?.replace("\\", "\\\\")
- ?: "${Endpoints.WATCH.url}/${mediaEntry.GUID}?token=${login?.watchToken}"
+ ?: "${Endpoints.WATCH.url()}/${mediaEntry.GUID}?token=${login?.watchToken}"
val appendType = if (current.percentage == 100 && current.eof) "append-play" else "append"
val options = if (append) " $appendType" else ""
val loaded = runCommand("loadfile \"$uri\"$options")
@@ -196,7 +204,7 @@ class MpvInstance {
runCommand("set pause no").also { return it }
if (!append) {
- val watched = Storage.watchedList.find { it.entryID eqI mediaEntry.GUID }
+ val watched = Storage.stores.watchedStore.getBlocking().find { it.entryID eqI mediaEntry.GUID }
if (watched != null)
launchThreaded {
delay(100)
@@ -290,8 +298,10 @@ data class MpvStatus(
current.percentage.toFloat(),
current.percentage.toFloat()
)
- RequestQueue.updateWatched(watched)
- currentPlayer?.let { it.execUpdate() }
+ launchThreaded {
+ RequestQueue.updateWatched(watched).first.join()
+ currentPlayer?.onClose?.let { it(MpvFinishStatus(0)) }
+ }
}
}
current = new
diff --git a/src/main/kotlin/moe/styx/logic/runner/ProcessUtils.kt b/src/main/kotlin/moe/styx/logic/runner/ProcessUtils.kt
index b39bee6..e11ac1d 100644
--- a/src/main/kotlin/moe/styx/logic/runner/ProcessUtils.kt
+++ b/src/main/kotlin/moe/styx/logic/runner/ProcessUtils.kt
@@ -2,7 +2,10 @@ package moe.styx.logic.runner
import moe.styx.common.extension.eqI
import moe.styx.common.isWindows
+import moe.styx.common.util.Log
+import java.awt.Desktop
import java.io.File
+import java.net.URI
fun getExecutableFromPath(name: String): File? {
var name = name
@@ -12,4 +15,18 @@ fun getExecutableFromPath(name: String): File? {
.map { File(it) }.filter { it.exists() && it.isDirectory }
return pathDirs.flatMap { it.listFiles()?.asList() ?: listOf() }.find { (if (isWindows) it.name else it.nameWithoutExtension) eqI name }
+}
+
+fun openURI(uri: String) = openURI(URI(uri))
+
+fun openURI(uri: URI) {
+ val xdgOpen = getExecutableFromPath("xdg-open")
+ if (xdgOpen != null) {
+ val result = ProcessBuilder(listOf(xdgOpen.absolutePath, uri.toString())).start().waitFor()
+ if (result == 0)
+ return
+ }
+ if (Desktop.isDesktopSupported()) {
+ runCatching { Desktop.getDesktop().browse(uri) }.onFailure { Log.e(exception = it) { "Failed to open URI: $uri" } }
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/logic/utils/MpvUtils.kt b/src/main/kotlin/moe/styx/logic/utils/MpvUtils.kt
index 776f9bf..d7fde90 100644
--- a/src/main/kotlin/moe/styx/logic/utils/MpvUtils.kt
+++ b/src/main/kotlin/moe/styx/logic/utils/MpvUtils.kt
@@ -71,7 +71,7 @@ object MpvUtils {
return
launchGlobal {
delay(8000)
- val response = httpClient.get(Endpoints.MPV.url)
+ val response = httpClient.get(Endpoints.MPV.url())
if (!response.status.isSuccess()) {
Log.w("MpvUtils::checkVersionAndDownload") { "Failed to check for mpv version." }
@@ -92,7 +92,7 @@ object MpvUtils {
launch {
Log.i { "Downloading latest mpv bundle" }
- val downloadResp = httpClient.get(Endpoints.MPV_DOWNLOAD.url)
+ val downloadResp = httpClient.get(Endpoints.MPV_DOWNLOAD.url())
if (Files.getMpvDir().exists() && downloadResp.status.isSuccess())
Files.getMpvDir().deleteRecursively()
val openChannel = downloadResp.bodyAsChannel()
diff --git a/src/main/kotlin/moe/styx/logic/utils/VersionCheck.kt b/src/main/kotlin/moe/styx/logic/utils/VersionCheck.kt
index 4cac646..520dc5f 100644
--- a/src/main/kotlin/moe/styx/logic/utils/VersionCheck.kt
+++ b/src/main/kotlin/moe/styx/logic/utils/VersionCheck.kt
@@ -5,10 +5,11 @@ import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
-import kotlinx.coroutines.*
-import moe.styx.Styx__.BuildConfig
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import moe.styx.Styx_2.BuildConfig
import moe.styx.common.compose.http.login
-import moe.styx.common.extension.eqI
import moe.styx.common.http.httpClient
import moe.styx.common.util.launchGlobal
import moe.styx.logic.Files
@@ -16,26 +17,6 @@ import java.awt.Desktop
import java.io.File
import kotlin.system.exitProcess
-// TODO: Actually check at some point lol.
-private val versionRegex = "^version = \\\"(?(?\\d)\\.(?\\d)(?:\\.(?\\d))?)\\\"\$".toRegex(RegexOption.IGNORE_CASE)
-fun isUpToDate(): Boolean = runBlocking {
- val response = httpClient.get(BuildConfig.VERSION_CHECK_URL)
- if (!response.status.isSuccess())
- return@runBlocking true
-
- val lines = response.bodyAsText().lines()
- for (line in lines) {
- val match = versionRegex.matchEntire(line) ?: continue
- runCatching {
- val parsed = match.groups["version"]!!.value
- if (parsed eqI BuildConfig.APP_VERSION)
- return@runBlocking true
- }
- }
-
- return@runBlocking false
-}
-
fun downloadNewInstaller() = launchGlobal {
val response = httpClient.get("${BuildConfig.BASE_URL}/download/desktop?token=${login?.accessToken}")
if (!response.status.isSuccess())
diff --git a/src/main/kotlin/moe/styx/logic/viewmodels/DesktopOverViewModel.kt b/src/main/kotlin/moe/styx/logic/viewmodels/DesktopOverViewModel.kt
new file mode 100644
index 0000000..990a318
--- /dev/null
+++ b/src/main/kotlin/moe/styx/logic/viewmodels/DesktopOverViewModel.kt
@@ -0,0 +1,8 @@
+package moe.styx.logic.viewmodels
+
+import moe.styx.common.compose.viewmodels.OverviewViewModel
+import moe.styx.logic.utils.MpvUtils
+
+class DesktopOverViewModel : OverviewViewModel() {
+ override fun checkPlayerVersion() = MpvUtils.checkVersionAndDownload()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/theme/Color.kt b/src/main/kotlin/moe/styx/theme/Color.kt
index f2ec1f8..8c42659 100644
--- a/src/main/kotlin/moe/styx/theme/Color.kt
+++ b/src/main/kotlin/moe/styx/theme/Color.kt
@@ -4,132 +4,297 @@ import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
-val md_theme_light_primary = Color(0xFF006D31)
-val md_theme_light_onPrimary = Color(0xFFFFFFFF)
-val md_theme_light_primaryContainer = Color(0xFF6EFE94)
-val md_theme_light_onPrimaryContainer = Color(0xFF00210A)
-val md_theme_light_secondary = Color(0xFF842BD2)
-val md_theme_light_onSecondary = Color(0xFFFFFFFF)
-val md_theme_light_secondaryContainer = Color(0xFFF0DBFF)
-val md_theme_light_onSecondaryContainer = Color(0xFF2C0051)
-val md_theme_light_tertiary = Color(0xFF206C2F)
-val md_theme_light_onTertiary = Color(0xFFFFFFFF)
-val md_theme_light_tertiaryContainer = Color(0xFFA6F5A8)
-val md_theme_light_onTertiaryContainer = Color(0xFF002106)
-val md_theme_light_error = Color(0xFFBA1A1A)
-val md_theme_light_errorContainer = Color(0xFFFFDAD6)
-val md_theme_light_onError = Color(0xFFFFFFFF)
-val md_theme_light_onErrorContainer = Color(0xFF410002)
-val md_theme_light_background = Color(0xFFFCFDF7)
-val md_theme_light_onBackground = Color(0xFF1A1C19)
-val md_theme_light_surface = Color(0xFFFCFDF7)
-val md_theme_light_onSurface = Color(0xFF1A1C19)
-val md_theme_light_surfaceVariant = Color(0xFFDDE5DA)
-val md_theme_light_onSurfaceVariant = Color(0xFF414941)
-val md_theme_light_outline = Color(0xFF727970)
-val md_theme_light_inverseOnSurface = Color(0xFFF0F1EC)
-val md_theme_light_inverseSurface = Color(0xFF2E312E)
-val md_theme_light_inversePrimary = Color(0xFF4FE17B)
-val md_theme_light_shadow = Color(0xFF000000)
-val md_theme_light_surfaceTint = Color(0xFF006D31)
-val md_theme_light_outlineVariant = Color(0xFFC1C9BE)
-val md_theme_light_scrim = Color(0xFF000000)
+val primaryLight = Color(0xFF006D31)
+val onPrimaryLight = Color(0xFFFFFFFF)
+val primaryContainerLight = Color(0xFF59EA83)
+val onPrimaryContainerLight = Color(0xFF00461D)
+val secondaryLight = Color(0xFF611E9A)
+val onSecondaryLight = Color(0xFFFFFFFF)
+val secondaryContainerLight = Color(0xFF8749C1)
+val onSecondaryContainerLight = Color(0xFFFFFFFF)
+val tertiaryLight = Color(0xFF00561C)
+val onTertiaryLight = Color(0xFFFFFFFF)
+val tertiaryContainerLight = Color(0xFF327C3D)
+val onTertiaryContainerLight = Color(0xFFFFFFFF)
+val errorLight = Color(0xFFBA1A1A)
+val onErrorLight = Color(0xFFFFFFFF)
+val errorContainerLight = Color(0xFFFFDAD6)
+val onErrorContainerLight = Color(0xFF410002)
+val backgroundLight = Color(0xFFF3FCF0)
+val onBackgroundLight = Color(0xFF161D16)
+val surfaceLight = Color(0xFFF3FCF0)
+val onSurfaceLight = Color(0xFF161D16)
+val surfaceVariantLight = Color(0xFFD8E7D5)
+val onSurfaceVariantLight = Color(0xFF3D4A3D)
+val outlineLight = Color(0xFF6D7B6C)
+val outlineVariantLight = Color(0xFFBCCBBA)
+val scrimLight = Color(0xFF000000)
+val inverseSurfaceLight = Color(0xFF2B322B)
+val inverseOnSurfaceLight = Color(0xFFEBF3E7)
+val inversePrimaryLight = Color(0xFF4FE17B)
+val surfaceDimLight = Color(0xFFD4DCD1)
+val surfaceBrightLight = Color(0xFFF3FCF0)
+val surfaceContainerLowestLight = Color(0xFFFFFFFF)
+val surfaceContainerLowLight = Color(0xFFEEF6EA)
+val surfaceContainerLight = Color(0xFFE8F0E4)
+val surfaceContainerHighLight = Color(0xFFE2EBDF)
+val surfaceContainerHighestLight = Color(0xFFDDE5D9)
-val md_theme_dark_primary = Color(0xFF4FE17B)
-val md_theme_dark_onPrimary = Color(0xFF003916)
-val md_theme_dark_primaryContainer = Color(0xFF005323)
-val md_theme_dark_onPrimaryContainer = Color(0xFF6EFE94)
-val md_theme_dark_secondary = Color(0xFFDDB8FF)
-val md_theme_dark_onSecondary = Color(0xFF490080)
-val md_theme_dark_secondaryContainer = Color(0xFF6800B3)
-val md_theme_dark_onSecondaryContainer = Color(0xFFF0DBFF)
-val md_theme_dark_tertiary = Color(0xFF8BD88E)
-val md_theme_dark_onTertiary = Color(0xFF003910)
-val md_theme_dark_tertiaryContainer = Color(0xFF00531B)
-val md_theme_dark_onTertiaryContainer = Color(0xFFA6F5A8)
-val md_theme_dark_error = Color(0xFFFFB4AB)
-val md_theme_dark_errorContainer = Color(0xFF93000A)
-val md_theme_dark_onError = Color(0xFF690005)
-val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
-val md_theme_dark_background = Color(0xFF1A1C19)
-val md_theme_dark_onBackground = Color(0xFFE2E3DE)
-val md_theme_dark_surface = Color(0xFF1A1C19)
-val md_theme_dark_onSurface = Color(0xFFE2E3DE)
-val md_theme_dark_surfaceVariant = Color(0xFF414941)
-val md_theme_dark_onSurfaceVariant = Color(0xFFC1C9BE)
-val md_theme_dark_outline = Color(0xFF8B9389)
-val md_theme_dark_inverseOnSurface = Color(0xFF1A1C19)
-val md_theme_dark_inverseSurface = Color(0xFFE2E3DE)
-val md_theme_dark_inversePrimary = Color(0xFF006D31)
-val md_theme_dark_shadow = Color(0xFF000000)
-val md_theme_dark_surfaceTint = Color(0xFF4FE17B)
-val md_theme_dark_outlineVariant = Color(0xFF414941)
-val md_theme_dark_scrim = Color(0xFF000000)
+val primaryLightMediumContrast = Color(0xFF004E21)
+val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
+val primaryContainerLightMediumContrast = Color(0xFF00873E)
+val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val secondaryLightMediumContrast = Color(0xFF5F1C99)
+val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightMediumContrast = Color(0xFF8749C1)
+val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryLightMediumContrast = Color(0xFF004E19)
+val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightMediumContrast = Color(0xFF327C3D)
+val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val errorLightMediumContrast = Color(0xFF8C0009)
+val onErrorLightMediumContrast = Color(0xFFFFFFFF)
+val errorContainerLightMediumContrast = Color(0xFFDA342E)
+val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
+val backgroundLightMediumContrast = Color(0xFFF3FCF0)
+val onBackgroundLightMediumContrast = Color(0xFF161D16)
+val surfaceLightMediumContrast = Color(0xFFF3FCF0)
+val onSurfaceLightMediumContrast = Color(0xFF161D16)
+val surfaceVariantLightMediumContrast = Color(0xFFD8E7D5)
+val onSurfaceVariantLightMediumContrast = Color(0xFF394639)
+val outlineLightMediumContrast = Color(0xFF556355)
+val outlineVariantLightMediumContrast = Color(0xFF707E70)
+val scrimLightMediumContrast = Color(0xFF000000)
+val inverseSurfaceLightMediumContrast = Color(0xFF2B322B)
+val inverseOnSurfaceLightMediumContrast = Color(0xFFEBF3E7)
+val inversePrimaryLightMediumContrast = Color(0xFF4FE17B)
+val surfaceDimLightMediumContrast = Color(0xFFD4DCD1)
+val surfaceBrightLightMediumContrast = Color(0xFFF3FCF0)
+val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightMediumContrast = Color(0xFFEEF6EA)
+val surfaceContainerLightMediumContrast = Color(0xFFE8F0E4)
+val surfaceContainerHighLightMediumContrast = Color(0xFFE2EBDF)
+val surfaceContainerHighestLightMediumContrast = Color(0xFFDDE5D9)
+val primaryLightHighContrast = Color(0xFF00290E)
+val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
+val primaryContainerLightHighContrast = Color(0xFF004E21)
+val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val secondaryLightHighContrast = Color(0xFF36005F)
+val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightHighContrast = Color(0xFF5F1C99)
+val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryLightHighContrast = Color(0xFF002909)
+val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightHighContrast = Color(0xFF004E19)
+val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val errorLightHighContrast = Color(0xFF4E0002)
+val onErrorLightHighContrast = Color(0xFFFFFFFF)
+val errorContainerLightHighContrast = Color(0xFF8C0009)
+val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
+val backgroundLightHighContrast = Color(0xFFF3FCF0)
+val onBackgroundLightHighContrast = Color(0xFF161D16)
+val surfaceLightHighContrast = Color(0xFFF3FCF0)
+val onSurfaceLightHighContrast = Color(0xFF000000)
+val surfaceVariantLightHighContrast = Color(0xFFD8E7D5)
+val onSurfaceVariantLightHighContrast = Color(0xFF1A271C)
+val outlineLightHighContrast = Color(0xFF394639)
+val outlineVariantLightHighContrast = Color(0xFF394639)
+val scrimLightHighContrast = Color(0xFF000000)
+val inverseSurfaceLightHighContrast = Color(0xFF2B322B)
+val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
+val inversePrimaryLightHighContrast = Color(0xFFACFFB8)
+val surfaceDimLightHighContrast = Color(0xFFD4DCD1)
+val surfaceBrightLightHighContrast = Color(0xFFF3FCF0)
+val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightHighContrast = Color(0xFFEEF6EA)
+val surfaceContainerLightHighContrast = Color(0xFFE8F0E4)
+val surfaceContainerHighLightHighContrast = Color(0xFFE2EBDF)
+val surfaceContainerHighestLightHighContrast = Color(0xFFDDE5D9)
-val seed = Color(0xFF4FE17B)
+val primaryDark = Color(0xFFA3FFB2)
+val onPrimaryDark = Color(0xFF003916)
+val primaryContainerDark = Color(0xFF47DA75)
+val onPrimaryContainerDark = Color(0xFF003B17)
+val secondaryDark = Color(0xFFDEB7FF)
+val onSecondaryDark = Color(0xFF4A007F)
+val secondaryContainerDark = Color(0xFF7E3FB7)
+val onSecondaryContainerDark = Color(0xFFFFFFFF)
+val tertiaryDark = Color(0xFF8BD88E)
+val onTertiaryDark = Color(0xFF003910)
+val tertiaryContainerDark = Color(0xFF126226)
+val onTertiaryContainerDark = Color(0xFFE4FFDF)
+val errorDark = Color(0xFFFFB4AB)
+val onErrorDark = Color(0xFF690005)
+val errorContainerDark = Color(0xFF93000A)
+val onErrorContainerDark = Color(0xFFFFDAD6)
+val backgroundDark = Color(0xFF0E150E)
+val onBackgroundDark = Color(0xFFDDE5D9)
+val surfaceDark = Color(0xFF0E150E)
+val onSurfaceDark = Color(0xFFDDE5D9)
+val surfaceVariantDark = Color(0xFF3D4A3D)
+val onSurfaceVariantDark = Color(0xFFBCCBBA)
+val outlineDark = Color(0xFF869585)
+val outlineVariantDark = Color(0xFF3D4A3D)
+val scrimDark = Color(0xFF000000)
+val inverseSurfaceDark = Color(0xFFDDE5D9)
+val inverseOnSurfaceDark = Color(0xFF2B322B)
+val inversePrimaryDark = Color(0xFF006D31)
+val surfaceDimDark = Color(0xFF0E150E)
+val surfaceBrightDark = Color(0xFF333B33)
+val surfaceContainerLowestDark = Color(0xFF091009)
+val surfaceContainerLowDark = Color(0xFF161D16)
+val surfaceContainerDark = Color(0xFF1A211A)
+val surfaceContainerHighDark = Color(0xFF242C24)
+val surfaceContainerHighestDark = Color(0xFF2F372F)
+
+val primaryDarkMediumContrast = Color(0xFFA3FFB2)
+val onPrimaryDarkMediumContrast = Color(0xFF003815)
+val primaryContainerDarkMediumContrast = Color(0xFF47DA75)
+val onPrimaryContainerDarkMediumContrast = Color(0xFF000A02)
+val secondaryDarkMediumContrast = Color(0xFFE1BDFF)
+val onSecondaryDarkMediumContrast = Color(0xFF250044)
+val secondaryContainerDarkMediumContrast = Color(0xFFB374EE)
+val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
+val tertiaryDarkMediumContrast = Color(0xFF8FDD92)
+val onTertiaryDarkMediumContrast = Color(0xFF001B05)
+val tertiaryContainerDarkMediumContrast = Color(0xFF56A15D)
+val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
+val errorDarkMediumContrast = Color(0xFFFFBAB1)
+val onErrorDarkMediumContrast = Color(0xFF370001)
+val errorContainerDarkMediumContrast = Color(0xFFFF5449)
+val onErrorContainerDarkMediumContrast = Color(0xFF000000)
+val backgroundDarkMediumContrast = Color(0xFF0E150E)
+val onBackgroundDarkMediumContrast = Color(0xFFDDE5D9)
+val surfaceDarkMediumContrast = Color(0xFF0E150E)
+val onSurfaceDarkMediumContrast = Color(0xFFF5FDF1)
+val surfaceVariantDarkMediumContrast = Color(0xFF3D4A3D)
+val onSurfaceVariantDarkMediumContrast = Color(0xFFC0CFBE)
+val outlineDarkMediumContrast = Color(0xFF98A797)
+val outlineVariantDarkMediumContrast = Color(0xFF798778)
+val scrimDarkMediumContrast = Color(0xFF000000)
+val inverseSurfaceDarkMediumContrast = Color(0xFFDDE5D9)
+val inverseOnSurfaceDarkMediumContrast = Color(0xFF242C24)
+val inversePrimaryDarkMediumContrast = Color(0xFF005424)
+val surfaceDimDarkMediumContrast = Color(0xFF0E150E)
+val surfaceBrightDarkMediumContrast = Color(0xFF333B33)
+val surfaceContainerLowestDarkMediumContrast = Color(0xFF091009)
+val surfaceContainerLowDarkMediumContrast = Color(0xFF161D16)
+val surfaceContainerDarkMediumContrast = Color(0xFF1A211A)
+val surfaceContainerHighDarkMediumContrast = Color(0xFF242C24)
+val surfaceContainerHighestDarkMediumContrast = Color(0xFF2F372F)
+
+val primaryDarkHighContrast = Color(0xFFF0FFED)
+val onPrimaryDarkHighContrast = Color(0xFF000000)
+val primaryContainerDarkHighContrast = Color(0xFF54E57E)
+val onPrimaryContainerDarkHighContrast = Color(0xFF000000)
+val secondaryDarkHighContrast = Color(0xFFFFF9FC)
+val onSecondaryDarkHighContrast = Color(0xFF000000)
+val secondaryContainerDarkHighContrast = Color(0xFFE1BDFF)
+val onSecondaryContainerDarkHighContrast = Color(0xFF000000)
+val tertiaryDarkHighContrast = Color(0xFFF0FFEB)
+val onTertiaryDarkHighContrast = Color(0xFF000000)
+val tertiaryContainerDarkHighContrast = Color(0xFF8FDD92)
+val onTertiaryContainerDarkHighContrast = Color(0xFF000000)
+val errorDarkHighContrast = Color(0xFFFFF9F9)
+val onErrorDarkHighContrast = Color(0xFF000000)
+val errorContainerDarkHighContrast = Color(0xFFFFBAB1)
+val onErrorContainerDarkHighContrast = Color(0xFF000000)
+val backgroundDarkHighContrast = Color(0xFF0E150E)
+val onBackgroundDarkHighContrast = Color(0xFFDDE5D9)
+val surfaceDarkHighContrast = Color(0xFF0E150E)
+val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
+val surfaceVariantDarkHighContrast = Color(0xFF3D4A3D)
+val onSurfaceVariantDarkHighContrast = Color(0xFFF0FFED)
+val outlineDarkHighContrast = Color(0xFFC0CFBE)
+val outlineVariantDarkHighContrast = Color(0xFFC0CFBE)
+val scrimDarkHighContrast = Color(0xFF000000)
+val inverseSurfaceDarkHighContrast = Color(0xFFDDE5D9)
+val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
+val inversePrimaryDarkHighContrast = Color(0xFF003212)
+val surfaceDimDarkHighContrast = Color(0xFF0E150E)
+val surfaceBrightDarkHighContrast = Color(0xFF333B33)
+val surfaceContainerLowestDarkHighContrast = Color(0xFF091009)
+val surfaceContainerLowDarkHighContrast = Color(0xFF161D16)
+val surfaceContainerDarkHighContrast = Color(0xFF1A211A)
+val surfaceContainerHighDarkHighContrast = Color(0xFF242C24)
+val surfaceContainerHighestDarkHighContrast = Color(0xFF2F372F)
-val LightColorScheme = lightColorScheme(
- primary = md_theme_light_primary,
- onPrimary = md_theme_light_onPrimary,
- primaryContainer = md_theme_light_primaryContainer,
- onPrimaryContainer = md_theme_light_onPrimaryContainer,
- secondary = md_theme_light_secondary,
- onSecondary = md_theme_light_onSecondary,
- secondaryContainer = md_theme_light_secondaryContainer,
- onSecondaryContainer = md_theme_light_onSecondaryContainer,
- tertiary = md_theme_light_tertiary,
- onTertiary = md_theme_light_onTertiary,
- tertiaryContainer = md_theme_light_tertiaryContainer,
- onTertiaryContainer = md_theme_light_onTertiaryContainer,
- error = md_theme_light_error,
- errorContainer = md_theme_light_errorContainer,
- onError = md_theme_light_onError,
- onErrorContainer = md_theme_light_onErrorContainer,
- background = md_theme_light_background,
- onBackground = md_theme_light_onBackground,
- surface = md_theme_light_surface,
- onSurface = md_theme_light_onSurface,
- surfaceVariant = md_theme_light_surfaceVariant,
- onSurfaceVariant = md_theme_light_onSurfaceVariant,
- outline = md_theme_light_outline,
- inverseOnSurface = md_theme_light_inverseOnSurface,
- inverseSurface = md_theme_light_inverseSurface,
- inversePrimary = md_theme_light_inversePrimary,
- surfaceTint = md_theme_light_surfaceTint,
- outlineVariant = md_theme_light_outlineVariant,
- scrim = md_theme_light_scrim,
+val seed = Color(0xFF4FE17B)
+
+val lightScheme = lightColorScheme(
+ primary = primaryLight,
+ onPrimary = onPrimaryLight,
+ primaryContainer = primaryContainerLight,
+ onPrimaryContainer = onPrimaryContainerLight,
+ secondary = secondaryLight,
+ onSecondary = onSecondaryLight,
+ secondaryContainer = secondaryContainerLight,
+ onSecondaryContainer = onSecondaryContainerLight,
+ tertiary = tertiaryLight,
+ onTertiary = onTertiaryLight,
+ tertiaryContainer = tertiaryContainerLight,
+ onTertiaryContainer = onTertiaryContainerLight,
+ error = errorLight,
+ onError = onErrorLight,
+ errorContainer = errorContainerLight,
+ onErrorContainer = onErrorContainerLight,
+ background = backgroundLight,
+ onBackground = onBackgroundLight,
+ surface = surfaceLight,
+ onSurface = onSurfaceLight,
+ surfaceVariant = surfaceVariantLight,
+ onSurfaceVariant = onSurfaceVariantLight,
+ outline = outlineLight,
+ outlineVariant = outlineVariantLight,
+ scrim = scrimLight,
+ inverseSurface = inverseSurfaceLight,
+ inverseOnSurface = inverseOnSurfaceLight,
+ inversePrimary = inversePrimaryLight,
+ surfaceDim = surfaceDimLight,
+ surfaceBright = surfaceBrightLight,
+ surfaceContainerLowest = surfaceContainerLowestLight,
+ surfaceContainerLow = surfaceContainerLowLight,
+ surfaceContainer = surfaceContainerLight,
+ surfaceContainerHigh = surfaceContainerHighLight,
+ surfaceContainerHighest = surfaceContainerHighestLight,
)
-val DarkColorScheme = darkColorScheme(
- primary = md_theme_dark_primary,
- onPrimary = md_theme_dark_onPrimary,
- primaryContainer = md_theme_dark_primaryContainer,
- onPrimaryContainer = md_theme_dark_onPrimaryContainer,
- secondary = md_theme_dark_secondary,
- onSecondary = md_theme_dark_onSecondary,
- secondaryContainer = md_theme_dark_secondaryContainer,
- onSecondaryContainer = md_theme_dark_onSecondaryContainer,
- tertiary = md_theme_dark_tertiary,
- onTertiary = md_theme_dark_onTertiary,
- tertiaryContainer = md_theme_dark_tertiaryContainer,
- onTertiaryContainer = md_theme_dark_onTertiaryContainer,
- error = md_theme_dark_error,
- errorContainer = md_theme_dark_errorContainer,
- onError = md_theme_dark_onError,
- onErrorContainer = md_theme_dark_onErrorContainer,
- background = md_theme_dark_background,
- onBackground = md_theme_dark_onBackground,
- surface = md_theme_dark_surface,
- onSurface = md_theme_dark_onSurface,
- surfaceVariant = md_theme_dark_surfaceVariant,
- onSurfaceVariant = md_theme_dark_onSurfaceVariant,
- outline = md_theme_dark_outline,
- inverseOnSurface = md_theme_dark_inverseOnSurface,
- inverseSurface = md_theme_dark_inverseSurface,
- inversePrimary = md_theme_dark_inversePrimary,
- surfaceTint = md_theme_dark_surfaceTint,
- outlineVariant = md_theme_dark_outlineVariant,
- scrim = md_theme_dark_scrim,
+val darkScheme = darkColorScheme(
+ primary = primaryDark,
+ onPrimary = onPrimaryDark,
+ primaryContainer = primaryContainerDark,
+ onPrimaryContainer = onPrimaryContainerDark,
+ secondary = secondaryDark,
+ onSecondary = onSecondaryDark,
+ secondaryContainer = secondaryContainerDark,
+ onSecondaryContainer = onSecondaryContainerDark,
+ tertiary = tertiaryDark,
+ onTertiary = onTertiaryDark,
+ tertiaryContainer = tertiaryContainerDark,
+ onTertiaryContainer = onTertiaryContainerDark,
+ error = errorDark,
+ onError = onErrorDark,
+ errorContainer = errorContainerDark,
+ onErrorContainer = onErrorContainerDark,
+ background = backgroundDark,
+ onBackground = onBackgroundDark,
+ surface = surfaceDark,
+ onSurface = onSurfaceDark,
+ surfaceVariant = surfaceVariantDark,
+ onSurfaceVariant = onSurfaceVariantDark,
+ outline = outlineDark,
+ outlineVariant = outlineVariantDark,
+ scrim = scrimDark,
+ inverseSurface = inverseSurfaceDark,
+ inverseOnSurface = inverseOnSurfaceDark,
+ inversePrimary = inversePrimaryDark,
+ surfaceDim = surfaceDimDark,
+ surfaceBright = surfaceBrightDark,
+ surfaceContainerLowest = surfaceContainerLowestDark,
+ surfaceContainerLow = surfaceContainerLowDark,
+ surfaceContainer = surfaceContainerDark,
+ surfaceContainerHigh = surfaceContainerHighDark,
+ surfaceContainerHighest = surfaceContainerHighestDark,
)
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/theme/Theme.kt b/src/main/kotlin/moe/styx/theme/Theme.kt
index ace1ac7..6fd5bee 100644
--- a/src/main/kotlin/moe/styx/theme/Theme.kt
+++ b/src/main/kotlin/moe/styx/theme/Theme.kt
@@ -33,37 +33,43 @@ object AppFont {
Font("fonts/OpenSans-Italic.ttf", FontWeight.Normal, FontStyle.Italic),
Font("fonts/OpenSans-Medium.ttf", FontWeight.Medium, FontStyle.Normal),
Font("fonts/OpenSans-MediumItalic.ttf", FontWeight.Medium, FontStyle.Italic),
- Font("fonts/OpenSans-SemiBold.ttf", FontWeight.SemiBold, FontStyle.Normal),
- Font("fonts/OpenSans-SemiBoldItalic.ttf", FontWeight.SemiBold, FontStyle.Italic),
Font("fonts/OpenSans-Bold.ttf", FontWeight.Bold, FontStyle.Normal),
Font("fonts/OpenSans-BoldItalic.ttf", FontWeight.Bold, FontStyle.Italic),
- Font("fonts/OpenSans-ExtraBold.ttf", FontWeight.ExtraBold, FontStyle.Normal),
- Font("fonts/OpenSans-ExtraBoldItalic.ttf", FontWeight.ExtraBold, FontStyle.Italic),
+ )
+ val JetbrainsMono = FontFamily(
+ Font("fonts/JetBrainsMono-Light.ttf", FontWeight.Light, FontStyle.Normal),
+ Font("fonts/JetBrainsMono-LightItalic.ttf", FontWeight.Light, FontStyle.Italic),
+ Font("fonts/JetBrainsMono-Regular.ttf", FontWeight.Normal, FontStyle.Normal),
+ Font("fonts/JetBrainsMono-Italic.ttf", FontWeight.Normal, FontStyle.Italic),
+ Font("fonts/JetBrainsMono-Medium.ttf", FontWeight.Medium, FontStyle.Normal),
+ Font("fonts/JetBrainsMono-MediumItalic.ttf", FontWeight.Medium, FontStyle.Italic),
+ Font("fonts/JetBrainsMono-Bold.ttf", FontWeight.Bold, FontStyle.Normal),
+ Font("fonts/JetBrainsMono-BoldItalic.ttf", FontWeight.Bold, FontStyle.Italic),
)
}
-val AppTypography = Typography(
- displayLarge = defaultTypo.displayLarge.copy(fontFamily = AppFont.OpenSans),
- displayMedium = defaultTypo.displayMedium.copy(fontFamily = AppFont.OpenSans),
- displaySmall = defaultTypo.displaySmall.copy(fontFamily = AppFont.OpenSans),
-
- headlineLarge = defaultTypo.headlineLarge.copy(fontFamily = AppFont.OpenSans),
- headlineMedium = defaultTypo.headlineMedium.copy(fontFamily = AppFont.OpenSans),
- headlineSmall = defaultTypo.headlineSmall.copy(fontFamily = AppFont.OpenSans),
+val FontFamily.Typography: Typography
+ get() = Typography(
+ displayLarge = defaultTypo.displayLarge.copy(fontFamily = this),
+ displayMedium = defaultTypo.displayMedium.copy(fontFamily = this),
+ displaySmall = defaultTypo.displaySmall.copy(fontFamily = this),
- titleLarge = defaultTypo.titleLarge.copy(fontFamily = AppFont.OpenSans),
- titleMedium = defaultTypo.titleMedium.copy(fontFamily = AppFont.OpenSans),
- titleSmall = defaultTypo.titleSmall.copy(fontFamily = AppFont.OpenSans),
+ headlineLarge = defaultTypo.headlineLarge.copy(fontFamily = this),
+ headlineMedium = defaultTypo.headlineMedium.copy(fontFamily = this),
+ headlineSmall = defaultTypo.headlineSmall.copy(fontFamily = this),
- bodyLarge = defaultTypo.bodyLarge.copy(fontFamily = AppFont.OpenSans),
- bodyMedium = defaultTypo.bodyMedium.copy(fontFamily = AppFont.OpenSans),
- bodySmall = defaultTypo.bodySmall.copy(fontFamily = AppFont.OpenSans),
+ titleLarge = defaultTypo.titleLarge.copy(fontFamily = this),
+ titleMedium = defaultTypo.titleMedium.copy(fontFamily = this),
+ titleSmall = defaultTypo.titleSmall.copy(fontFamily = this),
- labelLarge = defaultTypo.labelLarge.copy(fontFamily = AppFont.OpenSans),
- labelMedium = defaultTypo.labelMedium.copy(fontFamily = AppFont.OpenSans),
- labelSmall = defaultTypo.labelSmall.copy(fontFamily = AppFont.OpenSans)
-)
+ bodyLarge = defaultTypo.bodyLarge.copy(fontFamily = this),
+ bodyMedium = defaultTypo.bodyMedium.copy(fontFamily = this),
+ bodySmall = defaultTypo.bodySmall.copy(fontFamily = this),
+ labelLarge = defaultTypo.labelLarge.copy(fontFamily = this),
+ labelMedium = defaultTypo.labelMedium.copy(fontFamily = this),
+ labelSmall = defaultTypo.labelSmall.copy(fontFamily = this)
+ )
private val animationSpec: TweenSpec = tween(durationMillis = 650)
diff --git a/src/main/kotlin/moe/styx/views/Tabs.kt b/src/main/kotlin/moe/styx/views/Tabs.kt
index 9ebec75..e9feee1 100644
--- a/src/main/kotlin/moe/styx/views/Tabs.kt
+++ b/src/main/kotlin/moe/styx/views/Tabs.kt
@@ -11,26 +11,31 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce
import moe.styx.common.compose.components.search.MediaSearch
import moe.styx.common.compose.utils.SearchState
+import moe.styx.common.compose.viewmodels.ListPosViewModel
+import moe.styx.common.compose.viewmodels.MainDataViewModelStorage
import moe.styx.common.data.Favourite
import moe.styx.common.data.Media
import moe.styx.components.overviews.MediaGrid
import moe.styx.components.overviews.MediaList
-import moe.styx.views.anime.tabs.*
-import moe.styx.views.settings.SettingsView
+import moe.styx.views.anime.tabs.MediaListView
+import moe.styx.views.anime.tabs.ScheduleView
+import moe.styx.views.settings.SettingsTab
-val defaultTab = AnimeListView()
-val movieTab = MovieListView()
-var favsTab = FavouritesListView()
+val defaultTab = MediaListView()
+val movieTab = MediaListView(movies = true)
+var favsTab = MediaListView(favourites = true)
val scheduleTab = ScheduleView()
-val settingsTab = SettingsView()
+val settingsTab = SettingsTab()
@OptIn(FlowPreview::class)
@Composable
internal fun Tab.barWithListComp(
mediaSearch: MediaSearch,
initialState: SearchState,
+ storage: MainDataViewModelStorage,
filtered: List,
useList: Boolean = false,
+ listPosViewModel: ListPosViewModel,
showUnseen: Boolean = false,
favourites: List = emptyList()
) {
@@ -40,9 +45,9 @@ internal fun Tab.barWithListComp(
val flow by mediaSearch.stateEmitter.debounce(150L).collectAsState(initialState)
val processedMedia = flow.filterMedia(filtered, favourites)
if (!useList)
- MediaGrid(processedMedia, showUnseen)
+ MediaGrid(storage, processedMedia, listPosViewModel, showUnseen)
else
- MediaList(processedMedia)
+ MediaList(storage, processedMedia, listPosViewModel)
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/anime/AnimeDetailView.kt b/src/main/kotlin/moe/styx/views/anime/AnimeDetailView.kt
index 727ab1a..e42c5e9 100644
--- a/src/main/kotlin/moe/styx/views/anime/AnimeDetailView.kt
+++ b/src/main/kotlin/moe/styx/views/anime/AnimeDetailView.kt
@@ -14,33 +14,34 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.ScreenKey
+import com.dokar.sonner.TextToastAction
+import com.dokar.sonner.Toast
+import com.dokar.sonner.ToastType
import com.russhwolf.settings.get
import moe.styx.common.compose.components.anime.*
import moe.styx.common.compose.components.buttons.FavouriteIconButton
import moe.styx.common.compose.components.layout.MainScaffold
import moe.styx.common.compose.components.misc.OnlineUsersIcon
import moe.styx.common.compose.extensions.getPainter
-import moe.styx.common.compose.extensions.getThumb
-import moe.styx.common.compose.files.Storage
-import moe.styx.common.compose.files.getCurrentAndCollectFlow
import moe.styx.common.compose.settings
-import moe.styx.common.compose.threads.Heartbeats
import moe.styx.common.compose.utils.LocalGlobalNavigator
+import moe.styx.common.compose.utils.LocalToaster
+import moe.styx.common.compose.viewmodels.MainDataViewModel
+import moe.styx.common.compose.viewmodels.MediaStorage
import moe.styx.common.data.Media
import moe.styx.common.data.MediaEntry
-import moe.styx.common.extension.eqI
import moe.styx.components.anime.AppendDialog
import moe.styx.components.anime.BigScalingCardImage
-import moe.styx.components.anime.FailedDialog
import moe.styx.logic.runner.currentPlayer
import moe.styx.logic.runner.launchMPV
+import moe.styx.logic.runner.openURI
import moe.styx.logic.utils.*
import moe.styx.theme.AppShapes
+import moe.styx.views.settings.SettingsTab
import moe.styx.views.settings.SettingsView
-import java.awt.Desktop
-import java.net.URI
class AnimeDetailView(private val mediaID: String) : Screen {
@@ -51,70 +52,70 @@ class AnimeDetailView(private val mediaID: String) : Screen {
@Composable
override fun Content() {
val nav = LocalGlobalNavigator.current
+ val toaster = LocalToaster.current
+ val sm = nav.rememberNavigatorScreenModel("main-vm") { MainDataViewModel() }
+ val storage by sm.storageFlow.collectAsState()
+ val mediaStorage = remember(storage) { sm.getMediaStorageForID(mediaID, storage) }
- val mediaList by Storage.stores.mediaStore.getCurrentAndCollectFlow()
- val media = remember { mediaList.find { it.GUID eqI mediaID } }
- if (media == null) {
- nav.pop()
- return
- }
- val entries = fetchEntries(mediaID)
-
- val preferGerman = settings["prefer-german-metadata", false]
+ val preferGerman = remember { settings["prefer-german-metadata", false] }
val scrollState = rememberScrollState()
val showSelection = remember { mutableStateOf(false) }
- MainScaffold(title = media.name, actions = {
+ MainScaffold(title = mediaStorage.media.name, actions = {
OnlineUsersIcon { nav.pushMediaView(it, true) }
- FavouriteIconButton(media)
+ FavouriteIconButton(mediaStorage.media, sm, storage)
}) {
var failedToPlayMessage by remember { mutableStateOf("") }
if (failedToPlayMessage.isNotBlank()) {
- FailedDialog(failedToPlayMessage, Modifier.fillMaxWidth(0.6F)) {
- failedToPlayMessage = ""
- if (it) nav.push(SettingsView())
- }
+ toaster.show(Toast(failedToPlayMessage, type = ToastType.Error, action = TextToastAction("Open Settings") {
+ nav.push(SettingsView())
+ }))
+ failedToPlayMessage = ""
}
var appendEntry by remember { mutableStateOf(null) }
if (appendEntry != null) {
AppendDialog(appendEntry!!, Modifier.fillMaxWidth(0.6F), onDismiss = {
appendEntry = null
}) {
- failedToPlayMessage = it
- appendEntry = null
+ if (!it.isOK)
+ failedToPlayMessage = it.message
+ else
+ sm.updateData(true)
}
}
ElevatedCard(
- Modifier.padding(8.dp).fillMaxSize(),
- colors = CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colorScheme.surface),
- elevation = CardDefaults.elevatedCardElevation(8.dp)
+ Modifier.padding(8.dp).fillMaxSize()
) {
Row(Modifier.padding(5.dp).fillMaxSize()) {
Column(Modifier.fillMaxHeight().fillMaxWidth(.52F).verticalScroll(scrollState)) {
- StupidImageNameArea(media)
+ StupidImageNameArea(mediaStorage)
Spacer(Modifier.height(6.dp))
Text("About", Modifier.padding(6.dp, 2.dp), style = MaterialTheme.typography.titleLarge)
- MediaGenreListing(media)
- val synopsis = if (!media.synopsisDE.isNullOrBlank() && preferGerman) media.synopsisDE else media.synopsisEN
+ MediaGenreListing(mediaStorage.media)
+ val synopsis =
+ if (!mediaStorage.media.synopsisDE.isNullOrBlank() && preferGerman) mediaStorage.media.synopsisDE else mediaStorage.media.synopsisEN
if (!synopsis.isNullOrBlank())
SelectionContainer {
Text(synopsis.removeSomeHTMLTags(), Modifier.padding(6.dp), style = MaterialTheme.typography.bodyMedium)
}
- if (media.sequel != null || media.prequel != null) {
+ if (mediaStorage.sequel != null || mediaStorage.prequel != null) {
HorizontalDivider(Modifier.fillMaxWidth().padding(0.dp, 4.dp, 0.dp, 2.dp), thickness = 2.dp)
- MediaRelations(media, mediaList) { nav.pushMediaView(it, true) }
+ MediaRelations(mediaStorage) { nav.pushMediaView(it, true) }
}
}
VerticalDivider(Modifier.fillMaxHeight().padding(6.dp), thickness = 3.dp)
- EpisodeList(entries, showSelection, SettingsView(), onPlay = { entry ->
+ EpisodeList(storage, mediaStorage, showSelection, SettingsTab(), onPlay = { entry ->
if (currentPlayer == null) {
- launchMPV(entry, false, {
- failedToPlayMessage = it
- })
+ launchMPV(entry, false) {
+ if (!it.isOK)
+ failedToPlayMessage = it.message
+ else
+ sm.updateData(true)
+ }
} else {
appendEntry = entry
}
@@ -128,14 +129,14 @@ class AnimeDetailView(private val mediaID: String) : Screen {
@Composable
fun StupidImageNameArea(
- media: Media,
+ mediaStorage: MediaStorage,
dynamicMaxWidth: Dp = 760.dp,
requiredWidth: Dp = 385.dp,
requiredHeight: Dp = 535.dp,
otherContent: @Composable () -> Unit = {}
) {
- val img = media.getThumb()!!
- val painter = img.getPainter()
+ val (media, img) = mediaStorage.media to mediaStorage.image
+ val painter = img?.getPainter()
BoxWithConstraints {
val width = this.maxWidth
Row(Modifier.align(Alignment.TopStart).height(IntrinsicSize.Max).fillMaxWidth()) {
@@ -156,16 +157,6 @@ fun StupidImageNameArea(
}
}
-@Composable
-fun fetchEntries(mediaID: String): List {
- Heartbeats.mediaActivity = null
- val flow by Storage.stores.entryStore.getCurrentAndCollectFlow()
- val filtered = flow.filter { it.mediaID eqI mediaID }
- return if (settings["episode-asc", false]) filtered.sortedBy {
- it.entryNumber.toDoubleOrNull() ?: 0.0
- } else filtered.sortedByDescending { it.entryNumber.toDoubleOrNull() ?: 0.0 }
-}
-
@Composable
fun MappingIcons(media: Media) {
val malURL = media.getURLFromMap(StackType.MAL)
@@ -178,8 +169,7 @@ fun MappingIcons(media: Media) {
painterResource("icons/al.svg"),
"AniList",
Modifier.padding(8.dp, 3.dp).size(25.dp).clip(AppShapes.small).clickable {
- if (Desktop.isDesktopSupported())
- Desktop.getDesktop().browse(URI(anilistURL))
+ openURI(anilistURL)
},
contentScale = ContentScale.FillWidth,
colorFilter = filter
@@ -189,8 +179,7 @@ fun MappingIcons(media: Media) {
painterResource("icons/myanimelist.svg"),
"MyAnimeList",
Modifier.padding(8.dp, 3.dp).size(25.dp).clip(AppShapes.small).clickable {
- if (Desktop.isDesktopSupported())
- Desktop.getDesktop().browse(URI(malURL))
+ openURI(malURL)
},
contentScale = ContentScale.FillWidth,
colorFilter = filter
@@ -200,8 +189,7 @@ fun MappingIcons(media: Media) {
painterResource("icons/tmdb.svg"),
"TheMovieDB",
Modifier.padding(8.dp, 3.dp).size(25.dp).clip(AppShapes.small).clickable {
- if (Desktop.isDesktopSupported())
- Desktop.getDesktop().browse(URI(tmdbURL))
+ openURI(tmdbURL)
},
contentScale = ContentScale.FillWidth,
colorFilter = filter
diff --git a/src/main/kotlin/moe/styx/views/anime/AnimeOverview.kt b/src/main/kotlin/moe/styx/views/anime/AnimeOverview.kt
index f4b179b..3cad725 100644
--- a/src/main/kotlin/moe/styx/views/anime/AnimeOverview.kt
+++ b/src/main/kotlin/moe/styx/views/anime/AnimeOverview.kt
@@ -1,43 +1,117 @@
package moe.styx.views.anime
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.QuestionMark
-import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel
+import cafe.adriel.voyager.core.model.rememberScreenModel
+import cafe.adriel.voyager.core.model.screenModelScope
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.tab.*
+import com.dokar.sonner.TextToastAction
+import com.dokar.sonner.Toast
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import moe.styx.Main
-import moe.styx.Styx__.BuildConfig
+import moe.styx.Styx_2.BuildConfig
+import moe.styx.common.compose.components.AppShapes
import moe.styx.common.compose.components.buttons.IconButtonWithTooltip
import moe.styx.common.compose.components.layout.MainScaffold
import moe.styx.common.compose.components.misc.OnlineUsersIcon
import moe.styx.common.compose.files.Storage
+import moe.styx.common.compose.http.login
import moe.styx.common.compose.utils.LocalGlobalNavigator
+import moe.styx.common.compose.utils.LocalToaster
+import moe.styx.common.compose.utils.ServerStatus
+import moe.styx.common.compose.viewmodels.MainDataViewModel
import moe.styx.common.data.Changes
import moe.styx.logic.utils.pushMediaView
+import moe.styx.logic.viewmodels.DesktopOverViewModel
import moe.styx.views.*
+import moe.styx.views.login.LoginView
import moe.styx.views.other.FontSizeView
-import moe.styx.views.other.LoadingView
+import moe.styx.views.other.OutdatedView
class AnimeOverview() : Screen {
+ @OptIn(ExperimentalFoundationApi::class)
@Composable
override fun Content() {
+ val toaster = LocalToaster.current
+ val overviewSm = rememberScreenModel { DesktopOverViewModel() }
+
val nav = LocalGlobalNavigator.current
+ if (overviewSm.isOutdated == true) {
+ nav.replaceAll(OutdatedView())
+ }
+
+ if (overviewSm.isLoggedIn == false && ServerStatus.lastKnown == ServerStatus.UNAUTHORIZED) {
+ nav.replaceAll(LoginView())
+ }
+
+ LaunchedEffect(overviewSm.availablePreRelease) {
+ val ver = overviewSm.availablePreRelease
+ if (!ver.isNullOrBlank()) {
+ toaster.show(
+ Toast(
+ "New Pre-Release version available: $ver",
+ action = TextToastAction("Download") {
+ nav.push(OutdatedView(ver))
+ }
+ )
+ )
+ overviewSm.availablePreRelease = null
+ }
+ }
+
+ val sm = nav.rememberNavigatorScreenModel("main-vm") { MainDataViewModel() }
+ val isLoading by sm.isLoadingStateFlow.collectAsState()
+ val loadingState by sm.loadingStateFlow.collectAsState()
+
+ MainScaffold(title = BuildConfig.APP_NAME, addPopButton = false, addAnimatedTitleBackground = true, actions = {
+ if (isLoading) {
+ TooltipArea({ Text(loadingState) }, Modifier.fillMaxHeight(.9f), delayMillis = 200) {
+ Row(Modifier.fillMaxHeight(), verticalAlignment = Alignment.CenterVertically) {
+ LinearProgressIndicator(
+ trackColor = MaterialTheme.colorScheme.surfaceColorAtElevation(18.dp),
+ gapSize = 0.dp,
+ modifier = Modifier.requiredWidthIn(20.dp, 40.dp)
+ )
+ }
+ }
+ }
+ if (overviewSm.isOffline == true) {
+ IconButtonWithTooltip(Icons.Filled.CloudOff, ServerStatus.getLastKnownText()) {}
+ }
+
+ if (overviewSm.isLoggedIn == false) {
+ IconButtonWithTooltip(Icons.Filled.NoAccounts, "You are not logged in!\nClick to retry.") {
+ overviewSm.screenModelScope.launch {
+ val loginJob = overviewSm.runLoginAndChecks()
+ loginJob.join()
+ if (login != null) {
+ sm.updateData(updateStores = true)
+ }
+ }
+ }
+ }
- MainScaffold(title = BuildConfig.APP_NAME, addPopButton = false, actions = {
OnlineUsersIcon { nav.pushMediaView(it, false) }
if (Main.wasLaunchedInDebug)
IconButton(onClick = { nav.push(FontSizeView()) }, content = { Icon(Icons.Filled.QuestionMark, null) })
IconButtonWithTooltip(Icons.Filled.Refresh, "Reload") {
runBlocking { Storage.stores.changesStore.set(Changes(0, 0)) }
- nav.replaceAll(LoadingView())
+ sm.updateData(forceUpdate = true, updateStores = true)
}
}) {
TabNavigator(defaultTab) {
@@ -51,7 +125,11 @@ class AnimeOverview() : Screen {
@Composable
private fun SideNavRail() {
- NavigationRail(Modifier.fillMaxHeight().padding(0.dp, 0.dp, 5.dp, 0.dp)) {
+ NavigationRail(
+ Modifier.fillMaxHeight().padding(7.dp, 6.dp, 3.dp, 8.dp).shadow(2.dp, AppShapes.large).clip(AppShapes.large),
+ containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
+ ) {
+ Spacer(Modifier.height(8.dp))
RailNavItem(defaultTab)
RailNavItem(movieTab)
RailNavItem(favsTab)
@@ -66,6 +144,7 @@ class AnimeOverview() : Screen {
indicatorColor = MaterialTheme.colorScheme.secondary
)
)
+ Spacer(Modifier.height(5.dp))
}
}
@@ -78,7 +157,7 @@ class AnimeOverview() : Screen {
selected = tabNavigator.current.key == tab.key,
onClick = { tabNavigator.current = tab },
icon = { Icon(painter = tab.options.icon!!, contentDescription = tab.options.title) },
- label = { Text(tab.options.title) },
+ label = { Text(tab.options.title, modifier = Modifier.padding(3.dp, 1.dp)) },
alwaysShowLabel = true,
colors = colors
?: NavigationRailItemDefaults.colors(
diff --git a/src/main/kotlin/moe/styx/views/anime/MovieDetailView.kt b/src/main/kotlin/moe/styx/views/anime/MovieDetailView.kt
index f0bf533..5832071 100644
--- a/src/main/kotlin/moe/styx/views/anime/MovieDetailView.kt
+++ b/src/main/kotlin/moe/styx/views/anime/MovieDetailView.kt
@@ -12,20 +12,28 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
+import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.ScreenKey
+import com.dokar.sonner.TextToastAction
+import com.dokar.sonner.Toast
+import com.dokar.sonner.ToastType
import com.russhwolf.settings.get
import moe.styx.common.compose.components.anime.*
import moe.styx.common.compose.components.buttons.FavouriteIconButton
import moe.styx.common.compose.components.buttons.IconButtonWithTooltip
import moe.styx.common.compose.components.layout.MainScaffold
import moe.styx.common.compose.components.misc.OnlineUsersIcon
-import moe.styx.common.compose.files.*
+import moe.styx.common.compose.files.Storage
+import moe.styx.common.compose.files.collectWithEmptyInitial
+import moe.styx.common.compose.files.updateList
import moe.styx.common.compose.http.login
import moe.styx.common.compose.settings
import moe.styx.common.compose.threads.DownloadQueue
import moe.styx.common.compose.threads.RequestQueue
import moe.styx.common.compose.utils.LocalGlobalNavigator
+import moe.styx.common.compose.utils.LocalToaster
+import moe.styx.common.compose.viewmodels.MainDataViewModel
import moe.styx.common.data.MediaEntry
import moe.styx.common.data.MediaWatched
import moe.styx.common.extension.currentUnixSeconds
@@ -34,7 +42,6 @@ import moe.styx.common.extension.toBoolean
import moe.styx.common.util.SYSTEMFILES
import moe.styx.common.util.launchThreaded
import moe.styx.components.anime.AppendDialog
-import moe.styx.components.anime.FailedDialog
import moe.styx.logic.runner.currentPlayer
import moe.styx.logic.runner.launchMPV
import moe.styx.logic.utils.pushMediaView
@@ -51,13 +58,17 @@ class MovieDetailView(private val mediaID: String) : Screen {
@Composable
override fun Content() {
val nav = LocalGlobalNavigator.current
- val mediaList by Storage.stores.mediaStore.getCurrentAndCollectFlow()
- val media = remember { mediaList.find { it.GUID eqI mediaID } }
- val movieEntry = fetchEntries(mediaID).minByOrNull { it.entryNumber.toDoubleOrNull() ?: 0.0 }
- if (media == null) {
+ val toaster = LocalToaster.current
+ val sm = nav.rememberNavigatorScreenModel("main-vm") { MainDataViewModel() }
+ val storage by sm.storageFlow.collectAsState()
+ val mediaStorage = remember(storage) { sm.getMediaStorageForID(mediaID, storage) }
+ val movieEntry = mediaStorage.entries.getOrNull(0)
+
+ if (mediaStorage.image == null) {
nav.pop()
return
}
+
val watchedList by Storage.stores.watchedStore.collectWithEmptyInitial()
val watched = movieEntry?.let { watchedList.find { it.entryID eqI movieEntry.GUID } }
var showMediaInfoDialog by remember { mutableStateOf(false) }
@@ -65,9 +76,9 @@ class MovieDetailView(private val mediaID: String) : Screen {
MediaInfoDialog(movieEntry) { showMediaInfoDialog = false }
}
- MainScaffold(title = media.name, actions = {
+ MainScaffold(title = mediaStorage.media.name, actions = {
OnlineUsersIcon { nav.replace(if (it.isSeries.toBoolean()) AnimeDetailView(it.GUID) else MovieDetailView(it.GUID)) }
- FavouriteIconButton(media)
+ FavouriteIconButton(mediaStorage.media, sm, storage)
}) {
val scrollState = rememberScrollState()
ElevatedCard(
@@ -76,31 +87,37 @@ class MovieDetailView(private val mediaID: String) : Screen {
) {
var failedToPlayMessage by remember { mutableStateOf("") }
if (failedToPlayMessage.isNotBlank()) {
- FailedDialog(failedToPlayMessage, Modifier.fillMaxWidth(0.6F)) {
- failedToPlayMessage = ""
- if (it) nav.push(SettingsView())
- }
+ toaster.show(Toast(failedToPlayMessage, type = ToastType.Error, action = TextToastAction("Open Settings") {
+ nav.push(SettingsView())
+ }))
+ failedToPlayMessage = ""
}
var showAppendDialog by remember { mutableStateOf(false) }
if (showAppendDialog && movieEntry != null) {
AppendDialog(movieEntry, Modifier.fillMaxWidth(0.6F), Modifier.align(Alignment.CenterHorizontally), {
showAppendDialog = false
}) {
- failedToPlayMessage = it
+ if (!it.isOK)
+ failedToPlayMessage = it.message
+ else
+ sm.updateData(true)
}
}
Column(Modifier.fillMaxSize().verticalScroll(scrollState)) {
- StupidImageNameArea(media) {
+ StupidImageNameArea(mediaStorage) {
Column(Modifier.padding(6.dp).widthIn(0.dp, 560.dp).fillMaxWidth()) {
Row(Modifier.padding(3.dp).fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
IconButton({
if (movieEntry == null)
return@IconButton
if (currentPlayer == null) {
- launchMPV(movieEntry, false, {
- failedToPlayMessage = it
- })
+ launchMPV(movieEntry, false) {
+ if (!it.isOK)
+ failedToPlayMessage = it.message
+ else
+ sm.updateData(true)
+ }
} else {
showAppendDialog = true
}
@@ -111,14 +128,22 @@ class MovieDetailView(private val mediaID: String) : Screen {
IconButton(onClick = {
movieEntry?.let {
- RequestQueue.updateWatched(
- MediaWatched(movieEntry.GUID, login?.userID ?: "", currentUnixSeconds(), 0, 0F, 100F)
- )
+ launchThreaded {
+ RequestQueue.updateWatched(
+ MediaWatched(movieEntry.GUID, login?.userID ?: "", currentUnixSeconds(), 0, 0F, 100F)
+ ).first.join()
+ sm.updateData(true)
+ }
}
}) { Icon(Icons.Default.Visibility, "Set Watched") }
IconButton(onClick = {
- movieEntry?.let { RequestQueue.removeWatched(movieEntry) }
+ movieEntry?.let {
+ launchThreaded {
+ RequestQueue.removeWatched(movieEntry).first.join()
+ sm.updateData(true)
+ }
+ }
}) { Icon(Icons.Default.VisibilityOff, "Set Unwatched") }
Spacer(Modifier.weight(1f))
@@ -134,17 +159,23 @@ class MovieDetailView(private val mediaID: String) : Screen {
Spacer(Modifier.height(6.dp))
Text("About", Modifier.padding(6.dp, 2.dp), style = MaterialTheme.typography.titleLarge)
- MediaGenreListing(media)
+ MediaGenreListing(mediaStorage.media)
val preferGerman = settings["prefer-german-metadata", false]
- val synopsis = if (!media.synopsisDE.isNullOrBlank() && preferGerman) media.synopsisDE else media.synopsisEN
+ val synopsis =
+ if (!mediaStorage.media.synopsisDE.isNullOrBlank() && preferGerman) mediaStorage.media.synopsisDE else mediaStorage.media.synopsisEN
if (!synopsis.isNullOrBlank())
SelectionContainer {
Text(synopsis.removeSomeHTMLTags(), Modifier.padding(6.dp), style = MaterialTheme.typography.bodyMedium)
}
- if (media.sequel != null || media.prequel != null) {
+ if (mediaStorage.sequel != null || mediaStorage.prequel != null) {
HorizontalDivider(Modifier.fillMaxWidth().padding(8.dp, 6.dp), thickness = 2.dp)
- MediaRelations(media, mediaList) { nav.pushMediaView(it, true) }
+ MediaRelations(mediaStorage) {
+ nav.pushMediaView(
+ it,
+ true
+ )
+ }
}
}
}
diff --git a/src/main/kotlin/moe/styx/views/anime/tabs/AnimeListView.kt b/src/main/kotlin/moe/styx/views/anime/tabs/AnimeListView.kt
deleted file mode 100644
index 327f19e..0000000
--- a/src/main/kotlin/moe/styx/views/anime/tabs/AnimeListView.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package moe.styx.views.anime.tabs
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Tv
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import com.russhwolf.settings.get
-import kotlinx.coroutines.runBlocking
-import moe.styx.common.compose.components.search.MediaSearch
-import moe.styx.common.compose.extensions.SimpleTab
-import moe.styx.common.compose.extensions.getDistinctCategories
-import moe.styx.common.compose.extensions.getDistinctGenres
-import moe.styx.common.compose.files.Storage
-import moe.styx.common.compose.files.getCurrentAndCollectFlow
-import moe.styx.common.compose.utils.SearchState
-import moe.styx.common.extension.toBoolean
-import moe.styx.views.barWithListComp
-
-class AnimeListView : SimpleTab("Shows", Icons.Default.Tv) {
-
- @Composable
- override fun Content() {
- val media by Storage.stores.mediaStore.getCurrentAndCollectFlow()
- val categories by Storage.stores.categoryStore.getCurrentAndCollectFlow()
- val searchStore = Storage.stores.showSearchState
- val filtered = media.filter { it.isSeries.toBoolean() }
- val availableGenres = filtered.getDistinctGenres()
- val availableCategories = filtered.getDistinctCategories(categories)
- val initialState = runBlocking { searchStore.get() ?: SearchState() }
- val mediaSearch = MediaSearch(searchStore, initialState, availableGenres, availableCategories)
- barWithListComp(mediaSearch, initialState, filtered, moe.styx.common.compose.settings["shows-list", false])
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/anime/tabs/FavouritesListView.kt b/src/main/kotlin/moe/styx/views/anime/tabs/FavouritesListView.kt
deleted file mode 100644
index 3995cf4..0000000
--- a/src/main/kotlin/moe/styx/views/anime/tabs/FavouritesListView.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package moe.styx.views.anime.tabs
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Star
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import kotlinx.coroutines.runBlocking
-import moe.styx.common.compose.components.search.MediaSearch
-import moe.styx.common.compose.extensions.SimpleTab
-import moe.styx.common.compose.files.Storage
-import moe.styx.common.compose.files.getCurrentAndCollectFlow
-import moe.styx.common.compose.utils.SearchState
-import moe.styx.common.extension.eqI
-import moe.styx.views.barWithListComp
-
-class FavouritesListView : SimpleTab("Favourites", Icons.Default.Star) {
-
- @Composable
- override fun Content() {
- val media by Storage.stores.mediaStore.getCurrentAndCollectFlow()
- Column {
- val favourites by Storage.stores.favouriteStore.getCurrentAndCollectFlow()
- val searchStore = Storage.stores.favSearchState
- val filtered = media.filter { m -> favourites.find { m.GUID eqI it.mediaID } != null }
- val initialState = runBlocking { searchStore.get() ?: SearchState() }
- val mediaSearch = MediaSearch(searchStore, initialState, emptyList(), emptyList(), true)
- barWithListComp(mediaSearch, initialState, filtered, false, true, favourites)
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/anime/tabs/MediaListView.kt b/src/main/kotlin/moe/styx/views/anime/tabs/MediaListView.kt
new file mode 100644
index 0000000..7837e83
--- /dev/null
+++ b/src/main/kotlin/moe/styx/views/anime/tabs/MediaListView.kt
@@ -0,0 +1,84 @@
+package moe.styx.views.anime.tabs
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Movie
+import androidx.compose.material.icons.filled.Star
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.runtime.*
+import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel
+import cafe.adriel.voyager.core.screen.ScreenKey
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import com.russhwolf.settings.get
+import kotlinx.coroutines.runBlocking
+import moe.styx.common.compose.components.search.MediaSearch
+import moe.styx.common.compose.extensions.createTabOptions
+import moe.styx.common.compose.extensions.getDistinctCategories
+import moe.styx.common.compose.extensions.getDistinctGenres
+import moe.styx.common.compose.files.Stores
+import moe.styx.common.compose.settings
+import moe.styx.common.compose.utils.LocalGlobalNavigator
+import moe.styx.common.compose.utils.SearchState
+import moe.styx.common.compose.viewmodels.ListPosViewModel
+import moe.styx.common.compose.viewmodels.MainDataViewModel
+import moe.styx.common.extension.eqI
+import moe.styx.common.extension.toBoolean
+import moe.styx.views.barWithListComp
+
+class MediaListView(private val movies: Boolean = false, private val favourites: Boolean = false) : Tab {
+ override val options: TabOptions
+ @Composable
+ get() {
+ return if (favourites)
+ createTabOptions("Favourites", Icons.Default.Star)
+ else if (movies)
+ createTabOptions("Movies", Icons.Default.Movie)
+ else
+ createTabOptions("Shows", Icons.Default.Tv)
+ }
+
+ override val key: ScreenKey
+ get() {
+ return if (favourites)
+ "favourites-view"
+ else if (movies)
+ "movies-view"
+ else
+ "shows-view"
+ }
+
+ @Composable
+ override fun Content() {
+ val nav = LocalGlobalNavigator.current
+ val sm = nav.rememberNavigatorScreenModel("main-vm") { MainDataViewModel() }
+ val storage by sm.storageFlow.collectAsState()
+ val searchStore = if (favourites) Stores.favSearchState else if (movies) Stores.movieSearchState else Stores.showSearchState
+
+ val key = if (favourites) "favourites" else if (movies) "movies" else "shows"
+ val useList = if (favourites) false else settings["$key-list", false]
+ val listPosModel = nav.rememberNavigatorScreenModel("$key-pos-$useList") { ListPosViewModel() }
+
+ val filtered = remember(storage) {
+ storage.mediaList.let { mediaList ->
+ if (favourites)
+ mediaList.filter { m -> storage.favouritesList.find { it.mediaID eqI m.GUID } != null }
+ else
+ if (movies) mediaList.filter { !it.isSeries.toBoolean() } else mediaList.filter { it.isSeries.toBoolean() }
+ }
+ }
+ val availableGenres = remember(storage) { filtered.getDistinctGenres() }
+ val availableCategories = remember(storage) { filtered.getDistinctCategories(storage.categoryList) }
+ val initialState = runBlocking { searchStore.get() ?: SearchState() }
+ val mediaSearch = MediaSearch(searchStore, initialState, availableGenres, availableCategories, favourites)
+ barWithListComp(
+ mediaSearch,
+ initialState,
+ storage,
+ filtered,
+ useList,
+ listPosModel,
+ favourites,
+ if (favourites) storage.favouritesList else emptyList()
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/anime/tabs/MovieListView.kt b/src/main/kotlin/moe/styx/views/anime/tabs/MovieListView.kt
deleted file mode 100644
index adba4c6..0000000
--- a/src/main/kotlin/moe/styx/views/anime/tabs/MovieListView.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package moe.styx.views.anime.tabs
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Movie
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import com.russhwolf.settings.get
-import kotlinx.coroutines.runBlocking
-import moe.styx.common.compose.components.search.MediaSearch
-import moe.styx.common.compose.extensions.SimpleTab
-import moe.styx.common.compose.extensions.getDistinctCategories
-import moe.styx.common.compose.extensions.getDistinctGenres
-import moe.styx.common.compose.files.Storage
-import moe.styx.common.compose.files.getCurrentAndCollectFlow
-import moe.styx.common.compose.utils.SearchState
-import moe.styx.common.extension.toBoolean
-import moe.styx.views.barWithListComp
-
-class MovieListView : SimpleTab("Movies", Icons.Default.Movie) {
-
- @Composable
- override fun Content() {
- val media by Storage.stores.mediaStore.getCurrentAndCollectFlow()
- val categories by Storage.stores.categoryStore.getCurrentAndCollectFlow()
- val searchStore = Storage.stores.movieSearchState
- val filtered = media.filter { !it.isSeries.toBoolean() }
- val availableGenres = filtered.getDistinctGenres()
- val availableCategories = filtered.getDistinctCategories(categories)
- val initialState = runBlocking { searchStore.get() ?: SearchState() }
- val mediaSearch = MediaSearch(searchStore, initialState, availableGenres, availableCategories)
- barWithListComp(mediaSearch, initialState, filtered, moe.styx.common.compose.settings["movies-list", false])
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/anime/tabs/ScheduleView.kt b/src/main/kotlin/moe/styx/views/anime/tabs/ScheduleView.kt
index 6535019..e7b22be 100644
--- a/src/main/kotlin/moe/styx/views/anime/tabs/ScheduleView.kt
+++ b/src/main/kotlin/moe/styx/views/anime/tabs/ScheduleView.kt
@@ -1,14 +1,11 @@
package moe.styx.views.anime.tabs
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CalendarViewWeek
import androidx.compose.runtime.Composable
-import moe.styx.common.compose.components.schedule.ScheduleDay
+import moe.styx.common.compose.components.schedule.ScheduleViewComponent
import moe.styx.common.compose.extensions.SimpleTab
import moe.styx.common.compose.utils.LocalGlobalNavigator
-import moe.styx.common.data.ScheduleWeekday
import moe.styx.logic.utils.pushMediaView
class ScheduleView : SimpleTab("Schedule", Icons.Default.CalendarViewWeek) {
@@ -16,11 +13,8 @@ class ScheduleView : SimpleTab("Schedule", Icons.Default.CalendarViewWeek) {
@Composable
override fun Content() {
val nav = LocalGlobalNavigator.current
- val days = ScheduleWeekday.entries.toTypedArray()
- LazyColumn {
- items(items = days, itemContent = { day ->
- ScheduleDay(day) { nav.pushMediaView(it) }
- })
+ ScheduleViewComponent {
+ nav.pushMediaView(it, false)
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/login/LoginView.kt b/src/main/kotlin/moe/styx/views/login/LoginView.kt
index e0866da..90e5f97 100644
--- a/src/main/kotlin/moe/styx/views/login/LoginView.kt
+++ b/src/main/kotlin/moe/styx/views/login/LoginView.kt
@@ -12,13 +12,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import kotlinx.coroutines.delay
-import moe.styx.Styx__.BuildConfig
+import moe.styx.Styx_2.BuildConfig
import moe.styx.common.compose.components.buttons.IconButtonWithTooltip
import moe.styx.common.compose.http.checkLogin
import moe.styx.common.compose.http.generateCode
import moe.styx.common.compose.http.isLoggedIn
import moe.styx.common.compose.utils.LocalGlobalNavigator
-import moe.styx.views.other.LoadingView
+import moe.styx.views.anime.AnimeOverview
import java.awt.Desktop
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
@@ -39,7 +39,7 @@ class LoginView() : Screen {
return@let checkLogin(creationResponse!!.GUID, true)
}
if (log != null) {
- nav.push(LoadingView())
+ nav.replace(AnimeOverview())
break
}
diff --git a/src/main/kotlin/moe/styx/views/login/OfflineView.kt b/src/main/kotlin/moe/styx/views/login/OfflineView.kt
index d870924..bca3c0c 100644
--- a/src/main/kotlin/moe/styx/views/login/OfflineView.kt
+++ b/src/main/kotlin/moe/styx/views/login/OfflineView.kt
@@ -12,7 +12,7 @@ import cafe.adriel.voyager.core.screen.Screen
import moe.styx.common.compose.components.layout.MainScaffold
import moe.styx.common.compose.utils.LocalGlobalNavigator
import moe.styx.common.compose.utils.ServerStatus
-import moe.styx.views.other.LoadingView
+import moe.styx.views.anime.AnimeOverview
class OfflineView : Screen {
@@ -26,7 +26,7 @@ class OfflineView : Screen {
Text(ServerStatus.getLastKnownText(), style = MaterialTheme.typography.headlineSmall)
Text("Feel free to keep using Styx with the data you have from your last use.", Modifier.padding(0.dp, 15.dp).weight(1f))
- Button({ nav.replaceAll(LoadingView()) }) {
+ Button({ nav.replaceAll(AnimeOverview()) }) {
Text("OK")
}
}
diff --git a/src/main/kotlin/moe/styx/views/other/FontSizeView.kt b/src/main/kotlin/moe/styx/views/other/FontSizeView.kt
index 0ae7925..33bdd69 100644
--- a/src/main/kotlin/moe/styx/views/other/FontSizeView.kt
+++ b/src/main/kotlin/moe/styx/views/other/FontSizeView.kt
@@ -1,10 +1,12 @@
package moe.styx.views.other
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
+import moe.styx.Main
import moe.styx.common.compose.components.layout.MainScaffold
class FontSizeView : Screen {
@@ -13,6 +15,18 @@ class FontSizeView : Screen {
override fun Content() {
MainScaffold(title = "Font Sizes") {
Column {
+ Row {
+ Button({
+ Main.densityScale.value -= 0.25f
+ }) {
+ Text("Down the scale")
+ }
+ Button({
+ Main.densityScale.value += 0.25f
+ }) {
+ Text("Up the scale")
+ }
+ }
Text("Display Large", style = MaterialTheme.typography.displayLarge)
Text("Display Medium", style = MaterialTheme.typography.displayMedium)
Text("Display Small", style = MaterialTheme.typography.displaySmall)
@@ -28,6 +42,42 @@ class FontSizeView : Screen {
Text("Label Large", style = MaterialTheme.typography.labelLarge)
Text("Label Medium", style = MaterialTheme.typography.labelMedium)
Text("Label Small", style = MaterialTheme.typography.labelSmall)
+ HorizontalDivider()
+ LinearProgressIndicator(
+ { .5f },
+ Modifier.padding(5.dp).fillMaxWidth().height(5.dp),
+ trackColor = MaterialTheme.colorScheme.onSurface
+ )
+ LinearProgressIndicator(
+ { .5f },
+ Modifier.padding(5.dp).fillMaxWidth().height(5.dp),
+ trackColor = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ LinearProgressIndicator(
+ { .5f },
+ Modifier.padding(5.dp).fillMaxWidth().height(5.dp),
+ trackColor = MaterialTheme.colorScheme.inverseOnSurface
+ )
+ LinearProgressIndicator(
+ { .5f },
+ Modifier.padding(5.dp).fillMaxWidth().height(5.dp),
+ trackColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
+ )
+ LinearProgressIndicator(
+ { .5f },
+ Modifier.padding(5.dp).fillMaxWidth().height(5.dp),
+ trackColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
+ )
+ LinearProgressIndicator(
+ { .5f },
+ Modifier.padding(5.dp).fillMaxWidth().height(5.dp),
+ trackColor = MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp)
+ )
+ LinearProgressIndicator(
+ { .5f },
+ Modifier.padding(5.dp).fillMaxWidth().height(5.dp),
+ trackColor = MaterialTheme.colorScheme.surfaceColorAtElevation(15.dp)
+ )
}
}
}
diff --git a/src/main/kotlin/moe/styx/views/other/LoadingView.kt b/src/main/kotlin/moe/styx/views/other/LoadingView.kt
deleted file mode 100644
index 234a6c2..0000000
--- a/src/main/kotlin/moe/styx/views/other/LoadingView.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package moe.styx.views.other
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import cafe.adriel.voyager.core.screen.Screen
-import kotlinx.coroutines.delay
-import moe.styx.Styx__.BuildConfig
-import moe.styx.common.compose.components.layout.MainScaffold
-import moe.styx.common.compose.files.Storage
-import moe.styx.common.compose.utils.LocalGlobalNavigator
-import moe.styx.common.compose.utils.ServerStatus
-import moe.styx.common.isWindows
-import moe.styx.logic.utils.MpvUtils
-import moe.styx.logic.utils.downloadNewInstaller
-import moe.styx.logic.utils.isUpToDate
-import moe.styx.views.anime.AnimeOverview
-import java.awt.Desktop
-import java.net.URI
-
-class LoadingView : Screen {
-
- @Composable
- override fun Content() {
- val nav = LocalGlobalNavigator.current
- val progress = Storage.loadingProgress.collectAsState()
-
- if (ServerStatus.lastKnown != ServerStatus.UNKNOWN && !isUpToDate()) {
- if (isWindows)
- downloadNewInstaller()
- OutdatedVersion()
- return
- }
-
- LaunchedEffect(Unit) {
- delay(1000)
- Storage.loadData()
- MpvUtils.checkVersionAndDownload()
- nav.replaceAll(AnimeOverview())
- }
-
- MainScaffold(title = "Loading", addPopButton = false) {
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Column(modifier = Modifier.padding(10.dp).align(Alignment.TopCenter)) {
- Text(
- text = progress.value,
- modifier = Modifier.align(Alignment.CenterHorizontally).padding(0.dp, 25.dp)
- )
- CircularProgressIndicator(
- modifier = Modifier.align(Alignment.CenterHorizontally).padding(0.dp, 15.dp).fillMaxSize(.4F)
- )
- }
- }
- }
- }
-}
-
-@Composable
-fun OutdatedVersion() {
- MainScaffold(title = "Outdated", addPopButton = false) {
- Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
- var modifier = Modifier.padding(10.dp)
- if (!isWindows)
- modifier = modifier.weight(1f)
- Text(
- "This version of Styx is outdated.",
- modifier,
- style = MaterialTheme.typography.headlineMedium
- )
- if (isWindows)
- Text(
- "Attempting to download the new version automatically. Please wait a bit. If nothing happens, feel free to do it manually below.",
- Modifier.weight(1f).padding(16.dp)
- )
- Button({
- if (Desktop.isDesktopSupported())
- Desktop.getDesktop().browse(URI(BuildConfig.SITE_URL))
- }) {
- Text("Open ${BuildConfig.SITE}")
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/other/OutdatedView.kt b/src/main/kotlin/moe/styx/views/other/OutdatedView.kt
new file mode 100644
index 0000000..252eaa5
--- /dev/null
+++ b/src/main/kotlin/moe/styx/views/other/OutdatedView.kt
@@ -0,0 +1,115 @@
+package moe.styx.views.other
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.core.screen.Screen
+import com.dokar.sonner.*
+import kotlinx.coroutines.delay
+import moe.styx.Styx_2.BuildConfig
+import moe.styx.common.compose.components.layout.MainScaffold
+import moe.styx.common.compose.http.Endpoints
+import moe.styx.common.compose.http.login
+import moe.styx.common.compose.utils.LocalToaster
+import moe.styx.common.http.DownloadResult
+import moe.styx.common.http.downloadFileStream
+import moe.styx.common.isWindows
+import moe.styx.common.util.launchThreaded
+import moe.styx.logic.Files
+import moe.styx.logic.runner.openURI
+import okio.Path.Companion.toPath
+import java.awt.Desktop
+import java.io.File
+import kotlin.system.exitProcess
+
+class OutdatedView(private val requestedVersion: String? = null) : Screen {
+
+ @Composable
+ override fun Content() {
+ val shouldBeDownloading = remember { mutableStateOf(false) }
+
+ MainScaffold(title = "Outdated", addPopButton = requestedVersion != null) {
+ Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
+ Text(
+ if (requestedVersion == null) "This version of Styx is outdated." else "Download $requestedVersion",
+ Modifier.padding(10.dp).weight(1f),
+ style = MaterialTheme.typography.headlineMedium
+ )
+ DownloadButtons(shouldBeDownloading)
+ Button({
+ openURI("${BuildConfig.SITE_URL}/user")
+ }, colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary), modifier = Modifier.padding(10.dp)) {
+ Text("Open ${BuildConfig.SITE}")
+ }
+ }
+ }
+ }
+
+
+ @Suppress("NAME_SHADOWING")
+ @Composable
+ fun ColumnScope.DownloadButtons(shouldBeDownloading: MutableState) {
+ val toaster = LocalToaster.current
+ var shouldBeDownloading by shouldBeDownloading
+ Row(Modifier.weight(1f)) {
+ if (isWindows) {
+ Button({
+ shouldBeDownloading = true
+ runDownload("win", toaster) { shouldBeDownloading = false }
+ }, enabled = !shouldBeDownloading) {
+ Text("Download and open installer")
+ }
+ } else {
+ Button({
+ shouldBeDownloading = true
+ runDownload("rpm", toaster) { shouldBeDownloading = false }
+ }, enabled = !shouldBeDownloading, modifier = Modifier.padding(12.dp)) {
+ Text("RPM")
+ }
+ Button({
+ shouldBeDownloading = true
+ runDownload("deb", toaster) { shouldBeDownloading = false }
+ }, enabled = !shouldBeDownloading, modifier = Modifier.padding(12.dp)) {
+ Text("DEB")
+ }
+ }
+ }
+ }
+
+ private fun runDownload(platform: String, toaster: ToasterState, onDone: () -> Unit) = launchThreaded {
+ val outFile = File(Files.getDataDir().parentFile, "Installer." + if (isWindows) "msi" else platform)
+ val result = downloadFileStream(
+ Endpoints.DOWNLOAD_BUILD_BASE.url() + "/$platform" + (if (requestedVersion != null) "/$requestedVersion" else "") + "?token=${login?.accessToken}",
+ outFile.absolutePath.toPath()
+ )
+ if (result !in arrayOf(DownloadResult.OK, DownloadResult.AbortExists)) {
+ toaster.show(
+ Toast(
+ "Failed to download installer! Please check the logs.",
+ type = ToastType.Error,
+ duration = ToasterDefaults.DurationLong
+ )
+ )
+ onDone()
+ return@launchThreaded
+ }
+ onDone()
+ if (isWindows) {
+ if (Desktop.isDesktopSupported() && outFile.exists() && outFile.length() > 100) {
+ delay(1500L)
+ Desktop.getDesktop().open(outFile.parentFile)
+ delay(500L)
+ Desktop.getDesktop().open(outFile)
+ delay(1500L)
+ exitProcess(0)
+ }
+ } else {
+ openURI(outFile.parentFile.absolutePath)
+ delay(1500L)
+ exitProcess(0)
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/styx/views/settings/MpvConfigView.kt b/src/main/kotlin/moe/styx/views/settings/MpvConfigView.kt
index 47f5d1f..5426646 100644
--- a/src/main/kotlin/moe/styx/views/settings/MpvConfigView.kt
+++ b/src/main/kotlin/moe/styx/views/settings/MpvConfigView.kt
@@ -2,6 +2,7 @@ package moe.styx.views.settings
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
@@ -12,21 +13,49 @@ import com.russhwolf.settings.get
import com.russhwolf.settings.set
import kotlinx.serialization.encodeToString
import moe.styx.common.compose.components.layout.MainScaffold
+import moe.styx.common.compose.components.misc.ExpandableSettings
import moe.styx.common.compose.components.misc.Toggles
import moe.styx.common.compose.components.misc.Toggles.settingsContainer
import moe.styx.common.compose.settings
import moe.styx.common.compose.utils.*
import moe.styx.common.isWindows
import moe.styx.common.json
+import moe.styx.logic.Files
import moe.styx.logic.utils.generateNewConfig
+import java.io.File
class MpvConfigView : Screen {
@Composable
override fun Content() {
var preferences by remember { mutableStateOf(MpvPreferences.getOrDefault()) }
+ var tipsExpanded by remember { mutableStateOf(false) }
MainScaffold(title = "Mpv Configuration") {
Column {
Column(Modifier.padding(8.dp).fillMaxWidth().weight(1f).verticalScroll(rememberScrollState())) {
+ ExpandableSettings("MPV Tips and tricks", tipsExpanded, { tipsExpanded = !tipsExpanded }) {
+ SelectionContainer {
+ Text(
+ """
+ Here are some possibly useful keybinds:
+
+ CTRL+R Attempts to reload the video, may be useful if the API is having issues and stuff starts buffering.
+
+ SHIFT+C Tries to Auto-Crop the video. Useful if the actual content is 21:9 but has black bars in the file itself.
+
+ SHIFT+W Opens the Recording/Clip-Maker Menu. These clips are just dumped in your user folder.
+
+ H Toggle debanding on the fly.
+
+ You can also step frame by frame with SHIFT + Arrow Keys.
+
+ You can also create a custom config that will be persisted through mpv updates by creating a file at:
+ ${File(Files.getAppDir(), "custom-mpv.conf").absolutePath}
+ """.trimIndent(),
+ modifier = Modifier.padding(8.dp, 4.dp),
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
Column(Modifier.settingsContainer()) {
Text("General", Modifier.padding(10.dp, 7.dp), style = MaterialTheme.typography.titleLarge)
Toggles.ContainerSwitch(
diff --git a/src/main/kotlin/moe/styx/views/settings/SettingsTab.kt b/src/main/kotlin/moe/styx/views/settings/SettingsTab.kt
new file mode 100644
index 0000000..e7fad2e
--- /dev/null
+++ b/src/main/kotlin/moe/styx/views/settings/SettingsTab.kt
@@ -0,0 +1,148 @@
+package moe.styx.views.settings
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.core.model.ScreenModel
+import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel
+import cafe.adriel.voyager.core.screen.Screen
+import cafe.adriel.voyager.navigator.Navigator
+import com.dokar.sonner.Toast
+import com.dokar.sonner.ToastType
+import com.dokar.sonner.ToasterDefaults
+import com.russhwolf.settings.get
+import com.russhwolf.settings.set
+import moe.styx.common.compose.components.layout.MainScaffold
+import moe.styx.common.compose.components.misc.ExpandableSettings
+import moe.styx.common.compose.components.misc.ServerSelection
+import moe.styx.common.compose.components.misc.Toggles
+import moe.styx.common.compose.components.misc.Toggles.settingsContainer
+import moe.styx.common.compose.extensions.SimpleTab
+import moe.styx.common.compose.http.login
+import moe.styx.common.compose.settings
+import moe.styx.common.compose.utils.LocalGlobalNavigator
+import moe.styx.common.compose.utils.LocalToaster
+import moe.styx.common.isWindows
+import moe.styx.common.util.Log
+import moe.styx.components.misc.MpvVersionAndDownload
+import moe.styx.logic.Files
+import moe.styx.logic.runner.openURI
+import moe.styx.views.settings.sub.AppearanceSettings
+import moe.styx.views.settings.sub.DiscordSettings
+import moe.styx.views.settings.sub.MetadataSettings
+import java.awt.Desktop
+import java.io.File
+
+class SettingsTab : SimpleTab("Settings", Icons.Default.Settings) {
+
+ @Composable
+ override fun Content() {
+ SettingsViewComponent()
+ }
+}
+
+class SettingsView : Screen {
+
+ @Composable
+ override fun Content() {
+ MainScaffold(Modifier.fillMaxSize(), "Settings") {
+ SettingsViewComponent()
+ }
+ }
+}
+
+class SettingsViewModel : ScreenModel {
+ var appearanceExpanded by mutableStateOf(true)
+ var metadataExpanded by mutableStateOf(true)
+ var discordExpanded by mutableStateOf(false)
+ var systemExpanded by mutableStateOf(false)
+}
+
+@Composable
+fun SettingsViewComponent() {
+ val nav = LocalGlobalNavigator.current
+ val vm = nav.rememberNavigatorScreenModel("settings-vm") { SettingsViewModel() }
+ val scrollState = rememberScrollState(0)
+ val toaster = LocalToaster.current
+ Column(Modifier.fillMaxSize().padding(5.dp)) {
+ Column(Modifier.padding(5.dp).weight(1F).verticalScroll(scrollState, true)) {
+ ExpandableSettings(
+ "Appearance Options",
+ vm.appearanceExpanded,
+ { vm.appearanceExpanded = !vm.appearanceExpanded },
+ withContainer = false
+ ) {
+ AppearanceSettings()
+ }
+ ExpandableSettings("Metadata Options", vm.metadataExpanded, { vm.metadataExpanded = !vm.metadataExpanded }, withContainer = false) {
+ MetadataSettings()
+ }
+ ExpandableSettings("Discord", vm.discordExpanded, { vm.discordExpanded = !vm.discordExpanded }, withContainer = false) {
+ DiscordSettings()
+ }
+ ExpandableSettings("System", vm.systemExpanded, { vm.systemExpanded = !vm.systemExpanded }, withContainer = false) {
+ ServerSelection()
+ Toggles.ContainerSwitch(
+ "Enable Debug Logs",
+ description = "Please enable this and try to reproduce your issue if you want to report a bug to me!",
+ value = settings["enable-debug-logs", false]
+ ) {
+ settings["enable-debug-logs"] = it
+ Log.debugEnabled = it
+ }
+ Button({
+ val logFolder = File(Files.getAppDir(), "Logs")
+ if (!isWindows) {
+ openURI(logFolder.absolutePath)
+ return@Button
+ }
+ runCatching {
+ if (Desktop.isDesktopSupported()) {
+ Desktop.getDesktop().open(logFolder)
+ }
+ return@Button
+ }.onFailure {
+ Log.e(exception = it)
+ }.getOrNull()
+ toaster.show(
+ Toast(
+ "Could not open log folder!\nPlease check it yourself at: ${logFolder.absolutePath}",
+ type = ToastType.Error,
+ duration = ToasterDefaults.DurationLong
+ )
+ )
+ }, Modifier.padding(8.dp, 5.dp)) {
+ Text("Open Log Folder")
+ }
+ Spacer(Modifier.height(5.dp))
+ }
+ Column(Modifier.settingsContainer()) {
+ Text("MPV Options", style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(10.dp, 7.dp))
+ Button({ nav.push(MpvConfigView()) }, Modifier.padding(8.dp, 4.dp)) {
+ Text("Open Mpv Configuration")
+ }
+ MpvVersionAndDownload()
+ }
+ }
+ HorizontalDivider(Modifier.padding(5.dp), thickness = 2.dp)
+ LoggedInComponent(nav)
+ }
+}
+
+
+@Composable
+fun LoggedInComponent(nav: Navigator) {
+ val primaryColor = MaterialTheme.colorScheme.primary
+ if (login != null) {
+ Text(
+ "Logged in as: ${login!!.name}",
+ Modifier.padding(10.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/settings/SettingsView.kt b/src/main/kotlin/moe/styx/views/settings/SettingsView.kt
deleted file mode 100644
index e8e7a5b..0000000
--- a/src/main/kotlin/moe/styx/views/settings/SettingsView.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-package moe.styx.views.settings
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Settings
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import cafe.adriel.voyager.navigator.Navigator
-import com.russhwolf.settings.get
-import com.russhwolf.settings.set
-import moe.styx.Main.isUiModeDark
-import moe.styx.common.compose.components.misc.Toggles
-import moe.styx.common.compose.components.misc.Toggles.settingsContainer
-import moe.styx.common.compose.extensions.SimpleTab
-import moe.styx.common.compose.http.isLoggedIn
-import moe.styx.common.compose.http.login
-import moe.styx.common.compose.settings
-import moe.styx.common.compose.utils.LocalGlobalNavigator
-import moe.styx.common.compose.utils.ServerStatus
-import moe.styx.components.misc.MpvVersionAndDownload
-import moe.styx.logic.DiscordRPC
-import moe.styx.views.login.LoginView
-import moe.styx.views.login.OfflineView
-import moe.styx.views.other.LoadingView
-
-class SettingsView : SimpleTab("Settings", Icons.Default.Settings) {
-
- @Composable
- override fun Content() {
- val nav = LocalGlobalNavigator.current
- var darkMode by remember { isUiModeDark }
- val scrollState = rememberScrollState(0)
- Column(Modifier.fillMaxSize().padding(5.dp)) {
- Column(Modifier.padding(5.dp).weight(1F).verticalScroll(scrollState, true)) {
- Column(Modifier.settingsContainer()) {
- Text("Layout Options", modifier = Modifier.padding(10.dp, 7.dp), style = MaterialTheme.typography.titleLarge)
- Toggles.ContainerSwitch("Darkmode", value = darkMode) { darkMode = it }
- Toggles.ContainerSwitch("Show names by default", value = settings["display-names", false]) { settings["display-names"] = it }
- Toggles.ContainerSwitch(
- "Show episode summaries",
- value = settings["display-ep-synopsis", false]
- ) { settings["display-ep-synopsis"] = it }
- Toggles.ContainerSwitch(
- "Prefer german titles and summaries",
- value = settings["prefer-german-metadata", false]
- ) { settings["prefer-german-metadata"] = it }
- Row(Modifier.fillMaxWidth()) {
- Toggles.ContainerSwitch(
- "Use list for shows",
- modifier = Modifier.weight(1f),
- value = settings["shows-list", false],
- paddingValues = Toggles.rowStartPadding
- ) { settings["shows-list"] = it }
- Toggles.ContainerSwitch(
- "Use list for movies",
- modifier = Modifier.weight(1f),
- value = settings["movies-list", false],
- paddingValues = Toggles.rowEndPadding
- ) { settings["movies-list"] = it }
- }
- Toggles.ContainerSwitch(
- "Sort episodes ascendingly",
- value = settings["episode-asc", false],
- paddingValues = Toggles.colEndPadding
- ) { settings["episode-asc"] = it }
- }
- Column(Modifier.settingsContainer()) {
- Text("Discord", modifier = Modifier.padding(10.dp, 7.dp), style = MaterialTheme.typography.titleLarge)
-
- Row(Modifier.fillMaxWidth().height(IntrinsicSize.Max)) {
- Toggles.ContainerSwitch(
- "Enable RPC",
- modifier = Modifier.weight(1f).fillMaxHeight(),
- value = settings["discord-rpc", true],
- paddingValues = Toggles.rowStartPadding
- ) {
- settings["discord-rpc"] = it
- if (it && !DiscordRPC.isStarted())
- DiscordRPC.start()
- else if (!it && DiscordRPC.isStarted()) {
- DiscordRPC.clearActivity()
- }
- }
-
- Toggles.ContainerSwitch(
- "Show RPC when idle",
- "Disabling this means the discord status will only show while you're watching something.",
- value = settings["discord-rpc-idle", true], modifier = Modifier.weight(1f), paddingValues = Toggles.rowEndPadding
- ) { settings["discord-rpc-idle"] = it }
- }
- Spacer(Modifier.height(5.dp))
- }
- Column(Modifier.settingsContainer()) {
- Text("MPV Options", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(10.dp, 7.dp))
- Button({ nav.push(MpvConfigView()) }, Modifier.padding(8.dp, 4.dp)) {
- Text("Open Mpv Configuration")
- }
- MpvVersionAndDownload()
- }
- }
- HorizontalDivider(Modifier.padding(5.dp), thickness = 2.dp)
- LoggedInComponent(nav)
- }
- }
-}
-
-
-@Composable
-fun LoggedInComponent(nav: Navigator) {
- val primaryColor = MaterialTheme.colorScheme.primary
- if (login != null) {
- Text(
- "Logged in as: ${login!!.name}",
- Modifier.padding(10.dp)
- )
- } else {
- Text("You're not logged in right now.", Modifier.padding(10.dp).drawBehind {
- val strokeWidthPx = 1.dp.toPx()
- val verticalOffset = size.height - 1.sp.toPx()
- drawLine(
- color = primaryColor,
- strokeWidth = strokeWidthPx,
- start = Offset(0f, verticalOffset),
- end = Offset(size.width, verticalOffset)
- )
- }.clickable {
- val view = if (isLoggedIn())
- LoadingView()
- else {
- if (ServerStatus.lastKnown !in listOf(ServerStatus.ONLINE, ServerStatus.UNAUTHORIZED))
- OfflineView()
- else
- LoginView()
- }
- nav.replaceAll(view)
- })
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/settings/sub/AppearanceSettings.kt b/src/main/kotlin/moe/styx/views/settings/sub/AppearanceSettings.kt
new file mode 100644
index 0000000..3498165
--- /dev/null
+++ b/src/main/kotlin/moe/styx/views/settings/sub/AppearanceSettings.kt
@@ -0,0 +1,112 @@
+package moe.styx.views.settings.sub
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Add
+import androidx.compose.material.icons.outlined.Remove
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.dp
+import com.russhwolf.settings.get
+import com.russhwolf.settings.set
+import moe.styx.Main.densityScale
+import moe.styx.Main.isUiModeDark
+import moe.styx.Main.useMonoFont
+import moe.styx.common.compose.components.AppShapes
+import moe.styx.common.compose.components.buttons.IconButtonWithTooltip
+import moe.styx.common.compose.components.misc.Toggles
+import moe.styx.common.compose.settings
+
+@Composable
+fun AppearanceSettings() {
+ var darkMode by remember { isUiModeDark }
+ var monoFont by remember { useMonoFont }
+
+ Text(
+ "Note that these may not fully apply until you go in and out of an anime screen.\nThis is a bug in a 3rd party library and will be fixed in a later update.",
+ style = MaterialTheme.typography.labelMedium,
+ modifier = Modifier.padding(10.dp, 5.dp)
+ )
+
+ Row(Modifier.fillMaxWidth()) {
+ Toggles.ContainerSwitch(
+ "Darkmode",
+ modifier = Modifier.weight(1f),
+ value = darkMode,
+ paddingValues = Toggles.rowStartPadding
+ ) { darkMode = it.also { settings["darkmode"] = it } }
+
+ Toggles.ContainerSwitch(
+ "Mono Font",
+ modifier = Modifier.weight(1f),
+ value = monoFont,
+ paddingValues = Toggles.rowEndPadding
+ ) { monoFont = it.also { settings["mono-font"] = it } }
+ }
+ WindowScaleSetting()
+ Toggles.ContainerSwitch("Show names by default", value = settings["display-names", false]) { settings["display-names"] = it }
+ Spacer(Modifier.height(5.dp))
+}
+
+@Composable
+fun WindowScaleSetting(modifier: Modifier = Modifier) {
+ var densityScale by remember { densityScale }
+ Row(
+ modifier.padding(8.dp, 4.dp).clip(AppShapes.large).background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(Modifier.padding(10.dp).weight(1f), horizontalAlignment = Alignment.Start) {
+ Text("Window Scale: $densityScale", style = MaterialTheme.typography.bodyLarge)
+ }
+ Row(Modifier.padding(10.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) {
+ IconButtonWithTooltip(Icons.Outlined.Remove, "Decrease Scale") {
+ val newScale = densityScale - 0.25f
+ settings["density-scale"] = newScale
+ densityScale = newScale
+ }
+ IconButtonWithTooltip(Icons.Outlined.Add, "Increase Scale") {
+ val newScale = densityScale + 0.25f
+ settings["density-scale"] = newScale
+ densityScale = newScale
+ }
+ }
+ }
+}
+
+@Composable
+fun MetadataSettings() {
+ Toggles.ContainerSwitch(
+ "Show episode summaries",
+ value = settings["display-ep-synopsis", false]
+ ) { settings["display-ep-synopsis"] = it }
+ Toggles.ContainerSwitch(
+ "Prefer german titles and summaries",
+ value = settings["prefer-german-metadata", false]
+ ) { settings["prefer-german-metadata"] = it }
+ Row(Modifier.fillMaxWidth()) {
+ Toggles.ContainerSwitch(
+ "Use list for shows",
+ modifier = Modifier.weight(1f),
+ value = settings["shows-list", false],
+ paddingValues = Toggles.rowStartPadding
+ ) { settings["shows-list"] = it }
+ Toggles.ContainerSwitch(
+ "Use list for movies",
+ modifier = Modifier.weight(1f),
+ value = settings["movies-list", false],
+ paddingValues = Toggles.rowEndPadding
+ ) { settings["movies-list"] = it }
+ }
+ Toggles.ContainerSwitch(
+ "Sort episodes ascendingly",
+ value = settings["episode-asc", false],
+ paddingValues = Toggles.colEndPadding
+ ) { settings["episode-asc"] = it }
+ Spacer(Modifier.height(5.dp))
+}
\ No newline at end of file
diff --git a/src/main/kotlin/moe/styx/views/settings/sub/DiscordSettings.kt b/src/main/kotlin/moe/styx/views/settings/sub/DiscordSettings.kt
new file mode 100644
index 0000000..aeb1894
--- /dev/null
+++ b/src/main/kotlin/moe/styx/views/settings/sub/DiscordSettings.kt
@@ -0,0 +1,37 @@
+package moe.styx.views.settings.sub
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.russhwolf.settings.get
+import com.russhwolf.settings.set
+import moe.styx.common.compose.components.misc.Toggles
+import moe.styx.common.compose.settings
+import moe.styx.logic.DiscordRPC
+
+@Composable
+fun DiscordSettings(modifier: Modifier = Modifier) {
+ Row(Modifier.fillMaxWidth().height(IntrinsicSize.Max)) {
+ Toggles.ContainerSwitch(
+ "Enable RPC",
+ modifier = Modifier.weight(1f).fillMaxHeight(),
+ value = settings["discord-rpc", true],
+ paddingValues = Toggles.rowStartPadding
+ ) {
+ settings["discord-rpc"] = it
+ if (it && !DiscordRPC.isStarted())
+ DiscordRPC.start()
+ else if (!it && DiscordRPC.isStarted()) {
+ DiscordRPC.clearActivity()
+ }
+ }
+
+ Toggles.ContainerSwitch(
+ "Show RPC when idle",
+ "Disabling this means the discord status will only show while you're watching something.",
+ value = settings["discord-rpc-idle", true], modifier = Modifier.weight(1f), paddingValues = Toggles.rowEndPadding
+ ) { settings["discord-rpc-idle"] = it }
+ }
+ Spacer(Modifier.height(5.dp))
+}
\ No newline at end of file
diff --git a/src/main/resources/fonts/JetBrainsMono-Bold.ttf b/src/main/resources/fonts/JetBrainsMono-Bold.ttf
new file mode 100644
index 0000000..1926c80
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-Bold.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-BoldItalic.ttf b/src/main/resources/fonts/JetBrainsMono-BoldItalic.ttf
new file mode 100644
index 0000000..a447751
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-BoldItalic.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-Italic.ttf b/src/main/resources/fonts/JetBrainsMono-Italic.ttf
new file mode 100644
index 0000000..8cf794a
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-Italic.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-Light.ttf b/src/main/resources/fonts/JetBrainsMono-Light.ttf
new file mode 100644
index 0000000..9d5d8a5
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-Light.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-LightItalic.ttf b/src/main/resources/fonts/JetBrainsMono-LightItalic.ttf
new file mode 100644
index 0000000..4c91d3e
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-LightItalic.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-Medium.ttf b/src/main/resources/fonts/JetBrainsMono-Medium.ttf
new file mode 100644
index 0000000..ad71d92
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-Medium.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-MediumItalic.ttf b/src/main/resources/fonts/JetBrainsMono-MediumItalic.ttf
new file mode 100644
index 0000000..4c96cc5
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-MediumItalic.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-Regular.ttf b/src/main/resources/fonts/JetBrainsMono-Regular.ttf
new file mode 100644
index 0000000..436c982
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-Regular.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-SemiBold.ttf b/src/main/resources/fonts/JetBrainsMono-SemiBold.ttf
new file mode 100644
index 0000000..b00a648
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-SemiBold.ttf differ
diff --git a/src/main/resources/fonts/JetBrainsMono-SemiBoldItalic.ttf b/src/main/resources/fonts/JetBrainsMono-SemiBoldItalic.ttf
new file mode 100644
index 0000000..5b6c9a8
Binary files /dev/null and b/src/main/resources/fonts/JetBrainsMono-SemiBoldItalic.ttf differ
diff --git a/src/main/resources/fonts/OpenSans-ExtraBold.ttf b/src/main/resources/fonts/OpenSans-ExtraBold.ttf
deleted file mode 100644
index 4eb3393..0000000
Binary files a/src/main/resources/fonts/OpenSans-ExtraBold.ttf and /dev/null differ
diff --git a/src/main/resources/fonts/OpenSans-ExtraBoldItalic.ttf b/src/main/resources/fonts/OpenSans-ExtraBoldItalic.ttf
deleted file mode 100644
index 75789b4..0000000
Binary files a/src/main/resources/fonts/OpenSans-ExtraBoldItalic.ttf and /dev/null differ
diff --git a/src/main/resources/fonts/OpenSans-SemiBold.ttf b/src/main/resources/fonts/OpenSans-SemiBold.ttf
deleted file mode 100644
index e5ab464..0000000
Binary files a/src/main/resources/fonts/OpenSans-SemiBold.ttf and /dev/null differ
diff --git a/src/main/resources/fonts/OpenSans-SemiBoldItalic.ttf b/src/main/resources/fonts/OpenSans-SemiBoldItalic.ttf
deleted file mode 100644
index cd23e15..0000000
Binary files a/src/main/resources/fonts/OpenSans-SemiBoldItalic.ttf and /dev/null differ