Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ jobs:
:demo:assembleDebug
- name: Compile published artifact as YAQMC-style consumer
run: gradle -p integration/consumer-smoke assembleDebug
- name: Compile published artifact from plain Android View host
run: gradle -p integration/view-consumer-smoke assembleDebug
89 changes: 89 additions & 0 deletions amll/src/main/java/dev/yaqmc/amll/ui/AMLLPlayerView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package dev.yaqmc.amll.ui

import android.content.Context
import android.util.AttributeSet
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import dev.yaqmc.amll.model.LyricLine
import dev.yaqmc.amll.model.MaskObsceneWordsMode
import dev.yaqmc.amll.state.AMLLPlayerState

/**
* Android View host for applications that are not themselves Compose-first.
*
* The renderer remains [AMLLPlayer]. This wrapper owns the Compose composition and exposes
* imperative playback and lyric updates so a traditional Activity, Fragment, Capacitor host,
* or other View hierarchy can mount AMLL without declaring a host-side `@Composable`.
*
* Calls that mutate renderer state should be made from the Android main thread, like normal View
* mutations.
*/
class AMLLPlayerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AbstractComposeView(context, attrs, defStyleAttr) {
private val playerState = AMLLPlayerState()

/** Current renderer style. Assigning a new value recomposes the hosted player. */
var style: AMLLStyle by mutableStateOf(AMLLStyle())

/** Invoked when the user taps a lyric line. */
var onLineClick: ((LyricLine) -> Unit)? by mutableStateOf(null)

/** Exposes the underlying state for advanced integrations while simple hosts stay imperative. */
val state: AMLLPlayerState
get() = playerState

init {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
}

fun setLyricLines(lines: List<LyricLine>) {
playerState.setLyricLines(lines)
}

fun update(positionMs: Long, isPlaying: Boolean) {
playerState.update(positionMs = positionMs, isPlaying = isPlaying)
}

fun updatePosition(positionMs: Long) {
playerState.update(positionMs)
}

fun updatePlaybackState(isPlaying: Boolean) {
playerState.updatePlaybackState(isPlaying)
}

fun seekTo(positionMs: Long) {
playerState.seekTo(positionMs)
}

fun setAutoSeekDetectionEnabled(enabled: Boolean) {
playerState.updateAutoSeekDetectionEnabled(enabled)
}

fun setMaskObsceneWordsMode(mode: MaskObsceneWordsMode) {
playerState.updateMaskObsceneWordsMode(mode)
}

fun setMaskObsceneWordChar(char: Char) {
playerState.updateMaskObsceneWordChar(char)
}

@Composable
override fun Content() {
AMLLPlayer(
state = playerState,
modifier = Modifier.fillMaxSize(),
style = style,
onLineClick = onLineClick,
)
}
}
25 changes: 25 additions & 0 deletions integration/view-consumer-smoke/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id("com.android.library") version "8.13.0"
id("org.jetbrains.kotlin.android") version "2.2.20"
}

android {
namespace = "dev.yaqmc.amll.viewconsumer"
compileSdk = 36

defaultConfig {
minSdk = 26
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
kotlinOptions {
jvmTarget = "21"
}
}

dependencies {
implementation("dev.yaqmc:amll-android:0.1.0-SNAPSHOT")
}
1 change: 1 addition & 0 deletions integration/view-consumer-smoke/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
android.useAndroidX=true
18 changes: 18 additions & 0 deletions integration/view-consumer-smoke/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenLocal()
google()
mavenCentral()
}
}

rootProject.name = "amll-view-consumer-smoke"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.yaqmc.amll.viewconsumer

import android.content.Context
import android.widget.FrameLayout
import dev.yaqmc.amll.ui.AMLLPlayerView

class PublishedViewArtifactSmoke(context: Context) : FrameLayout(context) {
val lyricsView = AMLLPlayerView(context)

init {
addView(
lyricsView,
LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT,
),
)
lyricsView.setLyricLines(emptyList())
lyricsView.update(positionMs = 0L, isPlaying = false)
}
}
Loading