Initial commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:icon="@android:drawable/ic_menu_compass"
|
||||
android:label="Overseerr"
|
||||
android:theme="@android:style/Theme.Material.NoActionBar">
|
||||
<activity
|
||||
android:name=".AppActivity"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.meloda.overseerr
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
class AppActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent { App() }
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppPreview() { App() }
|
||||
@@ -0,0 +1,19 @@
|
||||
package dev.meloda.overseerr.theme
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
|
||||
@Composable
|
||||
internal actual fun SystemAppearance(isDark: Boolean) {
|
||||
val view = LocalView.current
|
||||
LaunchedEffect(isDark) {
|
||||
val window = (view.context as Activity).window
|
||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = isDark
|
||||
isAppearanceLightNavigationBars = isDark
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4c2.21,0 4,-1.79 4,-4C16,9.79 14.21,8 12,8zM12,14c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C14,13.1 13.1,14 12,14z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M22,7.47V5.35C20.05,4.77 16.56,4 12,4C9.85,4 7.89,4.86 6.46,6.24C6.59,5.39 6.86,3.84 7.47,2H5.35C4.77,3.95 4,7.44 4,12c0,2.15 0.86,4.11 2.24,5.54c-0.85,-0.14 -2.4,-0.4 -4.24,-1.01v2.12C3.95,19.23 7.44,20 12,20c2.15,0 4.11,-0.86 5.54,-2.24c-0.14,0.85 -0.4,2.4 -1.01,4.24h2.12C19.23,20.05 20,16.56 20,12c0,-2.15 -0.86,-4.11 -2.24,-5.54C18.61,6.59 20.16,6.86 22,7.47zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6s6,2.69 6,6S15.31,18 12,18z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36c-0.98,1.37 -2.58,2.26 -4.4,2.26c-2.98,0 -5.4,-2.42 -5.4,-5.4c0,-1.81 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3L12,3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7L12,7zM2,13l2,0c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1l-2,0c-0.55,0 -1,0.45 -1,1S1.45,13 2,13zM20,13l2,0c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1l-2,0c-0.55,0 -1,0.45 -1,1S19.45,13 20,13zM11,2v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1V2c0,-0.55 -0.45,-1 -1,-1S11,1.45 11,2zM11,20v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1C11.45,19 11,19.45 11,20zM5.99,4.58c-0.39,-0.39 -1.03,-0.39 -1.41,0c-0.39,0.39 -0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0s0.39,-1.03 0,-1.41L5.99,4.58zM18.36,16.95c-0.39,-0.39 -1.03,-0.39 -1.41,0c-0.39,0.39 -0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0c0.39,-0.39 0.39,-1.03 0,-1.41L18.36,16.95zM19.42,5.99c0.39,-0.39 0.39,-1.03 0,-1.41c-0.39,-0.39 -1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0L19.42,5.99zM7.05,18.36c0.39,-0.39 0.39,-1.03 0,-1.41c-0.39,-0.39 -1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0L7.05,18.36z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M15.55,5.55L11,1v3.07C7.06,4.56 4,7.92 4,12s3.05,7.44 7,7.93v-2.02c-2.84,-0.48 -5,-2.94 -5,-5.91s2.16,-5.43 5,-5.91L11,10l4.55,-4.45zM19.93,11c-0.17,-1.39 -0.72,-2.73 -1.62,-3.89l-1.42,1.42c0.54,0.75 0.88,1.6 1.02,2.47h2.02zM13,17.9v2.02c1.39,-0.17 2.74,-0.71 3.9,-1.61l-1.44,-1.44c-0.75,0.54 -1.59,0.89 -2.46,1.03zM16.89,15.48l1.42,1.41c0.9,-1.16 1.45,-2.5 1.62,-3.89h-2.02c-0.14,0.87 -0.48,1.72 -1.02,2.48z" />
|
||||
</vector>
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<string name="cyclone">Cyclone</string>
|
||||
<string name="open_github">Open github</string>
|
||||
<string name="run">Run</string>
|
||||
<string name="stop">Stop</string>
|
||||
<string name="theme">Theme</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,100 @@
|
||||
package dev.meloda.overseerr
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.Image
|
||||
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.draw.rotate
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import overseerr.composeapp.generated.resources.*
|
||||
import dev.meloda.overseerr.theme.AppTheme
|
||||
import dev.meloda.overseerr.theme.LocalThemeIsDark
|
||||
import kotlinx.coroutines.isActive
|
||||
import org.jetbrains.compose.resources.Font
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.jetbrains.compose.resources.vectorResource
|
||||
|
||||
@Composable
|
||||
internal fun App() = AppTheme {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing)
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.cyclone),
|
||||
fontFamily = FontFamily(Font(Res.font.IndieFlower_Regular)),
|
||||
style = MaterialTheme.typography.displayLarge
|
||||
)
|
||||
|
||||
var isRotating by remember { mutableStateOf(false) }
|
||||
|
||||
val rotate = remember { Animatable(0f) }
|
||||
val target = 360f
|
||||
if (isRotating) {
|
||||
LaunchedEffect(Unit) {
|
||||
while (isActive) {
|
||||
val remaining = (target - rotate.value) / target
|
||||
rotate.animateTo(target, animationSpec = tween((1_000 * remaining).toInt(), easing = LinearEasing))
|
||||
rotate.snapTo(0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(250.dp)
|
||||
.padding(16.dp)
|
||||
.run { rotate(rotate.value) },
|
||||
imageVector = vectorResource(Res.drawable.ic_cyclone),
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
ElevatedButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
.widthIn(min = 200.dp),
|
||||
onClick = { isRotating = !isRotating },
|
||||
content = {
|
||||
Icon(vectorResource(Res.drawable.ic_rotate_right), contentDescription = null)
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(
|
||||
stringResource(if (isRotating) Res.string.stop else Res.string.run)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
var isDark by LocalThemeIsDark.current
|
||||
val icon = remember(isDark) {
|
||||
if (isDark) Res.drawable.ic_light_mode
|
||||
else Res.drawable.ic_dark_mode
|
||||
}
|
||||
|
||||
ElevatedButton(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp).widthIn(min = 200.dp),
|
||||
onClick = { isDark = !isDark },
|
||||
content = {
|
||||
Icon(vectorResource(icon), contentDescription = null)
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(stringResource(Res.string.theme))
|
||||
}
|
||||
)
|
||||
|
||||
val uriHandler = LocalUriHandler.current
|
||||
TextButton(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp).widthIn(min = 200.dp),
|
||||
onClick = { uriHandler.openUri("https://github.com/terrakok") },
|
||||
) {
|
||||
Text(stringResource(Res.string.open_github))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package dev.meloda.overseerr.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
//generated by https://m3.material.io/theme-builder#/custom
|
||||
//Color palette was taken here: https://colorhunt.co/palettes/popular
|
||||
|
||||
internal val md_theme_light_primary = Color(0xFF00687A)
|
||||
internal val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||
internal val md_theme_light_primaryContainer = Color(0xFFABEDFF)
|
||||
internal val md_theme_light_onPrimaryContainer = Color(0xFF001F26)
|
||||
internal val md_theme_light_secondary = Color(0xFF00696E)
|
||||
internal val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||
internal val md_theme_light_secondaryContainer = Color(0xFF6FF6FE)
|
||||
internal val md_theme_light_onSecondaryContainer = Color(0xFF002022)
|
||||
internal val md_theme_light_tertiary = Color(0xFF904D00)
|
||||
internal val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||
internal val md_theme_light_tertiaryContainer = Color(0xFFFFDCC2)
|
||||
internal val md_theme_light_onTertiaryContainer = Color(0xFF2E1500)
|
||||
internal val md_theme_light_error = Color(0xFFBA1A1A)
|
||||
internal val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||
internal val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||
internal val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||
internal val md_theme_light_background = Color(0xFFFFFBFF)
|
||||
internal val md_theme_light_onBackground = Color(0xFF221B00)
|
||||
internal val md_theme_light_surface = Color(0xFFFFFBFF)
|
||||
internal val md_theme_light_onSurface = Color(0xFF221B00)
|
||||
internal val md_theme_light_surfaceVariant = Color(0xFFDBE4E7)
|
||||
internal val md_theme_light_onSurfaceVariant = Color(0xFF3F484B)
|
||||
internal val md_theme_light_outline = Color(0xFF70797B)
|
||||
internal val md_theme_light_inverseOnSurface = Color(0xFFFFF0C0)
|
||||
internal val md_theme_light_inverseSurface = Color(0xFF3A3000)
|
||||
internal val md_theme_light_inversePrimary = Color(0xFF55D6F4)
|
||||
internal val md_theme_light_shadow = Color(0xFF000000)
|
||||
internal val md_theme_light_surfaceTint = Color(0xFF00687A)
|
||||
internal val md_theme_light_outlineVariant = Color(0xFFBFC8CB)
|
||||
internal val md_theme_light_scrim = Color(0xFF000000)
|
||||
|
||||
internal val md_theme_dark_primary = Color(0xFF55D6F4)
|
||||
internal val md_theme_dark_onPrimary = Color(0xFF003640)
|
||||
internal val md_theme_dark_primaryContainer = Color(0xFF004E5C)
|
||||
internal val md_theme_dark_onPrimaryContainer = Color(0xFFABEDFF)
|
||||
internal val md_theme_dark_secondary = Color(0xFF4CD9E2)
|
||||
internal val md_theme_dark_onSecondary = Color(0xFF00373A)
|
||||
internal val md_theme_dark_secondaryContainer = Color(0xFF004F53)
|
||||
internal val md_theme_dark_onSecondaryContainer = Color(0xFF6FF6FE)
|
||||
internal val md_theme_dark_tertiary = Color(0xFFFFB77C)
|
||||
internal val md_theme_dark_onTertiary = Color(0xFF4D2700)
|
||||
internal val md_theme_dark_tertiaryContainer = Color(0xFF6D3900)
|
||||
internal val md_theme_dark_onTertiaryContainer = Color(0xFFFFDCC2)
|
||||
internal val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||
internal val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||
internal val md_theme_dark_onError = Color(0xFF690005)
|
||||
internal val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||
internal val md_theme_dark_background = Color(0xFF221B00)
|
||||
internal val md_theme_dark_onBackground = Color(0xFFFFE264)
|
||||
internal val md_theme_dark_surface = Color(0xFF221B00)
|
||||
internal val md_theme_dark_onSurface = Color(0xFFFFE264)
|
||||
internal val md_theme_dark_surfaceVariant = Color(0xFF3F484B)
|
||||
internal val md_theme_dark_onSurfaceVariant = Color(0xFFBFC8CB)
|
||||
internal val md_theme_dark_outline = Color(0xFF899295)
|
||||
internal val md_theme_dark_inverseOnSurface = Color(0xFF221B00)
|
||||
internal val md_theme_dark_inverseSurface = Color(0xFFFFE264)
|
||||
internal val md_theme_dark_inversePrimary = Color(0xFF00687A)
|
||||
internal val md_theme_dark_shadow = Color(0xFF000000)
|
||||
internal val md_theme_dark_surfaceTint = Color(0xFF55D6F4)
|
||||
internal val md_theme_dark_outlineVariant = Color(0xFF3F484B)
|
||||
internal val md_theme_dark_scrim = Color(0xFF000000)
|
||||
|
||||
|
||||
internal val seed = Color(0xFF2C3639)
|
||||
@@ -0,0 +1,31 @@
|
||||
package dev.meloda.overseerr.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.*
|
||||
|
||||
internal val LocalThemeIsDark = compositionLocalOf { mutableStateOf(true) }
|
||||
|
||||
@Composable
|
||||
internal fun AppTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val systemIsDark = isSystemInDarkTheme()
|
||||
val isDarkState = remember { mutableStateOf(systemIsDark) }
|
||||
CompositionLocalProvider(
|
||||
LocalThemeIsDark provides isDarkState
|
||||
) {
|
||||
val isDark by isDarkState
|
||||
SystemAppearance(!isDark)
|
||||
MaterialTheme(
|
||||
colorScheme = if (isDark) darkColorScheme() else lightColorScheme(),
|
||||
content = { Surface(content = content) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal expect fun SystemAppearance(isDark: Boolean)
|
||||
@@ -0,0 +1,45 @@
|
||||
package dev.meloda.overseerr
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.runComposeUiTest
|
||||
import kotlin.test.Test
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
class ComposeTest {
|
||||
|
||||
@Test
|
||||
fun simpleCheck() = runComposeUiTest {
|
||||
setContent {
|
||||
var txt by remember { mutableStateOf("Go") }
|
||||
Column {
|
||||
Text(
|
||||
text = txt,
|
||||
modifier = Modifier.testTag("t_text")
|
||||
)
|
||||
Button(
|
||||
onClick = { txt += "." },
|
||||
modifier = Modifier.testTag("t_button")
|
||||
) {
|
||||
Text("click me")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNodeWithTag("t_button").apply {
|
||||
repeat(3) { performClick() }
|
||||
}
|
||||
onNodeWithTag("t_text").assertTextEquals("Go...")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package dev.meloda.overseerr.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import platform.UIKit.UIApplication
|
||||
import platform.UIKit.UIStatusBarStyleDarkContent
|
||||
import platform.UIKit.UIStatusBarStyleLightContent
|
||||
import platform.UIKit.setStatusBarStyle
|
||||
|
||||
@Composable
|
||||
internal actual fun SystemAppearance(isDark: Boolean) {
|
||||
LaunchedEffect(isDark) {
|
||||
UIApplication.sharedApplication.setStatusBarStyle(
|
||||
if (isDark) UIStatusBarStyleDarkContent else UIStatusBarStyleLightContent
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
import dev.meloda.overseerr.App
|
||||
import platform.UIKit.UIViewController
|
||||
|
||||
fun MainViewController(): UIViewController = ComposeUIViewController { App() }
|
||||
@@ -0,0 +1,7 @@
|
||||
package dev.meloda.overseerr.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
internal actual fun SystemAppearance(isDark: Boolean) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import java.awt.Dimension
|
||||
import dev.meloda.overseerr.App
|
||||
|
||||
fun main() = application {
|
||||
Window(
|
||||
title = "Overseerr",
|
||||
state = rememberWindowState(width = 800.dp, height = 600.dp),
|
||||
onCloseRequest = ::exitApplication,
|
||||
) {
|
||||
window.minimumSize = Dimension(350, 600)
|
||||
App()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user