Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
a13ffc9
Add tests for compose
Luna712 Jul 1, 2026
3e68f62
Add
Luna712 Jul 1, 2026
c4032dd
Add
Luna712 Jul 1, 2026
09e4659
Add test
Luna712 Jul 1, 2026
751ca27
v2
Luna712 Jul 1, 2026
4e3bdf3
Test
Luna712 Jul 1, 2026
04d7d42
Test
Luna712 Jul 1, 2026
6cf05ae
Fix
Luna712 Jul 1, 2026
eb0f05f
Add test
Luna712 Jul 1, 2026
1098107
Add test
Luna712 Jul 1, 2026
aac8f73
Fix
Luna712 Jul 1, 2026
3c923a2
Add test
Luna712 Jul 1, 2026
5503fc4
Fix
Luna712 Jul 1, 2026
5963dbb
Add test
Luna712 Jul 1, 2026
fd79f9a
Fix?
Luna712 Jul 1, 2026
6560608
-
Luna712 Jul 1, 2026
0b15fef
-
Luna712 Jul 1, 2026
35bebe3
Update
Luna712 Jul 1, 2026
0f00719
-
Luna712 Jul 1, 2026
a575d65
Update
Luna712 Jul 1, 2026
cf72155
Update
Luna712 Jul 1, 2026
69bfcf3
Try new test
Luna712 Jul 1, 2026
e649792
Update
Luna712 Jul 5, 2026
05a01c8
Add to jvmTest
Luna712 Jul 5, 2026
5d2ba24
Add
Luna712 Jul 5, 2026
3f63b06
-
Luna712 Jul 5, 2026
5ca4c14
Try fix
Luna712 Jul 5, 2026
0959f8e
Try a different way
Luna712 Jul 5, 2026
10d09d5
Test
Luna712 Jul 5, 2026
0fc1748
Try
Luna712 Jul 5, 2026
84275b7
Try reflect
Luna712 Jul 5, 2026
71b6c9c
Try fix
Luna712 Jul 5, 2026
93417eb
Try fix
Luna712 Jul 5, 2026
ea2c295
Try
Luna712 Jul 5, 2026
de2906e
Add comment
Luna712 Jul 5, 2026
d310fa3
-
Luna712 Jul 5, 2026
cafbd5f
Merge branch 'compose' into compose-tests
Luna712 Jul 5, 2026
eca7b7a
+
Luna712 Jul 5, 2026
30d55a4
Use LocalResources
Luna712 Jul 5, 2026
93d8fee
fix
Luna712 Jul 5, 2026
1f30a58
Simplify
Luna712 Jul 5, 2026
c068e47
-comment for tvFocusable
Luna712 Jul 5, 2026
ca77d6a
-optin
Luna712 Jul 5, 2026
feb000a
+
Luna712 Jul 5, 2026
ef91218
ReadOnlyComposable
Luna712 Jul 5, 2026
fb5baea
ReadOnlyComposable
Luna712 Jul 5, 2026
c3c8fd2
ReadOnlyComposable
Luna712 Jul 5, 2026
927146a
ReadOnlyComposable
Luna712 Jul 5, 2026
2e51a1d
ReadOnlyComposable
Luna712 Jul 5, 2026
c1f31af
ReadOnlyComposable
Luna712 Jul 5, 2026
755a743
Add lint
Luna712 Jul 5, 2026
f8a5180
Add detekt
Luna712 Jul 5, 2026
c59e3f1
Add
Luna712 Jul 5, 2026
e5b0dba
Add config
Luna712 Jul 5, 2026
fb36a17
rules
Luna712 Jul 5, 2026
924c2ba
exclude icons
Luna712 Jul 5, 2026
751295d
Fix
Luna712 Jul 5, 2026
a4b99ac
Fix
Luna712 Jul 5, 2026
31e2565
Update
Luna712 Jul 5, 2026
7b889d6
exclude indentation
Luna712 Jul 5, 2026
bab8ec0
Disable MultiLineIfElse
Luna712 Jul 5, 2026
691b938
-
Luna712 Jul 5, 2026
13ade8f
Disable SpacingBetweenDeclarationsWithComments
Luna712 Jul 5, 2026
89ff227
Fix
Luna712 Jul 5, 2026
577d656
Fix
Luna712 Jul 5, 2026
0767e36
no wildcard import
Luna712 Jul 5, 2026
27b176f
Same
Luna712 Jul 5, 2026
cbe21eb
no wildcard
Luna712 Jul 5, 2026
f7f4d72
No wildcard
Luna712 Jul 5, 2026
43ec0ee
No wildcard
Luna712 Jul 5, 2026
8065976
-
Luna712 Jul 5, 2026
329b5c8
Fix
Luna712 Jul 5, 2026
1e726e6
-
Luna712 Jul 5, 2026
5a48c80
Update
Luna712 Jul 5, 2026
f6a83ab
-
Luna712 Jul 5, 2026
ca29506
-
Luna712 Jul 5, 2026
20673da
Fix
Luna712 Jul 5, 2026
14b3baf
Fix
Luna712 Jul 5, 2026
6dfe2e6
Update rules
Luna712 Jul 5, 2026
bab11d0
Update
Luna712 Jul 5, 2026
70b11ec
-
Luna712 Jul 5, 2026
f5ecf14
Enable
Luna712 Jul 5, 2026
9192efa
suffix
Luna712 Jul 5, 2026
0e8274b
Update
Luna712 Jul 5, 2026
7460fb6
test
Luna712 Jul 5, 2026
64ab19b
-
Luna712 Jul 5, 2026
6cece3a
WildcardImport rule
Luna712 Jul 6, 2026
3d900f5
Add
Luna712 Jul 6, 2026
0952821
Fix
Luna712 Jul 6, 2026
172b66e
Fix
Luna712 Jul 6, 2026
e06c467
2.0
Luna712 Jul 6, 2026
ba5a604
Update rules for 2.0
Luna712 Jul 6, 2026
51f3680
ktlint rule
Luna712 Jul 6, 2026
5ad41a8
BackingPropertyNaming
Luna712 Jul 6, 2026
7bf330c
Merge branch 'compose' into compose-tests
Luna712 Jul 7, 2026
85fff25
Updates to some rules
Luna712 Jul 7, 2026
0039fff
Split enum
Luna712 Jul 7, 2026
a55c8fc
rules
Luna712 Jul 7, 2026
3434ca4
Temp
Luna712 Jul 7, 2026
6f82280
Order
Luna712 Jul 7, 2026
b9fc456
Add modifier param
Luna712 Jul 7, 2026
184bb85
Add modifier param
Luna712 Jul 7, 2026
7117f9c
Order
Luna712 Jul 7, 2026
341fe91
Order
Luna712 Jul 7, 2026
015d3d0
Order
Luna712 Jul 7, 2026
55510ea
Fix
Luna712 Jul 7, 2026
61e220b
Composable
Luna712 Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,22 @@ jobs:
with:
name: pull-request-build
path: "app/build/outputs/apk/prerelease/debug/*.apk"

- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results
path: |
**/build/reports/tests/
**/build/test-results/
include-hidden-files: false

- name: Upload Lint Results
if: always()
uses: actions/upload-artifact@v7
with:
name: lint-results
path: |
**/build/reports/lint-results-*.html
**/build/reports/lint-results-*.xml
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
alias(libs.plugins.buildkonfig) apply false // Universal build config
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.compose.multiplatform) apply false
alias(libs.plugins.detekt) apply false
alias(libs.plugins.dokka) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
Expand Down
32 changes: 32 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.android.multiplatform.library)
alias(libs.plugins.compose.multiplatform)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.detekt)
}

kotlin {
Expand All @@ -16,6 +17,11 @@ kotlin {
androidResources {
enable = true
}

lint {
checkTestSources = true
checkDependencies = true
}
}

jvm()
Expand Down Expand Up @@ -47,11 +53,37 @@ kotlin {
implementation(libs.activity.compose)
implementation(libs.preference.ktx)
}

commonTest.dependencies {
implementation(libs.compose.ui.test)
implementation(libs.kotlin.test)
}

jvmTest.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlin.reflect)
implementation(libs.kotlin.test)
}
}
}

detekt {
buildUponDefaultConfig = true
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
source.setFrom(
"src/commonMain/kotlin",
"src/androidMain/kotlin",
"src/jvmMain/kotlin",
"src/commonTest/kotlin",
"src/jvmTest/kotlin",
)
}

dependencies {
androidRuntimeClasspath(libs.compose.ui.tooling)

detektPlugins(libs.detekt.formatting)
detektPlugins(libs.compose.rules.detekt)
}

compose.resources {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ package com.lagradost.cloudstream4.ui.components
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import com.lagradost.cloudstream4.ui.theme.CloudStreamTheme

/**
* Applies a ripple indication styled to match the app's selected theme.
*/
@Composable
fun Modifier.cloudStreamRipple(
interactionSource: MutableInteractionSource,
bounded: Boolean = true,
): Modifier = composed {
): Modifier {
val colors = CloudStreamTheme.colors
this.indication(
return this.indication(
interactionSource = interactionSource,
indication = ripple(bounded = bounded, color = colors.onBackground),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource

enum class ProfileImage {
DARK_BLUE, BLUE, ORANGE, PINK, PURPLE, RED, TEAL,
BLUE,
DARK_BLUE,
ORANGE,
PINK,
PURPLE,
RED,
TEAL,
}

@Composable
Expand All @@ -50,18 +56,20 @@ private fun ProfileImage.toRes(): DrawableResource = when (this) {
* otherwise falls back to the local [profileImage] background.
*
* @param profileImage Local background image fallback
* @param modifier Modifier to be applied to the profile picture container
* @param size Diameter of the circle, default 50.dp
* @param profilePictureUrl Optional remote URL to load via Coil
*/
@Composable
fun ProfilePicture(
profileImage: ProfileImage,
modifier: Modifier = Modifier,
size: Dp = 50.dp,
profilePictureUrl: String? = null,
) {
val colors = CloudStreamTheme.colors
Box(
modifier = Modifier
modifier = modifier
.size(size)
.border(2.dp, colors.onBackground.copy(alpha = 0.2f), CircleShape)
.clip(CircleShape),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fun SettingsItem(
subtitle: String? = null,
icon: ImageVector? = null,
focusRequester: FocusRequester? = null,
onClick: () -> Unit,
onClick: () -> Unit = {},
) {
val colors = CloudStreamTheme.colors
val isTV = remember { DeviceLayout.isLayout(DeviceLayout.TV) }
Expand Down Expand Up @@ -89,7 +89,6 @@ private fun SettingsItemPreview() {
title = stringResource(Res.string.category_general),
subtitle = stringResource(Res.string.category_general_subtitle),
icon = tune,
onClick = {},
)
}
}
Expand All @@ -101,7 +100,6 @@ private fun SettingsItemNoIconPreview() {
SettingsItem(
title = stringResource(Res.string.category_general),
subtitle = stringResource(Res.string.category_general_subtitle),
onClick = {},
)
}
}
Expand All @@ -113,7 +111,6 @@ private fun SettingsItemNoSubtitlePreview() {
SettingsItem(
title = stringResource(Res.string.category_general),
icon = tune,
onClick = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ fun SettingsScreen(
version: SettingsVersionState,
onNavigate: (SettingsCategory) -> Unit,
onVersionLongClick: () -> Unit = {},
modifier: Modifier = Modifier,
) {
val colors = CloudStreamTheme.colors
val isTV = remember { DeviceLayout.isLayout(DeviceLayout.TV) }
Expand All @@ -138,7 +139,7 @@ fun SettingsScreen(
}

Box(
modifier = Modifier
modifier = modifier
.fillMaxSize()
.background(colors.background)
.windowInsetsPadding(WindowInsets.statusBars)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.lagradost.cloudstream4.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.v2.runComposeUiTest
import androidx.compose.ui.unit.dp
import com.lagradost.cloudstream4.ui.theme.CloudStreamPalette
import com.lagradost.cloudstream4.ui.theme.CloudStreamTheme
import com.lagradost.cloudstream4.ui.theme.CloudStreamThemeMode
import kotlin.test.Test
import kotlin.test.assertNotEquals

@OptIn(ExperimentalTestApi::class)
class CloudStreamRippleTest {

@Test
fun appliesSuccessfullyWhenBounded() = runComposeUiTest {
val interactionSource = MutableInteractionSource()
setContent {
CloudStreamTheme {
Box(
modifier = Modifier
.testTag("target")
.size(64.dp)
.cloudStreamRipple(interactionSource, bounded = true),
)
}
}
waitForIdle()

onNodeWithTag("target").assertIsDisplayed()
}

@Test
fun appliesSuccessfullyWhenUnbounded() = runComposeUiTest {
val interactionSource = MutableInteractionSource()
setContent {
CloudStreamTheme {
Box(
modifier = Modifier
.testTag("target")
.size(64.dp)
.cloudStreamRipple(interactionSource, bounded = false),
)
}
}
waitForIdle()

onNodeWithTag("target").assertIsDisplayed()
}

@Test
fun appliesSuccessfullyUnderEveryThemeMode() = runComposeUiTest {
val modes = CloudStreamThemeMode.entries.filterNot { it == CloudStreamThemeMode.Dynamic }

setContent {
Column {
modes.forEach { mode ->
val interactionSource = remember { MutableInteractionSource() }
CloudStreamTheme(mode = mode) {
Box(
modifier = Modifier
.testTag("target-${mode.name}")
.size(32.dp)
.cloudStreamRipple(interactionSource),
)
}
}
}
}
waitForIdle()

modes.forEach { mode ->
onNodeWithTag("target-${mode.name}").assertIsDisplayed()
}
}

/**
* Check rhat it emits a real [PressInteraction.Press] into the
* [MutableInteractionSource] and confirms the pixel under the press point
* actually changes once the ripple draws over it.
*
* This test may end up being flaky. If so, it can be dropped.
*/
@Test
fun pressInteractionChangesThePixelsUnderThePress() = runComposeUiTest {
val interactionSource = MutableInteractionSource()
val boxSizeDp = 80

setContent {
CloudStreamTheme(mode = CloudStreamThemeMode.Dark) {
Box(
modifier = Modifier
.testTag("target")
.size(boxSizeDp.dp)
.background(CloudStreamPalette.DarkBlackBg)
.cloudStreamRipple(interactionSource),
)
}
}
waitForIdle()

val target = onNodeWithTag("target")
val idlePixels = target.captureToImage().toPixelMap()
val centerX = idlePixels.width / 2
val centerY = idlePixels.height / 2
val idleCenterColor = idlePixels[centerX, centerY]

val press = PressInteraction.Press(Offset(centerX.toFloat(), centerY.toFloat()))
interactionSource.tryEmit(press)
waitForIdle()

val pressedPixels = target.captureToImage().toPixelMap()
val pressedCenterColor = pressedPixels[centerX, centerY]

assertNotEquals(
idleCenterColor,
pressedCenterColor,
"Expected the ripple to visibly change the pixel under the press point",
)

// Release cleanly so the ripple doesn't leak a running animation past the test.
interactionSource.tryEmit(PressInteraction.Release(press))
waitForIdle()
}
}
Loading