Improve IntentUriScreen and handle links in SearchScreen
This commit is contained in:
@@ -21,15 +21,17 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/Theme.ViMusic.NoActionBar"
|
android:theme="@style/Theme.ViMusic.NoActionBar"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package it.vfsfitvnm.vimusic
|
package it.vfsfitvnm.vimusic
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@@ -15,7 +16,7 @@ import androidx.compose.foundation.LocalIndication
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.LocalOverScrollConfiguration
|
import androidx.compose.foundation.gestures.LocalOverScrollConfiguration
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.ripple.LocalRippleTheme
|
import androidx.compose.material.ripple.LocalRippleTheme
|
||||||
import androidx.compose.material.ripple.RippleAlpha
|
import androidx.compose.material.ripple.RippleAlpha
|
||||||
@@ -27,7 +28,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.text.ExperimentalTextApi
|
import androidx.compose.ui.text.ExperimentalTextApi
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.media3.session.MediaController
|
import androidx.media3.session.MediaController
|
||||||
import androidx.media3.session.SessionToken
|
import androidx.media3.session.SessionToken
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
@@ -38,12 +39,14 @@ import it.vfsfitvnm.vimusic.enums.ColorPaletteMode
|
|||||||
import it.vfsfitvnm.vimusic.services.PlayerService
|
import it.vfsfitvnm.vimusic.services.PlayerService
|
||||||
import it.vfsfitvnm.vimusic.ui.components.BottomSheetMenu
|
import it.vfsfitvnm.vimusic.ui.components.BottomSheetMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.rememberBottomSheetState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.rememberMenuState
|
import it.vfsfitvnm.vimusic.ui.components.rememberMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.HomeScreen
|
import it.vfsfitvnm.vimusic.ui.screens.HomeScreen
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.*
|
import it.vfsfitvnm.vimusic.ui.styling.*
|
||||||
|
import it.vfsfitvnm.vimusic.ui.views.PlayerView
|
||||||
import it.vfsfitvnm.vimusic.utils.*
|
import it.vfsfitvnm.vimusic.utils.*
|
||||||
|
|
||||||
private val Context.dataStore by preferencesDataStore(name = "preferences")
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@@ -51,12 +54,16 @@ private val Context.dataStore by preferencesDataStore(name = "preferences")
|
|||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var mediaControllerFuture: ListenableFuture<MediaController>
|
private lateinit var mediaControllerFuture: ListenableFuture<MediaController>
|
||||||
|
|
||||||
|
private var uri by mutableStateOf<Uri?>(null)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val sessionToken = SessionToken(this, ComponentName(this, PlayerService::class.java))
|
val sessionToken = SessionToken(this, ComponentName(this, PlayerService::class.java))
|
||||||
mediaControllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
|
mediaControllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
|
||||||
|
|
||||||
|
uri = intent?.data
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val preferences = rememberPreferences()
|
val preferences = rememberPreferences()
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
@@ -119,16 +126,26 @@ class MainActivity : ComponentActivity() {
|
|||||||
LocalTypography provides rememberTypography(colorPalette.text),
|
LocalTypography provides rememberTypography(colorPalette.text),
|
||||||
LocalYoutubePlayer provides rememberYoutubePlayer(mediaControllerFuture) {
|
LocalYoutubePlayer provides rememberYoutubePlayer(mediaControllerFuture) {
|
||||||
it.repeatMode = preferences.repeatMode
|
it.repeatMode = preferences.repeatMode
|
||||||
},
|
},
|
||||||
LocalMenuState provides rememberMenuState(),
|
LocalMenuState provides rememberMenuState(),
|
||||||
LocalHapticFeedback provides rememberHapticFeedback()
|
LocalHapticFeedback provides rememberHapticFeedback()
|
||||||
) {
|
) {
|
||||||
Box(
|
BoxWithConstraints(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(LocalColorPalette.current.background)
|
.background(colorPalette.background)
|
||||||
) {
|
) {
|
||||||
HomeScreen(intentUri = intent?.data)
|
uri?.let {
|
||||||
|
IntentUriScreen(uri = it)
|
||||||
|
} ?: HomeScreen()
|
||||||
|
|
||||||
|
PlayerView(
|
||||||
|
layoutState = rememberBottomSheetState(
|
||||||
|
lowerBound = 64.dp, upperBound = maxHeight
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
)
|
||||||
|
|
||||||
BottomSheetMenu(
|
BottomSheetMenu(
|
||||||
state = LocalMenuState.current,
|
state = LocalMenuState.current,
|
||||||
@@ -140,9 +157,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent?) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
uri = intent?.data
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
MediaController.releaseFuture(mediaControllerFuture)
|
MediaController.releaseFuture(mediaControllerFuture)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import it.vfsfitvnm.route.Route
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.route.rememberRoute
|
import it.vfsfitvnm.route.rememberRoute
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
@@ -39,23 +40,20 @@ import it.vfsfitvnm.vimusic.models.SearchQuery
|
|||||||
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||||
import it.vfsfitvnm.vimusic.ui.components.rememberBottomSheetState
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
||||||
import it.vfsfitvnm.vimusic.ui.views.PlayerView
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.PlaylistPreviewItem
|
import it.vfsfitvnm.vimusic.ui.views.PlaylistPreviewItem
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
||||||
import it.vfsfitvnm.vimusic.utils.*
|
import it.vfsfitvnm.vimusic.utils.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(
|
fun HomeScreen(
|
||||||
intentUri: Uri?
|
// uri: Uri?,
|
||||||
) {
|
) {
|
||||||
val colorPalette = LocalColorPalette.current
|
val colorPalette = LocalColorPalette.current
|
||||||
val typography = LocalTypography.current
|
val typography = LocalTypography.current
|
||||||
@@ -64,7 +62,7 @@ fun HomeScreen(
|
|||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
val intentUriRoute = rememberIntentUriRoute(intentUri)
|
val intentUriRoute = rememberIntentUriRoute()
|
||||||
val settingsRoute = rememberSettingsRoute()
|
val settingsRoute = rememberSettingsRoute()
|
||||||
val playlistRoute = rememberLocalPlaylistRoute()
|
val playlistRoute = rememberLocalPlaylistRoute()
|
||||||
val searchRoute = rememberSearchRoute()
|
val searchRoute = rememberSearchRoute()
|
||||||
@@ -72,7 +70,7 @@ fun HomeScreen(
|
|||||||
val albumRoute = rememberPlaylistOrAlbumRoute()
|
val albumRoute = rememberPlaylistOrAlbumRoute()
|
||||||
val artistRoute = rememberArtistRoute()
|
val artistRoute = rememberArtistRoute()
|
||||||
|
|
||||||
val (route, onRouteChanged) = rememberRoute(intentUri?.let { intentUriRoute })
|
// val (route, onRouteChanged) = rememberRoute(uri?.let { intentUriRoute })
|
||||||
|
|
||||||
val playlistPreviews by remember {
|
val playlistPreviews by remember {
|
||||||
Database.playlistPreviews()
|
Database.playlistPreviews()
|
||||||
@@ -88,224 +86,223 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
||||||
|
|
||||||
BoxWithConstraints(
|
RouteHandler(
|
||||||
modifier = Modifier
|
// route = route,
|
||||||
.fillMaxSize()
|
// onRouteChanged = onRouteChanged,
|
||||||
|
listenToGlobalEmitter = true
|
||||||
) {
|
) {
|
||||||
RouteHandler(
|
println("route: ${this.route?.tag}")
|
||||||
route = route,
|
|
||||||
onRouteChanged = onRouteChanged,
|
settingsRoute {
|
||||||
listenToGlobalEmitter = true
|
SettingsScreen()
|
||||||
) {
|
}
|
||||||
intentUriRoute { uri ->
|
|
||||||
IntentUriScreen(
|
playlistRoute { playlistId ->
|
||||||
uri = uri ?: error("uri must be not null")
|
LocalPlaylistScreen(
|
||||||
)
|
playlistId = playlistId ?: error("playlistId cannot be null")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResultRoute { query ->
|
||||||
|
SearchResultScreen(
|
||||||
|
query = query,
|
||||||
|
onSearchAgain = {
|
||||||
|
searchRoute(query)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchRoute { initialTextInput ->
|
||||||
|
SearchScreen(
|
||||||
|
initialTextInput = initialTextInput,
|
||||||
|
onSearch = { query ->
|
||||||
|
searchResultRoute(query)
|
||||||
|
|
||||||
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
Database.insert(SearchQuery(query = query))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUri = { uri ->
|
||||||
|
intentUriRoute(uri)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
albumRoute { browseId ->
|
||||||
|
PlaylistOrAlbumScreen(browseId = browseId ?: error("browseId cannot be null"))
|
||||||
|
}
|
||||||
|
|
||||||
|
artistRoute { browseId ->
|
||||||
|
ArtistScreen(
|
||||||
|
browseId = browseId ?: error("browseId cannot be null")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
intentUriRoute { uri ->
|
||||||
|
IntentUriScreen(
|
||||||
|
uri = uri ?: Uri.EMPTY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
host {
|
||||||
|
val player = LocalYoutubePlayer.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
val thumbnailSize = remember {
|
||||||
|
density.run {
|
||||||
|
54.dp.roundToPx()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsRoute {
|
var isCreatingANewPlaylist by rememberSaveable {
|
||||||
SettingsScreen()
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistRoute { playlistId ->
|
if (isCreatingANewPlaylist) {
|
||||||
LocalPlaylistScreen(
|
TextFieldDialog(
|
||||||
playlistId = playlistId ?: error("playlistId cannot be null")
|
hintText = "Enter the playlist name",
|
||||||
)
|
onDismiss = {
|
||||||
}
|
isCreatingANewPlaylist = false
|
||||||
|
|
||||||
searchResultRoute { query ->
|
|
||||||
SearchResultScreen(
|
|
||||||
query = query,
|
|
||||||
onSearchAgain = {
|
|
||||||
searchRoute(query)
|
|
||||||
},
|
},
|
||||||
)
|
onDone = { text ->
|
||||||
}
|
|
||||||
|
|
||||||
searchRoute { initialTextInput ->
|
|
||||||
SearchScreen(
|
|
||||||
initialTextInput = initialTextInput,
|
|
||||||
onSearch = { query ->
|
|
||||||
searchResultRoute(query)
|
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
Database.insert(SearchQuery(query = query))
|
Database.insert(Playlist(name = text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
albumRoute { browseId ->
|
LazyColumn(
|
||||||
PlaylistOrAlbumScreen(browseId = browseId ?: error("browseId cannot be null"))
|
state = lazyListState,
|
||||||
}
|
contentPadding = PaddingValues(bottom = 72.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
TopAppBar(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(52.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.cog),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
settingsRoute()
|
||||||
|
}
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
artistRoute { browseId ->
|
Image(
|
||||||
ArtistScreen(
|
painter = painterResource(R.drawable.search),
|
||||||
browseId = browseId ?: error("browseId cannot be null")
|
contentDescription = null,
|
||||||
)
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
}
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
host {
|
searchRoute("")
|
||||||
val player = LocalYoutubePlayer.current
|
}
|
||||||
val menuState = LocalMenuState.current
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
val density = LocalDensity.current
|
.size(24.dp)
|
||||||
|
)
|
||||||
val thumbnailSize = remember {
|
|
||||||
density.run {
|
|
||||||
54.dp.roundToPx()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isCreatingANewPlaylist by rememberSaveable {
|
item {
|
||||||
mutableStateOf(false)
|
BasicText(
|
||||||
}
|
text = "Your playlists",
|
||||||
|
style = typography.m.semiBold,
|
||||||
if (isCreatingANewPlaylist) {
|
modifier = Modifier
|
||||||
TextFieldDialog(
|
.padding(horizontal = 16.dp)
|
||||||
hintText = "Enter the playlist name",
|
|
||||||
onDismiss = {
|
|
||||||
isCreatingANewPlaylist = false
|
|
||||||
},
|
|
||||||
onDone = { text ->
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
|
||||||
Database.insert(Playlist(name = text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
item {
|
||||||
state = lazyListState,
|
LazyHorizontalGrid(
|
||||||
contentPadding = PaddingValues(bottom = 72.dp),
|
rows = GridCells.Fixed(2),
|
||||||
modifier = Modifier
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
.background(colorPalette.background)
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.height(248.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
TopAppBar(
|
Column(
|
||||||
modifier = Modifier
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.cog),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.padding(all = 8.dp)
|
||||||
settingsRoute()
|
.width(108.dp)
|
||||||
}
|
) {
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
Box(
|
||||||
.size(24.dp)
|
contentAlignment = Alignment.Center,
|
||||||
)
|
|
||||||
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.search),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
searchRoute("")
|
|
||||||
}
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
BasicText(
|
|
||||||
text = "Your playlists",
|
|
||||||
style = typography.m.semiBold,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
LazyHorizontalGrid(
|
|
||||||
rows = GridCells.Fixed(2),
|
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.height(248.dp)
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(all = 8.dp)
|
|
||||||
.width(108.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(
|
|
||||||
indication = rememberRipple(bounded = true),
|
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
|
||||||
) {
|
|
||||||
isCreatingANewPlaylist = true
|
|
||||||
}
|
|
||||||
.background(colorPalette.lightBackground)
|
|
||||||
.size(108.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.add),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items(
|
|
||||||
items = playlistPreviews,
|
|
||||||
contentType = { it }
|
|
||||||
) { playlistPreview ->
|
|
||||||
PlaylistPreviewItem(
|
|
||||||
playlistPreview = playlistPreview,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(all = 8.dp)
|
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rememberRipple(bounded = true),
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
playlistRoute(playlistPreview.playlist.id)
|
isCreatingANewPlaylist = true
|
||||||
}
|
}
|
||||||
)
|
.background(colorPalette.lightBackground)
|
||||||
|
.size(108.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.add),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!preferences.isReady) return@LazyColumn
|
items(
|
||||||
|
items = playlistPreviews,
|
||||||
item {
|
contentType = { it }
|
||||||
Row(
|
) { playlistPreview ->
|
||||||
verticalAlignment = Alignment.Bottom,
|
PlaylistPreviewItem(
|
||||||
modifier = Modifier
|
playlistPreview = playlistPreview,
|
||||||
.zIndex(1f)
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.padding(top = 32.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.padding(all = 8.dp)
|
||||||
.padding(horizontal = 8.dp)
|
.clickable(
|
||||||
) {
|
indication = rememberRipple(bounded = true),
|
||||||
BasicText(
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
text = when (preferences.homePageSongCollection) {
|
) {
|
||||||
SongCollection.MostPlayed -> "Most played"
|
playlistRoute(playlistPreview.playlist.id)
|
||||||
SongCollection.Favorites -> "Favorites"
|
}
|
||||||
SongCollection.History -> "History"
|
)
|
||||||
},
|
}
|
||||||
style = typography.m.semiBold,
|
}
|
||||||
modifier = Modifier
|
}
|
||||||
.animateContentSize()
|
|
||||||
)
|
|
||||||
|
|
||||||
val songCollections = enumValues<SongCollection>()
|
item {
|
||||||
val nextSongCollection = songCollections[(preferences.homePageSongCollection.ordinal + 1) % songCollections.size]
|
Row(
|
||||||
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(1f)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.padding(top = 32.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = when (preferences.homePageSongCollection) {
|
||||||
|
SongCollection.MostPlayed -> "Most played"
|
||||||
|
SongCollection.Favorites -> "Favorites"
|
||||||
|
SongCollection.History -> "History"
|
||||||
|
},
|
||||||
|
style = typography.m.semiBold,
|
||||||
|
modifier = Modifier
|
||||||
|
.animateContentSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
val songCollections = enumValues<SongCollection>()
|
||||||
|
val nextSongCollection = songCollections[(preferences.homePageSongCollection.ordinal + 1) % songCollections.size]
|
||||||
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = when (nextSongCollection) {
|
text = when (nextSongCollection) {
|
||||||
@@ -321,131 +318,125 @@ fun HomeScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
preferences.homePageSongCollection = nextSongCollection
|
preferences.homePageSongCollection = nextSongCollection
|
||||||
}
|
}
|
||||||
|
)
|
||||||
// .alignByBaseline()
|
// .alignByBaseline()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.ellipsis_horizontal),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
menuState.display {
|
|
||||||
BasicMenu(onDismiss = menuState::hide) {
|
|
||||||
MenuEntry(
|
|
||||||
icon = R.drawable.play,
|
|
||||||
text = "Play",
|
|
||||||
enabled = songCollection.isNotEmpty(),
|
|
||||||
onClick = {
|
|
||||||
menuState.hide()
|
|
||||||
YoutubePlayer.Radio.reset()
|
|
||||||
player?.mediaController?.forcePlayFromBeginning(
|
|
||||||
songCollection
|
|
||||||
.map(SongWithInfo::asMediaItem)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
MenuEntry(
|
|
||||||
icon = R.drawable.shuffle,
|
|
||||||
text = "Shuffle",
|
|
||||||
enabled = songCollection.isNotEmpty(),
|
|
||||||
onClick = {
|
|
||||||
menuState.hide()
|
|
||||||
YoutubePlayer.Radio.reset()
|
|
||||||
player?.mediaController?.forcePlayFromBeginning(
|
|
||||||
songCollection
|
|
||||||
.shuffled()
|
|
||||||
.map(SongWithInfo::asMediaItem)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
MenuEntry(
|
|
||||||
icon = R.drawable.time,
|
|
||||||
text = "Enqueue",
|
|
||||||
enabled = songCollection.isNotEmpty() && player?.playbackState == Player.STATE_READY,
|
|
||||||
onClick = {
|
|
||||||
menuState.hide()
|
|
||||||
player?.mediaController?.enqueue(
|
|
||||||
songCollection.map(SongWithInfo::asMediaItem)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
|
||||||
.size(20.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
itemsIndexed(
|
Image(
|
||||||
items = songCollection,
|
painter = painterResource(R.drawable.ellipsis_horizontal),
|
||||||
key = { _, song ->
|
contentDescription = null,
|
||||||
song.song.id
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
},
|
modifier = Modifier
|
||||||
contentType = { _, song -> song }
|
.clickable {
|
||||||
) { index, song ->
|
menuState.display {
|
||||||
SongItem(
|
BasicMenu(onDismiss = menuState::hide) {
|
||||||
song = song,
|
MenuEntry(
|
||||||
thumbnailSize = thumbnailSize,
|
icon = R.drawable.play,
|
||||||
onClick = {
|
text = "Play",
|
||||||
YoutubePlayer.Radio.reset()
|
enabled = songCollection.isNotEmpty(),
|
||||||
player?.mediaController?.forcePlayAtIndex(
|
onClick = {
|
||||||
songCollection.map(SongWithInfo::asMediaItem),
|
menuState.hide()
|
||||||
index
|
YoutubePlayer.Radio.reset()
|
||||||
)
|
player?.mediaController?.forcePlayFromBeginning(
|
||||||
},
|
songCollection
|
||||||
menuContent = {
|
.map(SongWithInfo::asMediaItem)
|
||||||
when (preferences.homePageSongCollection) {
|
|
||||||
SongCollection.MostPlayed -> NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
|
||||||
SongCollection.Favorites -> InFavoritesMediaItemMenu(song = song)
|
|
||||||
SongCollection.History -> InHistoryMediaItemMenu(song = song)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onThumbnailContent = {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = preferences.homePageSongCollection == SongCollection.MostPlayed,
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = song.song.formattedTotalPlayTime,
|
|
||||||
style = typography.xxs.semiBold.center.color(Color.White),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
brush = Brush.verticalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color.Transparent,
|
|
||||||
Color.Black.copy(alpha = 0.75f)
|
|
||||||
)
|
)
|
||||||
),
|
}
|
||||||
shape = ThumbnailRoundness.shape
|
|
||||||
)
|
)
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
|
||||||
)
|
MenuEntry(
|
||||||
|
icon = R.drawable.shuffle,
|
||||||
|
text = "Shuffle",
|
||||||
|
enabled = songCollection.isNotEmpty(),
|
||||||
|
onClick = {
|
||||||
|
menuState.hide()
|
||||||
|
YoutubePlayer.Radio.reset()
|
||||||
|
player?.mediaController?.forcePlayFromBeginning(
|
||||||
|
songCollection
|
||||||
|
.shuffled()
|
||||||
|
.map(SongWithInfo::asMediaItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
MenuEntry(
|
||||||
|
icon = R.drawable.time,
|
||||||
|
text = "Enqueue",
|
||||||
|
enabled = songCollection.isNotEmpty() && player?.playbackState == Player.STATE_READY,
|
||||||
|
onClick = {
|
||||||
|
menuState.hide()
|
||||||
|
player?.mediaController?.enqueue(
|
||||||
|
songCollection.map(SongWithInfo::asMediaItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||||
|
.size(20.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itemsIndexed(
|
||||||
|
items = songCollection,
|
||||||
|
key = { _, song ->
|
||||||
|
song.song.id
|
||||||
|
},
|
||||||
|
contentType = { _, song -> song }
|
||||||
|
) { index, song ->
|
||||||
|
SongItem(
|
||||||
|
song = song,
|
||||||
|
thumbnailSize = thumbnailSize,
|
||||||
|
onClick = {
|
||||||
|
YoutubePlayer.Radio.reset()
|
||||||
|
player?.mediaController?.forcePlayAtIndex(
|
||||||
|
songCollection.map(SongWithInfo::asMediaItem),
|
||||||
|
index
|
||||||
|
)
|
||||||
|
},
|
||||||
|
menuContent = {
|
||||||
|
when (preferences.homePageSongCollection) {
|
||||||
|
SongCollection.MostPlayed -> NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
SongCollection.Favorites -> InFavoritesMediaItemMenu(song = song)
|
||||||
|
SongCollection.History -> InHistoryMediaItemMenu(song = song)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onThumbnailContent = {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = preferences.homePageSongCollection == SongCollection.MostPlayed,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = song.song.formattedTotalPlayTime,
|
||||||
|
style = typography.xxs.semiBold.center.color(Color.White),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
Color.Black.copy(alpha = 0.75f)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shape = ThumbnailRoundness.shape
|
||||||
|
)
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerView(
|
|
||||||
layoutState = rememberBottomSheetState(lowerBound = 64.dp, upperBound = maxHeight),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,12 +73,11 @@ fun IntentUriScreen(uri: Uri) {
|
|||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
|
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
|
||||||
|
|
||||||
|
var items by remember(uri) {
|
||||||
var items by remember {
|
|
||||||
mutableStateOf<Outcome<List<YouTube.Item.Song>>>(Outcome.Loading)
|
mutableStateOf<Outcome<List<YouTube.Item.Song>>>(Outcome.Loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
val onLoad = relaunchableEffect(Unit) {
|
val onLoad = relaunchableEffect(uri) {
|
||||||
items = withContext(Dispatchers.IO) {
|
items = withContext(Dispatchers.IO) {
|
||||||
uri.getQueryParameter("list")?.let { playlistId ->
|
uri.getQueryParameter("list")?.let { playlistId ->
|
||||||
YouTube.queue(playlistId).toNullable()?.map { songList ->
|
YouTube.queue(playlistId).toNullable()?.map { songList ->
|
||||||
@@ -90,7 +89,7 @@ fun IntentUriScreen(uri: Uri) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isImportingAsPlaylist by remember {
|
var isImportingAsPlaylist by remember(uri) {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +245,6 @@ fun IntentUriScreen(uri: Uri) {
|
|||||||
YouTube.Item.Song::asMediaItem
|
YouTube.Item.Song::asMediaItem
|
||||||
), index
|
), index
|
||||||
)
|
)
|
||||||
pop()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@@ -30,6 +28,7 @@ import androidx.compose.ui.text.input.ImeAction
|
|||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.net.toUri
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
@@ -47,11 +46,13 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchScreen(
|
fun SearchScreen(
|
||||||
initialTextInput: String,
|
initialTextInput: String,
|
||||||
onSearch: (String) -> Unit
|
onSearch: (String) -> Unit,
|
||||||
|
onUri: (Uri) -> Unit,
|
||||||
) {
|
) {
|
||||||
var textFieldValue by rememberSaveable(initialTextInput, stateSaver = TextFieldValue.Saver) {
|
var textFieldValue by rememberSaveable(initialTextInput, stateSaver = TextFieldValue.Saver) {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -107,6 +108,10 @@ fun SearchScreen(
|
|||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val isOpenableUrl = remember(textFieldValue.text) {
|
||||||
|
Regex("""https://(music|www)\.youtube.com/(watch|playlist).*""").matches(textFieldValue.text)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
delay(300)
|
delay(300)
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
@@ -184,6 +189,40 @@ fun SearchScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOpenableUrl) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
indication = rememberRipple(bounded = true),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = {
|
||||||
|
onUri(textFieldValue.text.toUri())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(colorPalette.lightBackground)
|
||||||
|
.padding(vertical = 16.dp, horizontal = 8.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.link),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.darkGray),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = "Open URL",
|
||||||
|
style = typography.s.secondary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import it.vfsfitvnm.route.Route0
|
|||||||
import it.vfsfitvnm.route.Route1
|
import it.vfsfitvnm.route.Route1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberIntentUriRoute(intentUri: Uri?): Route1<Uri?> {
|
fun rememberIntentUriRoute(): Route1<Uri?> {
|
||||||
val uri = rememberSaveable {
|
val uri = rememberSaveable {
|
||||||
mutableStateOf(intentUri)
|
mutableStateOf<Uri?>(null)
|
||||||
}
|
}
|
||||||
return remember {
|
return remember {
|
||||||
Route1("rememberIntentUriRoute", uri)
|
Route1("IntentUriRoute", uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
app/src/main/res/drawable/link.xml
Normal file
27
app/src/main/res/drawable/link.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="512"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:pathData="M200.66,352H144a96,96 0,0 1,0 -192h55.41"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="48"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M312.59,160H368a96,96 0,0 1,0 192H311.34"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="48"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M169.07,256L344.93,256"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="48"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user