Skip to content

Commit 61194a1

Browse files
authored
Merge pull request #1272 from maxrave-dev/v0.2.21/floating_toolbar
Floating toolbar with Liquid glass implementation
2 parents 7f30bb1 + e4d8bcb commit 61194a1

20 files changed

Lines changed: 644 additions & 127 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,6 @@ android {
148148
compose = true
149149
buildConfig = true
150150
}
151-
@Suppress("UnstableApiUsage")
152-
composeOptions {
153-
kotlinCompilerExtensionVersion = "1.5.15"
154-
}
155151
packaging {
156152
jniLibs.useLegacyPackaging = true
157153
jniLibs.excludes +=
@@ -233,7 +229,6 @@ dependencies {
233229
// Other module
234230
implementation(project(":domain"))
235231
implementation(project(":data"))
236-
implementation(project(":media3"))
237232
implementation(project(":media3-ui"))
238233

239234
implementation(libs.lifecycle.livedata.ktx)
@@ -304,6 +299,8 @@ dependencies {
304299

305300
fullImplementation(libs.sentry.android)
306301

302+
implementation(libs.liquid.glass)
303+
307304
// debugImplementation(libs.leak.canary)
308305
}
309306
/**

app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -173,29 +173,6 @@
173173
<meta-data
174174
android:name="androidx.car.app.TintableAttributionIcon"
175175
android:resource="@drawable/monochrome" />
176-
177-
<service
178-
android:name="com.maxrave.media3.service.SimpleMediaService"
179-
android:exported="true"
180-
android:foregroundServiceType="mediaPlayback"
181-
android:stopWithTask="false">
182-
<intent-filter>
183-
<action android:name="androidx.media3.session.MediaSessionService" />
184-
<action android:name="androidx.media3.session.MediaLibraryService" />
185-
<action android:name="android.media.browse.MediaBrowserService" />
186-
<action android:name="android.intent.action.MEDIA_BUTTON" />
187-
</intent-filter>
188-
</service>
189-
<service
190-
android:name="com.maxrave.media3.service.download.MusicDownloadService"
191-
android:exported="true"
192-
android:foregroundServiceType="dataSync">
193-
<intent-filter>
194-
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
195-
196-
<category android:name="android.intent.category.DEFAULT" />
197-
</intent-filter>
198-
</service>
199176
<service
200177
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
201178
android:enabled="false"

app/src/main/java/com/maxrave/simpmusic/extension/UIExt.kt

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.activity.ComponentActivity
1010
import androidx.annotation.ColorInt
1111
import androidx.annotation.DrawableRes
1212
import androidx.compose.animation.core.animateFloat
13+
import androidx.compose.animation.core.animateFloatAsState
1314
import androidx.compose.animation.core.infiniteRepeatable
1415
import androidx.compose.animation.core.rememberInfiniteTransition
1516
import androidx.compose.animation.core.tween
@@ -24,13 +25,16 @@ import androidx.compose.foundation.lazy.LazyListState
2425
import androidx.compose.runtime.Composable
2526
import androidx.compose.runtime.DisposableEffect
2627
import androidx.compose.runtime.LaunchedEffect
28+
import androidx.compose.runtime.State
2729
import androidx.compose.runtime.derivedStateOf
2830
import androidx.compose.runtime.getValue
2931
import androidx.compose.runtime.mutableIntStateOf
3032
import androidx.compose.runtime.mutableStateOf
3133
import androidx.compose.runtime.remember
3234
import androidx.compose.runtime.setValue
3335
import androidx.compose.runtime.snapshotFlow
36+
import androidx.compose.ui.Alignment
37+
import androidx.compose.ui.BiasAlignment
3438
import androidx.compose.ui.Modifier
3539
import androidx.compose.ui.composed
3640
import androidx.compose.ui.draw.DrawModifier
@@ -42,6 +46,7 @@ import androidx.compose.ui.graphics.Color
4246
import androidx.compose.ui.graphics.ColorFilter
4347
import androidx.compose.ui.graphics.ColorMatrix
4448
import androidx.compose.ui.graphics.Paint
49+
import androidx.compose.ui.graphics.Shape
4550
import androidx.compose.ui.graphics.asImageBitmap
4651
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
4752
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
@@ -56,12 +61,18 @@ import androidx.compose.ui.platform.LocalResources
5661
import androidx.compose.ui.platform.LocalView
5762
import androidx.compose.ui.text.TextStyle
5863
import androidx.compose.ui.unit.IntSize
64+
import androidx.compose.ui.unit.dp
5965
import androidx.core.app.PictureInPictureModeChangedInfo
6066
import androidx.core.content.res.ResourcesCompat
6167
import androidx.core.graphics.ColorUtils
6268
import androidx.core.graphics.drawable.toBitmap
6369
import androidx.core.util.Consumer
6470
import com.kmpalette.palette.graphics.Palette
71+
import com.kyant.backdrop.Backdrop
72+
import com.kyant.backdrop.drawBackdrop
73+
import com.kyant.backdrop.effects.dispersion
74+
import com.kyant.backdrop.effects.refraction
75+
import com.kyant.backdrop.effects.saturation
6576
import com.maxrave.domain.data.model.ui.ScreenSizeInfo
6677
import com.maxrave.logger.Logger
6778
import com.maxrave.simpmusic.ui.theme.md_theme_dark_background
@@ -545,4 +556,37 @@ fun rememberIsInPipMode(): Boolean {
545556
onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) }
546557
}
547558
return pipMode
548-
}
559+
}
560+
561+
@Composable
562+
fun animateAlignmentAsState(targetAlignment: Alignment): State<Alignment> {
563+
val biased = targetAlignment as BiasAlignment
564+
val horizontal by animateFloatAsState(biased.horizontalBias)
565+
val vertical by animateFloatAsState(biased.verticalBias)
566+
return remember { derivedStateOf { BiasAlignment(horizontal, vertical) } }
567+
}
568+
569+
fun Modifier.drawBackdropCustomShape(
570+
backdrop: Backdrop,
571+
shape: Shape,
572+
alpha: Float = 0.6f,
573+
): Modifier =
574+
this.drawBackdrop(
575+
backdrop = backdrop,
576+
shapeProvider = { shape },
577+
onDrawSurface = { drawRect(Color.Black.copy(alpha = alpha.coerceIn(0f, 1f))) },
578+
) {
579+
// saturation boost
580+
saturation()
581+
// blur
582+
// lens
583+
refraction(
584+
height = 24f.dp.toPx(),
585+
amount = 64f.dp.toPx(),
586+
hasDepthEffect = true,
587+
)
588+
dispersion(
589+
height = 12.dp.toPx(),
590+
amount = 24.dp.toPx(),
591+
)
592+
}

app/src/main/java/com/maxrave/simpmusic/ui/MainActivity.kt

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import androidx.compose.animation.fadeIn
1919
import androidx.compose.animation.fadeOut
2020
import androidx.compose.animation.slideInHorizontally
2121
import androidx.compose.foundation.ExperimentalFoundationApi
22+
import androidx.compose.foundation.layout.Box
2223
import androidx.compose.foundation.layout.Column
24+
import androidx.compose.foundation.layout.fillMaxSize
2325
import androidx.compose.foundation.layout.fillMaxWidth
2426
import androidx.compose.foundation.layout.height
2527
import androidx.compose.foundation.layout.heightIn
@@ -40,14 +42,21 @@ import androidx.compose.ui.Modifier
4042
import androidx.compose.ui.graphics.Color
4143
import androidx.compose.ui.graphics.toArgb
4244
import androidx.compose.ui.res.stringResource
45+
import androidx.compose.ui.text.SpanStyle
46+
import androidx.compose.ui.text.TextLinkStyles
47+
import androidx.compose.ui.text.font.FontWeight
4348
import androidx.compose.ui.text.style.TextDecoration
4449
import androidx.compose.ui.unit.dp
50+
import androidx.compose.ui.unit.sp
4551
import androidx.compose.ui.window.DialogProperties
4652
import androidx.core.net.toUri
4753
import androidx.core.os.LocaleListCompat
4854
import androidx.lifecycle.compose.collectAsStateWithLifecycle
55+
import androidx.navigation.NavDestination.Companion.hasRoute
4956
import androidx.navigation.compose.currentBackStackEntryAsState
5057
import androidx.navigation.compose.rememberNavController
58+
import com.kyant.backdrop.backdrop
59+
import com.kyant.backdrop.rememberBackdrop
5160
import com.maxrave.common.FIRST_TIME_MIGRATION
5261
import com.maxrave.common.R
5362
import com.maxrave.common.SELECTED_LANGUAGE
@@ -60,14 +69,17 @@ import com.maxrave.domain.mediaservice.handler.MediaPlayerHandler
6069
import com.maxrave.logger.Logger
6170
import com.maxrave.simpmusic.di.viewModelModule
6271
import com.maxrave.simpmusic.ui.component.AppBottomNavigationBar
72+
import com.maxrave.simpmusic.ui.component.LiquidGlassAppBottomNavigationBar
6373
import com.maxrave.simpmusic.ui.navigation.destination.home.NotificationDestination
6474
import com.maxrave.simpmusic.ui.navigation.destination.list.AlbumDestination
6575
import com.maxrave.simpmusic.ui.navigation.destination.list.ArtistDestination
6676
import com.maxrave.simpmusic.ui.navigation.destination.list.PlaylistDestination
77+
import com.maxrave.simpmusic.ui.navigation.destination.search.SearchDestination
6778
import com.maxrave.simpmusic.ui.navigation.graph.AppNavigationGraph
6879
import com.maxrave.simpmusic.ui.screen.MiniPlayer
6980
import com.maxrave.simpmusic.ui.screen.player.NowPlayingScreen
7081
import com.maxrave.simpmusic.ui.theme.AppTheme
82+
import com.maxrave.simpmusic.ui.theme.fontFamily
7183
import com.maxrave.simpmusic.ui.theme.typo
7284
import com.maxrave.simpmusic.utils.VersionManager
7385
import com.maxrave.simpmusic.viewModel.SharedViewModel
@@ -229,11 +241,16 @@ class MainActivity : AppCompatActivity() {
229241
val intent by viewModel.intent.collectAsStateWithLifecycle()
230242

231243
val isTranslucentBottomBar by viewModel.getTranslucentBottomBar().collectAsStateWithLifecycle(DataStoreManager.FALSE)
244+
val isLiquidGlassEnabled by viewModel.getEnableLiquidGlass().collectAsStateWithLifecycle(DataStoreManager.FALSE)
232245
// MiniPlayer visibility logic
233246
var isShowMiniPlayer by rememberSaveable {
234247
mutableStateOf(true)
235248
}
236249

250+
var isInSearchPage by rememberSaveable {
251+
mutableStateOf(false)
252+
}
253+
237254
// Now playing screen
238255
var isShowNowPlaylistScreen by rememberSaveable {
239256
mutableStateOf(false)
@@ -340,8 +357,11 @@ class MainActivity : AppCompatActivity() {
340357
if (navBackStackEntry?.destination?.route?.contains("FullscreenDestination") == true) {
341358
isShowNowPlaylistScreen = false
342359
}
360+
navBackStackEntry?.destination?.let { current ->
361+
isInSearchPage = current.hasRoute(SearchDestination::class)
362+
}
343363
}
344-
364+
val backdrop = rememberBackdrop()
345365
AppTheme {
346366
Scaffold(
347367
bottomBar = {
@@ -352,7 +372,7 @@ class MainActivity : AppCompatActivity() {
352372
) {
353373
Column {
354374
AnimatedVisibility(
355-
isShowMiniPlayer,
375+
isShowMiniPlayer && (!isInSearchPage || isLiquidGlassEnabled == DataStoreManager.FALSE),
356376
enter = fadeIn() + slideInHorizontally(),
357377
exit = fadeOut(),
358378
) {
@@ -365,6 +385,7 @@ class MainActivity : AppCompatActivity() {
365385
).padding(
366386
bottom = 4.dp,
367387
),
388+
backdrop = backdrop,
368389
onClick = {
369390
isShowNowPlaylistScreen = true
370391
},
@@ -374,29 +395,47 @@ class MainActivity : AppCompatActivity() {
374395
},
375396
)
376397
}
377-
AppBottomNavigationBar(
378-
navController = navController,
379-
isTranslucentBackground = isTranslucentBottomBar == DataStoreManager.TRUE,
380-
) { klass ->
381-
viewModel.reloadDestination(klass)
398+
if (isLiquidGlassEnabled == DataStoreManager.TRUE) {
399+
LiquidGlassAppBottomNavigationBar(
400+
navController = navController,
401+
backdrop = backdrop,
402+
viewModel = viewModel,
403+
shouldShowMiniPlayer = isShowMiniPlayer,
404+
onOpenNowPlaying = { isShowNowPlaylistScreen = true },
405+
) { klass ->
406+
viewModel.reloadDestination(klass)
407+
}
408+
} else {
409+
AppBottomNavigationBar(
410+
navController = navController,
411+
isTranslucentBackground = isTranslucentBottomBar == DataStoreManager.TRUE,
412+
) { klass ->
413+
viewModel.reloadDestination(klass)
414+
}
382415
}
383416
}
384417
}
385418
},
386419
content = { innerPadding ->
387-
AppNavigationGraph(
388-
innerPadding = innerPadding,
389-
navController = navController,
390-
hideNavBar = {
391-
isNavBarVisible = false
392-
},
393-
showNavBar = {
394-
isNavBarVisible = true
395-
},
396-
showNowPlayingSheet = {
397-
isShowNowPlaylistScreen = true
398-
},
399-
)
420+
Box(
421+
Modifier
422+
.fillMaxSize()
423+
.backdrop(backdrop),
424+
) {
425+
AppNavigationGraph(
426+
innerPadding = innerPadding,
427+
navController = navController,
428+
hideNavBar = {
429+
isNavBarVisible = false
430+
},
431+
showNavBar = {
432+
isNavBarVisible = true
433+
},
434+
showNowPlayingSheet = {
435+
isShowNowPlaylistScreen = true
436+
},
437+
)
438+
}
400439

401440
if (isShowNowPlaylistScreen) {
402441
NowPlayingScreen(
@@ -535,9 +574,14 @@ class MainActivity : AppCompatActivity() {
535574
text = typo.bodySmall,
536575
bullet = typo.bodySmall,
537576
paragraph = typo.bodySmall,
538-
link =
539-
typo.bodySmall.copy(
540-
textDecoration = TextDecoration.Underline,
577+
textLink =
578+
TextLinkStyles(
579+
SpanStyle(
580+
fontSize = 11.sp,
581+
fontWeight = FontWeight.Normal,
582+
fontFamily = fontFamily,
583+
textDecoration = TextDecoration.Underline,
584+
),
541585
),
542586
),
543587
)

0 commit comments

Comments
 (0)