Improve IntentUriScreen and handle links in SearchScreen
This commit is contained in:
@@ -21,15 +21,17 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.ViMusic.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<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.BROWSABLE" />
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package it.vfsfitvnm.vimusic
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
@@ -15,7 +16,7 @@ import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.LocalOverScrollConfiguration
|
||||
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.material.ripple.LocalRippleTheme
|
||||
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.platform.LocalHapticFeedback
|
||||
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.SessionToken
|
||||
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.ui.components.BottomSheetMenu
|
||||
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.screens.HomeScreen
|
||||
import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen
|
||||
import it.vfsfitvnm.vimusic.ui.styling.*
|
||||
import it.vfsfitvnm.vimusic.ui.views.PlayerView
|
||||
import it.vfsfitvnm.vimusic.utils.*
|
||||
|
||||
private val Context.dataStore by preferencesDataStore(name = "preferences")
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@ExperimentalFoundationApi
|
||||
@@ -51,12 +54,16 @@ private val Context.dataStore by preferencesDataStore(name = "preferences")
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var mediaControllerFuture: ListenableFuture<MediaController>
|
||||
|
||||
private var uri by mutableStateOf<Uri?>(null)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val sessionToken = SessionToken(this, ComponentName(this, PlayerService::class.java))
|
||||
mediaControllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
|
||||
|
||||
uri = intent?.data
|
||||
|
||||
setContent {
|
||||
val preferences = rememberPreferences()
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@@ -123,12 +130,22 @@ class MainActivity : ComponentActivity() {
|
||||
LocalMenuState provides rememberMenuState(),
|
||||
LocalHapticFeedback provides rememberHapticFeedback()
|
||||
) {
|
||||
Box(
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.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(
|
||||
state = LocalMenuState.current,
|
||||
@@ -140,9 +157,13 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
uri = intent?.data
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
MediaController.releaseFuture(mediaControllerFuture)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.media3.common.Player
|
||||
import it.vfsfitvnm.route.Route
|
||||
import it.vfsfitvnm.route.RouteHandler
|
||||
import it.vfsfitvnm.route.rememberRoute
|
||||
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.ui.components.LocalMenuState
|
||||
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.styling.LocalColorPalette
|
||||
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.SongItem
|
||||
import it.vfsfitvnm.vimusic.utils.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
intentUri: Uri?
|
||||
// uri: Uri?,
|
||||
) {
|
||||
val colorPalette = LocalColorPalette.current
|
||||
val typography = LocalTypography.current
|
||||
@@ -64,7 +62,7 @@ fun HomeScreen(
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
val intentUriRoute = rememberIntentUriRoute(intentUri)
|
||||
val intentUriRoute = rememberIntentUriRoute()
|
||||
val settingsRoute = rememberSettingsRoute()
|
||||
val playlistRoute = rememberLocalPlaylistRoute()
|
||||
val searchRoute = rememberSearchRoute()
|
||||
@@ -72,7 +70,7 @@ fun HomeScreen(
|
||||
val albumRoute = rememberPlaylistOrAlbumRoute()
|
||||
val artistRoute = rememberArtistRoute()
|
||||
|
||||
val (route, onRouteChanged) = rememberRoute(intentUri?.let { intentUriRoute })
|
||||
// val (route, onRouteChanged) = rememberRoute(uri?.let { intentUriRoute })
|
||||
|
||||
val playlistPreviews by remember {
|
||||
Database.playlistPreviews()
|
||||
@@ -88,20 +86,12 @@ fun HomeScreen(
|
||||
}
|
||||
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
RouteHandler(
|
||||
route = route,
|
||||
onRouteChanged = onRouteChanged,
|
||||
// route = route,
|
||||
// onRouteChanged = onRouteChanged,
|
||||
listenToGlobalEmitter = true
|
||||
) {
|
||||
intentUriRoute { uri ->
|
||||
IntentUriScreen(
|
||||
uri = uri ?: error("uri must be not null")
|
||||
)
|
||||
}
|
||||
println("route: ${this.route?.tag}")
|
||||
|
||||
settingsRoute {
|
||||
SettingsScreen()
|
||||
@@ -118,7 +108,7 @@ fun HomeScreen(
|
||||
query = query,
|
||||
onSearchAgain = {
|
||||
searchRoute(query)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,6 +121,9 @@ fun HomeScreen(
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
Database.insert(SearchQuery(query = query))
|
||||
}
|
||||
},
|
||||
onUri = { uri ->
|
||||
intentUriRoute(uri)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -145,6 +138,12 @@ fun HomeScreen(
|
||||
)
|
||||
}
|
||||
|
||||
intentUriRoute { uri ->
|
||||
IntentUriScreen(
|
||||
uri = uri ?: Uri.EMPTY
|
||||
)
|
||||
}
|
||||
|
||||
host {
|
||||
val player = LocalYoutubePlayer.current
|
||||
val menuState = LocalMenuState.current
|
||||
@@ -277,8 +276,6 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (!preferences.isReady) return@LazyColumn
|
||||
|
||||
item {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
@@ -321,6 +318,7 @@ fun HomeScreen(
|
||||
onClick = {
|
||||
preferences.homePageSongCollection = nextSongCollection
|
||||
}
|
||||
)
|
||||
// .alignByBaseline()
|
||||
.padding(horizontal = 16.dp)
|
||||
.animateContentSize()
|
||||
@@ -441,11 +439,4 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
|
||||
|
||||
|
||||
var items by remember {
|
||||
var items by remember(uri) {
|
||||
mutableStateOf<Outcome<List<YouTube.Item.Song>>>(Outcome.Loading)
|
||||
}
|
||||
|
||||
val onLoad = relaunchableEffect(Unit) {
|
||||
val onLoad = relaunchableEffect(uri) {
|
||||
items = withContext(Dispatchers.IO) {
|
||||
uri.getQueryParameter("list")?.let { playlistId ->
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -246,7 +245,6 @@ fun IntentUriScreen(uri: Uri) {
|
||||
YouTube.Item.Song::asMediaItem
|
||||
), index
|
||||
)
|
||||
pop()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package it.vfsfitvnm.vimusic.ui.screens
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
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.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import it.vfsfitvnm.route.RouteHandler
|
||||
import it.vfsfitvnm.vimusic.Database
|
||||
import it.vfsfitvnm.vimusic.R
|
||||
@@ -47,11 +46,13 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@Composable
|
||||
fun SearchScreen(
|
||||
initialTextInput: String,
|
||||
onSearch: (String) -> Unit
|
||||
onSearch: (String) -> Unit,
|
||||
onUri: (Uri) -> Unit,
|
||||
) {
|
||||
var textFieldValue by rememberSaveable(initialTextInput, stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(
|
||||
@@ -107,6 +108,10 @@ fun SearchScreen(
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val isOpenableUrl = remember(textFieldValue.text) {
|
||||
Regex("""https://(music|www)\.youtube.com/(watch|playlist).*""").matches(textFieldValue.text)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(300)
|
||||
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(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
|
||||
@@ -9,12 +9,12 @@ import it.vfsfitvnm.route.Route0
|
||||
import it.vfsfitvnm.route.Route1
|
||||
|
||||
@Composable
|
||||
fun rememberIntentUriRoute(intentUri: Uri?): Route1<Uri?> {
|
||||
fun rememberIntentUriRoute(): Route1<Uri?> {
|
||||
val uri = rememberSaveable {
|
||||
mutableStateOf(intentUri)
|
||||
mutableStateOf<Uri?>(null)
|
||||
}
|
||||
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