neiam
/media-bubbles
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
Adopt the shared neiam look and feel
eddae77 · gmorell · 2026-11-05 16:27
Message
{commit_body(@commit)}
Files changed
modified
README.md
+18
−0
| 41 | 41 | seek ±10 s, the progress ring, double-tap play/pause, and hiding when no session exists. |
| 42 | 42 | Settings apply live, and the settings screen includes a working preview. |
| 43 | 43 | |
| 44 | +## Look & feel | |
| 45 | + | |
| 46 | +Shared with the other neiam Android apps (`bigtrippr`, `fieri-eternal`), with | |
| 47 | +bigtrippr as the reference copy: | |
| 48 | + | |
| 49 | +- `ui/theme/AppTheme.kt`: the daisyUI palettes (Light, Dark, Her, After Dark, | |
| 50 | + Forest, Sky, Clays, Stones, Blueprint) defined in OKLCh, with every Material 3 slot | |
| 51 | + pinned so Material's baseline purple never shows. **System** follows the OS | |
| 52 | + between Light and Dark. | |
| 53 | +- `Typography.kt`: B612 throughout. `Shapes.kt`: the shared corner radii. | |
| 54 | +- `ThemePicker.kt`: the web picker's list with swatches. `Common.kt`: an `AppBarTitle` | |
| 55 | + in the accent color, B612 Bold. | |
| 56 | +- System bars are painted in the palette's base color, and the window starts on After Dark | |
| 57 | + so launch doesn't flash white. | |
| 58 | + | |
| 59 | +The bubble follows the chosen palette too: base-100 body, primary play button | |
| 60 | +and progress ring, base-content text. | |
| 61 | + | |
| 44 | 62 | ## Architecture |
| 45 | 63 | |
| 46 | 64 | | Package | Role | |
modified
app/src/main/AndroidManifest.xml
+2
−2
| 25 | 25 | android:roundIcon="@mipmap/ic_icon" |
| 26 | 26 | android:allowBackup="false" |
| 27 | 27 | android:supportsRtl="true" |
| 28 | − android:theme="@style/Theme.Floaty"> | |
| 28 | + android:theme="@style/Theme.MediaBubbles"> | |
| 29 | 29 | |
| 30 | 30 | <activity |
| 31 | 31 | android:name=".MainActivity" |
| 32 | 32 | android:exported="true" |
| 33 | 33 | android:launchMode="singleTask" |
| 34 | − android:theme="@style/Theme.Floaty"> | |
| 34 | + android:theme="@style/Theme.MediaBubbles"> | |
| 35 | 35 | <intent-filter> |
| 36 | 36 | <action android:name="android.intent.action.MAIN"/> |
| 37 | 37 | <category android:name="android.intent.category.LAUNCHER"/> |
modified
app/src/main/java/org/neiam/mediabubbles/MainActivity.kt
+21
−3
| 4 | 4 | import android.os.Bundle |
| 5 | 5 | import android.provider.Settings |
| 6 | 6 | import androidx.activity.ComponentActivity |
| 7 | +import androidx.activity.SystemBarStyle | |
| 7 | 8 | import androidx.activity.compose.setContent |
| 9 | +import androidx.activity.enableEdgeToEdge | |
| 8 | 10 | import androidx.compose.foundation.layout.fillMaxSize |
| 9 | 11 | import androidx.compose.material3.MaterialTheme |
| 10 | 12 | import androidx.compose.material3.Surface |
| 13 | +import androidx.compose.runtime.LaunchedEffect | |
| 14 | +import androidx.compose.runtime.collectAsState | |
| 15 | +import androidx.compose.runtime.getValue | |
| 11 | 16 | import androidx.compose.ui.Modifier |
| 17 | +import androidx.compose.ui.graphics.toArgb | |
| 12 | 18 | import org.neiam.mediabubbles.overlay.OverlayService |
| 13 | −import org.neiam.mediabubbles.ui.FloatyTheme | |
| 14 | 19 | import org.neiam.mediabubbles.ui.SettingsScreen |
| 20 | +import org.neiam.mediabubbles.ui.theme.MediaBubblesTheme | |
| 21 | +import org.neiam.mediabubbles.ui.theme.appThemeForKey | |
| 15 | 22 | |
| 16 | 23 | class MainActivity : ComponentActivity() { |
| 17 | 24 | override fun onCreate(savedInstanceState: Bundle?) { |
| 18 | 25 | super.onCreate(savedInstanceState) |
| 19 | 26 | handle(intent) |
| 27 | + val prefs = container.prefs | |
| 20 | 28 | setContent { |
| 21 | − FloatyTheme { | |
| 29 | + val themeKey by prefs.themeKey.collectAsState() | |
| 30 | + val theme = appThemeForKey(themeKey) | |
| 31 | + | |
| 32 | + // System bars painted in the palette's base, as in the other neiam apps. | |
| 33 | + LaunchedEffect(theme) { | |
| 34 | + val bar = theme.bg.toArgb() | |
| 35 | + val style = if (theme.isLight) SystemBarStyle.light(bar, bar) else SystemBarStyle.dark(bar) | |
| 36 | + enableEdgeToEdge(statusBarStyle = style, navigationBarStyle = style) | |
| 37 | + } | |
| 38 | + | |
| 39 | + MediaBubblesTheme(theme) { | |
| 22 | 40 | Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { |
| 23 | − SettingsScreen() | |
| 41 | + SettingsScreen(themeKey = themeKey, onPickTheme = prefs::setThemeKey) | |
| 24 | 42 | } |
| 25 | 43 | } |
| 26 | 44 | } |
modified
app/src/main/java/org/neiam/mediabubbles/data/BubblePrefs.kt
+11
−0
| 3 | 3 | import android.content.Context |
| 4 | 4 | import kotlinx.coroutines.flow.MutableStateFlow |
| 5 | 5 | import kotlinx.coroutines.flow.StateFlow |
| 6 | +import org.neiam.mediabubbles.ui.theme.SYSTEM_THEME_KEY | |
| 6 | 7 | |
| 7 | 8 | /** Everything the user can tune about the bubble. */ |
| 8 | 9 | data class BubbleConfig( |
| 55 | 56 | |
| 56 | 57 | fun reset() = update { BubbleConfig() } |
| 57 | 58 | |
| 59 | + /** Chosen palette key; nothing chosen follows the OS, as on the web. */ | |
| 60 | + private val _themeKey = MutableStateFlow(sp.getString(K_THEME, null) ?: SYSTEM_THEME_KEY) | |
| 61 | + val themeKey: StateFlow<String> = _themeKey | |
| 62 | + | |
| 63 | + fun setThemeKey(key: String) { | |
| 64 | + sp.edit().putString(K_THEME, key).apply() | |
| 65 | + _themeKey.value = key | |
| 66 | + } | |
| 67 | + | |
| 58 | 68 | /** The user wants the bubble shown; used to bring it back after reboot / process death. */ |
| 59 | 69 | var bubbleWanted: Boolean |
| 60 | 70 | get() = sp.getBoolean(K_WANTED, false) |
| 106 | 116 | const val K_WANTED = "bubble_wanted" |
| 107 | 117 | const val K_POS_RIGHT = "pos_right" |
| 108 | 118 | const val K_POS_Y = "pos_y" |
| 119 | + const val K_THEME = "theme_key" | |
| 109 | 120 | } |
| 110 | 121 | } |
modified
app/src/main/java/org/neiam/mediabubbles/overlay/MediaBubble.kt
+17
−12
| 57 | 57 | import org.neiam.mediabubbles.data.BubblePrefs |
| 58 | 58 | import org.neiam.mediabubbles.media.MediaRepository |
| 59 | 59 | import org.neiam.mediabubbles.media.NowPlaying |
| 60 | +import org.neiam.mediabubbles.ui.theme.LocalAppTheme | |
| 60 | 61 | |
| 61 | −// Fixed dark palette (afterdark): the bubble sits over arbitrary maps, so it | |
| 62 | −// shouldn't flip with the system theme. | |
| 63 | −private val BubbleBg = Color(0xFF1B1840) | |
| 64 | −private val ArtFallbackBg = Color(0xFF2A2675) | |
| 65 | −private val Accent = Color(0xFFACABD5) | |
| 66 | −private val OnAccent = Color(0xFF201D65) | |
| 67 | −private val RingTrack = Color(0x40FFFFFF) | |
| 62 | +// The bubble follows the chosen neiam palette: base-100 body, base-200 art | |
| 63 | +// well, primary for the play button and progress, base-content for text. | |
| 64 | +private val BubbleBg @Composable get() = LocalAppTheme.current.bg | |
| 65 | +private val ArtFallbackBg @Composable get() = LocalAppTheme.current.cardBg | |
| 66 | +private val Accent @Composable get() = LocalAppTheme.current.primary | |
| 67 | +private val OnAccent @Composable get() = LocalAppTheme.current.onPrimary | |
| 68 | +private val Content @Composable get() = LocalAppTheme.current.content | |
| 69 | +private val Dim @Composable get() = LocalAppTheme.current.dim | |
| 70 | +private val RingTrack @Composable get() = LocalAppTheme.current.content.copy(alpha = 0.25f) | |
| 68 | 71 | |
| 69 | 72 | class BubbleActions( |
| 70 | 73 | val toggleExpanded: () -> Unit, |
| 141 | 144 | .graphicsLayer { alpha = cfg.opacity }, |
| 142 | 145 | shape = RoundedCornerShape(percent = 50), |
| 143 | 146 | color = BubbleBg, |
| 144 | − contentColor = Color.White, | |
| 147 | + contentColor = Content, | |
| 145 | 148 | shadowElevation = 3.dp, |
| 146 | 149 | ) { |
| 147 | 150 | Row(verticalAlignment = Alignment.CenterVertically) { |
| 211 | 214 | |
| 212 | 215 | if (cfg.showProgress && np != null && np.durationMs > 0) { |
| 213 | 216 | val progress = rememberProgress(np) |
| 217 | + val track = RingTrack | |
| 218 | + val accent = Accent | |
| 214 | 219 | Canvas(Modifier.size(size)) { |
| 215 | 220 | val sw = ring.toPx() * 0.7f |
| 216 | 221 | val inset = ring.toPx() / 2 |
| 217 | 222 | val arcSize = Size(this.size.width - inset * 2, this.size.height - inset * 2) |
| 218 | 223 | val tl = Offset(inset, inset) |
| 219 | − drawArc(RingTrack, 0f, 360f, false, tl, arcSize, style = Stroke(sw)) | |
| 224 | + drawArc(track, 0f, 360f, false, tl, arcSize, style = Stroke(sw)) | |
| 220 | 225 | drawArc( |
| 221 | − Accent, -90f, 360f * progress, false, tl, arcSize, | |
| 226 | + accent, -90f, 360f * progress, false, tl, arcSize, | |
| 222 | 227 | style = Stroke(sw, cap = StrokeCap.Round), |
| 223 | 228 | ) |
| 224 | 229 | } |
| 244 | 249 | text = sub, |
| 245 | 250 | maxLines = 1, |
| 246 | 251 | fontSize = (base * 0.82f).sp, |
| 247 | − color = Color.White.copy(alpha = 0.7f), | |
| 252 | + color = Dim, | |
| 248 | 253 | modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE), |
| 249 | 254 | ) |
| 250 | 255 | } |
| 292 | 297 | ) { |
| 293 | 298 | Icon( |
| 294 | 299 | icon, description, Modifier.size(size * 0.62f), |
| 295 | − tint = if (filled) OnAccent else Color.White, | |
| 300 | + tint = if (filled) OnAccent else Content, | |
| 296 | 301 | ) |
| 297 | 302 | } |
| 298 | 303 | } |
modified
app/src/main/java/org/neiam/mediabubbles/overlay/OverlayService.kt
+5
−2
Click to load diff…
added
app/src/main/java/org/neiam/mediabubbles/ui/Common.kt
+19
−0
Click to load diff…
modified
app/src/main/java/org/neiam/mediabubbles/ui/SettingsScreen.kt
+59
−10
Click to load diff…
deleted
app/src/main/java/org/neiam/mediabubbles/ui/Theme.kt
+0
−36
Click to load diff…
added
app/src/main/java/org/neiam/mediabubbles/ui/ThemePicker.kt
+71
−0
Click to load diff…
added
app/src/main/java/org/neiam/mediabubbles/ui/theme/AppTheme.kt
+215
−0
Click to load diff…
added
app/src/main/java/org/neiam/mediabubbles/ui/theme/Shapes.kt
+31
−0
Click to load diff…
added
app/src/main/java/org/neiam/mediabubbles/ui/theme/Theme.kt
+18
−0
Click to load diff…
added
app/src/main/java/org/neiam/mediabubbles/ui/theme/Typography.kt
+39
−0
Click to load diff…
modified
?
+0
−0
Click to load diff…
modified
?
+0
−0
Click to load diff…
added
app/src/main/res/values/colors.xml
+5
−0
Click to load diff…
modified
app/src/main/res/values/themes.xml
+6
−1
Click to load diff…
Parents: 19cd52b