diff --git a/README.md b/README.md index 89bbf52..01061a1 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ https://www.youtube.com/watch?v=IAExKH6L5P4 - Adds songs to favorite - Check your recent played - Check your most listened songs +- Set close timer for the app - Different themes - Shuffle, repeat one/all songs - Notification manager displayed diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 5c46623..44d3a36 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -12,20 +12,24 @@ dependencies { implementation("androidx.appcompat:appcompat:1.4.2") implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc02") implementation("androidx.activity:activity-compose:1.4.0") implementation("androidx.palette:palette-ktx:1.0.0") - implementation("androidx.compose.ui:ui-util:1.2.0-rc01") - implementation("androidx.compose.material:material:1.2.0-rc01") - implementation("androidx.compose.ui:ui:1.2.0-rc01") - implementation("androidx.compose.animation:animation:1.2.0-rc01") - debugImplementation("androidx.compose.ui:ui-tooling:1.2.0-rc01") - implementation("androidx.compose.ui:ui-tooling-preview:1.2.0-rc01") + implementation("androidx.compose.ui:ui-util:1.2.0-rc02") + implementation("androidx.compose.material:material:1.2.0-rc02") + implementation("androidx.compose.ui:ui:1.2.0-rc02") + implementation("androidx.compose.animation:animation:1.2.0-rc02") + debugImplementation("androidx.compose.ui:ui-tooling:1.2.0-rc02") + implementation("androidx.compose.ui:ui-tooling-preview:1.2.0-rc02") + + implementation ("com.google.android.exoplayer:exoplayer-core:2.18.0") + implementation ("com.google.android.exoplayer:extension-mediasession:2.18.0") + implementation ("com.google.android.exoplayer:exoplayer-ui:2.18.0") + + implementation ("dev.chrisbanes.snapper:snapper:0.2.2") + implementation ("androidx.work:work-runtime-ktx:2.7.1") - implementation ("com.google.android.exoplayer:exoplayer-core:2.17.1") - implementation ("com.google.android.exoplayer:extension-mediasession:2.17.1") - implementation ("com.google.android.exoplayer:exoplayer-ui:2.17.1") implementation("androidx.lifecycle:lifecycle-process:2.5.0-rc02") implementation("io.coil-kt:coil-compose:2.1.0") @@ -47,8 +51,8 @@ android { applicationId = "com.rld.justlisten.android" minSdk = 21 targetSdk = 32 - versionCode = 17 - versionName = "1.0.5" + versionCode = 18 + versionName = "1.0.6" vectorDrawables { useSupportLibrary = true } diff --git a/androidApp/src/main/java/com/rld/justlisten/android/MainActivity.kt b/androidApp/src/main/java/com/rld/justlisten/android/MainActivity.kt index 90f206e..4e4e251 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/MainActivity.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/MainActivity.kt @@ -8,6 +8,7 @@ import androidx.compose.material.MaterialTheme import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.toArgb import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.work.WorkManager import com.rld.justlisten.android.ui.MainComposable import com.rld.justlisten.android.ui.theme.ColorPallet import com.rld.justlisten.android.ui.theme.JustListenTheme @@ -23,8 +24,10 @@ class MainActivity : ComponentActivity() { val model = (application as JustListenApp).model val musicServiceConnection = (application as JustListenApp).musicServiceConnection installSplashScreen().apply { - } + val workManager = WorkManager.getInstance(applicationContext) + workManager.cancelUniqueWork("SleepWorker") + val settingsInfo = mutableStateOf(model.repository.getSettingsInfo()) setContent { JustListenTheme( diff --git a/androidApp/src/main/java/com/rld/justlisten/android/exoplayer/MusicSource.kt b/androidApp/src/main/java/com/rld/justlisten/android/exoplayer/MusicSource.kt index 879044e..aa0b390 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/exoplayer/MusicSource.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/exoplayer/MusicSource.kt @@ -35,7 +35,7 @@ class MusicSource { } private fun MediaMetadataCompat.Builder.from(song: Item): MediaMetadataCompat.Builder { - artist = song.title + artist = song.user id = song.id title = song.title displayIconUri = song.songIconList.songImageURL480px diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/bottombarnav/Level1BottomBar.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/bottombarnav/Level1BottomBar.kt index 41e344e..0177e79 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/bottombarnav/Level1BottomBar.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/bottombarnav/Level1BottomBar.kt @@ -56,7 +56,7 @@ fun Navigation.Level1BottomBar( BottomNavigationItem( icon = { if (donateSelected)Icon(painter = painterResource(id = R.drawable.ic_baseline_monetization_on_24), "Donate") else Icon(painter = painterResource(id = R.drawable.ic_outline_monetization_on_24), "Donate")}, - label = { Text("Donate", fontSize = 10.sp) }, + label = { Text("Support", fontSize = 10.sp) }, selected = selectedTab.URI == Level1Navigation.Donation.screenIdentifier.URI, onClick = { navigateByLevel1Menu(Level1Navigation.Donation) }, selectedContentColor = MaterialTheme.colors.primaryVariant, diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/libraryscreen/LibraryScreen.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/libraryscreen/LibraryScreen.kt index 218200e..0a14aae 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/libraryscreen/LibraryScreen.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/libraryscreen/LibraryScreen.kt @@ -10,7 +10,7 @@ import com.rld.justlisten.android.ui.libraryscreen.components.FavoritePlaylist import com.rld.justlisten.android.ui.libraryscreen.components.MostPlayedSongs import com.rld.justlisten.android.ui.libraryscreen.components.PlaylistView import com.rld.justlisten.android.ui.libraryscreen.components.RowListOfRecentActivity -import com.rld.justlisten.android.ui.playlistscreen.Header +import com.rld.justlisten.android.ui.playlistscreen.components.Header import com.rld.justlisten.android.ui.utils.playMusicFromId import com.rld.justlisten.datalayer.models.PlayListModel import com.rld.justlisten.datalayer.models.SongIconList @@ -25,7 +25,8 @@ fun LibraryScreen( onFavoritePlaylistPressed: (String, String, String, String) -> Unit, onMostPlaylistPressed: (String, String, String, String) -> Unit, onPlayListViewClicked: () -> Unit, - lasItemReached: (Int) -> Unit + lasItemReached: (Int) -> Unit, + isPlayerReady: Boolean ) { Box(modifier = Modifier .fillMaxSize() @@ -45,7 +46,7 @@ fun LibraryScreen( false ) val item = TrackItem(playlistModel, isFavorite) - playMusicFromId(musicServiceConnection, listOf(item), id, false) + playMusicFromId(musicServiceConnection, listOf(item), id, isPlayerReady) } }, lasItemReached = lasItemReached, diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/PlaylistScreen.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/PlaylistScreen.kt index 22ae3aa..32d3640 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/PlaylistScreen.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/PlaylistScreen.kt @@ -1,37 +1,22 @@ package com.rld.justlisten.android.ui.playlistscreen -import androidx.compose.foundation.ScrollState -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Search -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEachIndexed import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState -import com.rld.justlisten.android.ui.extensions.customTabIndicatorOffset import com.rld.justlisten.android.ui.loadingscreen.LoadingScreen -import com.rld.justlisten.android.ui.playlistscreen.components.PlaylistRowItem -import com.rld.justlisten.android.ui.searchscreen.components.SearchGridTracks -import com.rld.justlisten.android.ui.theme.modifiers.horizontalGradientBackground -import com.rld.justlisten.android.ui.theme.typography +import com.rld.justlisten.android.ui.playlistscreen.components.AnimatedToolBar +import com.rld.justlisten.android.ui.playlistscreen.components.ScrollableContent import com.rld.justlisten.datalayer.models.SongIconList import com.rld.justlisten.datalayer.utils.Constants.list import com.rld.justlisten.viewmodel.Events -import com.rld.justlisten.viewmodel.screens.playlist.* -import java.util.* +import com.rld.justlisten.viewmodel.screens.playlist.PlayListEnum +import com.rld.justlisten.viewmodel.screens.playlist.PlaylistState +import com.rld.justlisten.viewmodel.screens.playlist.fetchPlaylist +import com.rld.justlisten.viewmodel.screens.playlist.getNewTracks @Composable fun PlaylistScreen( @@ -81,259 +66,6 @@ fun PlaylistScreen( } ) AnimatedToolBar(onSearchClicked) - - - } - } - } -} - -@Composable -fun AnimatedToolBar( - onSearchClicked: () -> Unit -) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .horizontalGradientBackground( - listOf(MaterialTheme.colors.background, MaterialTheme.colors.background) - ) - .padding(horizontal = 8.dp, vertical = 4.dp) - ) { - - val rightNow = Calendar.getInstance() - - val text = when (rightNow.get(Calendar.HOUR_OF_DAY)) { - in 0..5 -> "Chilling" - in 5..11 -> "Good Morning" - in 12..17 -> "Hey there" - in 17..23 -> "Good Evening" - else -> "Hello" - } - Header(text = text) - Icon( - modifier = Modifier.clickable(onClick = onSearchClicked), - imageVector = Icons.Default.Search, - contentDescription = null - ) - } -} - -@Composable -fun ScrollableContent( - lasItemReached: (Int, PlayListEnum) -> Unit, - scrollState: ScrollState, - playlistState: PlaylistState, - onPlaylistClicked: (String, String, String, String, Boolean) -> Unit, - onSongPressed: (String, String, String, SongIconList) -> Unit, - getNewTracks: (TracksCategory, TimeRange) -> Unit -) { - Column( - modifier = Modifier - .padding(8.dp) - .verticalScroll(scrollState) - ) - { - Spacer(modifier = Modifier.height(50.dp)) - ListOfCollections( - playlistState = playlistState, lasItemReached = lasItemReached, - onPlaylistClicked = onPlaylistClicked - ) - Spacer(modifier = Modifier.height(25.dp)) - - - val density = LocalDensity.current - val list = getTrackCategory() - val timeRangeList = getTimeRange() - - val tabWidths = remember { - val tabWidthStateList = mutableStateListOf() - repeat(list.size) { - tabWidthStateList.add(0.dp) - } - tabWidthStateList - } - - val tabWidthsTimeRange = remember { - val tabWidthStateList = mutableStateListOf() - repeat(timeRangeList.size) { - tabWidthStateList.add(0.dp) - } - tabWidthStateList - } - - - var selectedTab by remember { mutableStateOf(0) } - var selectedTabTimeRange by remember { mutableStateOf(0) } - - ScrollableTabRow( - selectedTabIndex = selectedTab, - backgroundColor = Color.Transparent, - modifier = Modifier.padding(8.dp), - edgePadding = 0.dp, - indicator = { tabPositions -> - TabRowDefaults.Indicator( - modifier = Modifier.customTabIndicatorOffset( - currentTabPosition = tabPositions[selectedTab], - tabWidth = tabWidths[selectedTab] - ) - ) - } - ) { - list.fastForEachIndexed { index, item -> - Tab( - modifier = Modifier.padding(bottom = 10.dp), - selected = index == selectedTab, - onClick = { - selectedTab = index - getNewTracks(item, timeRangeList[selectedTabTimeRange]) - } - ) - { - Text( - item.value, - onTextLayout = { textLayoutResult -> - tabWidths[selectedTab] = - with(density) { textLayoutResult.size.width.toDp() } - } - ) - } - } - } - Column( - Modifier.padding(5.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - ScrollableTabRow(selectedTabIndex = selectedTabTimeRange, - backgroundColor = Color.Transparent, - indicator = { tabPositions -> - TabRowDefaults.Indicator( - modifier = Modifier.customTabIndicatorOffset( - currentTabPosition = tabPositions[selectedTabTimeRange], - tabWidth = tabWidthsTimeRange[selectedTabTimeRange] - ) - ) - }) { - timeRangeList.fastForEachIndexed { index, item -> - Tab( - modifier = Modifier.padding(bottom = 10.dp), - selected = index == selectedTabTimeRange, - onClick = { - selectedTabTimeRange = index - getNewTracks(list[selectedTab], item) - } - ) - { - Text( - item.value, - onTextLayout = { textLayoutResult -> - tabWidthsTimeRange[selectedTabTimeRange] = - with(density) { textLayoutResult.size.width.toDp() } - } - ) - } - } - } - } - - Column( - modifier = if (playlistState.tracksLoading) Modifier - .height(500.dp) - .fillMaxWidth() else Modifier, - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - if (playlistState.tracksLoading) { - CircularProgressIndicator() - } else { - SearchGridTracks(list = playlistState.tracksList, onSongPressed = onSongPressed) - } - } - } -} - -@Composable -fun Header(text: String, modifier: Modifier = Modifier) { - Text( - text = text, - style = typography.h5.copy(fontWeight = FontWeight.ExtraBold), - modifier = modifier.padding(start = 8.dp, end = 4.dp, bottom = 8.dp, top = 24.dp) - ) -} - -@Composable -fun ListOfCollections( - playlistState: PlaylistState, lasItemReached: (Int, PlayListEnum) -> Unit, - onPlaylistClicked: (String, String, String, String, Boolean) -> Unit -) { - val list = remember { - mutableListOf( - "Top Playlist", - list[playlistState.queryIndex], - list[playlistState.queryIndex2] - ) - } - list.fastForEachIndexed { index, item -> - Header(text = item) - when (index) { - 0 -> PlaylistRow( - playlist = playlistState.playlistItems, - lasItemReached = lasItemReached, - PlayListEnum.TOP_PLAYLIST, - onPlaylistClicked, - playlistState.lastFetchPlaylist - ) - 1 -> PlaylistRow( - playlist = playlistState.remixPlaylist, - lasItemReached = lasItemReached, - PlayListEnum.REMIX, - onPlaylistClicked, - playlistState.lastFetchRemix - ) - 2 -> PlaylistRow( - playlist = playlistState.hotPlaylist, - lasItemReached = lasItemReached, - PlayListEnum.HOT, - onPlaylistClicked, - playlistState.lastFetchHot - ) - } - } -} - -@Composable -fun PlaylistRow( - playlist: List, lasItemReached: (Int, PlayListEnum) -> Unit, - playlistEnum: PlayListEnum, - onPlaylistClicked: (String, String, String, String, Boolean) -> Unit, - lastIndexReached: Boolean = false -) { - val fetchMore = remember { mutableStateOf(false) } - LazyRow(verticalAlignment = Alignment.CenterVertically) { - itemsIndexed(items = playlist) { index, playlistItem -> - - if (index == playlist.lastIndex && !lastIndexReached) { - LaunchedEffect(key1 = playlist.lastIndex) - { - lasItemReached(index + 20, playlistEnum) - fetchMore.value = true - } - } - - if (lastIndexReached) { - fetchMore.value = false - } - - PlaylistRowItem( - playlistItem = playlistItem, - onPlaylistClicked = onPlaylistClicked - ) - } - if (fetchMore.value) { - item { - CircularProgressIndicator() } } } diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/AnimatedToolBar.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/AnimatedToolBar.kt new file mode 100644 index 0000000..ec5325a --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/AnimatedToolBar.kt @@ -0,0 +1,50 @@ +package com.rld.justlisten.android.ui.playlistscreen.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.rld.justlisten.android.ui.theme.modifiers.horizontalGradientBackground +import java.util.* + +@Composable +fun AnimatedToolBar( + onSearchClicked: () -> Unit +) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .horizontalGradientBackground( + listOf(MaterialTheme.colors.background, MaterialTheme.colors.background) + ) + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + + val rightNow = Calendar.getInstance() + + val text = when (rightNow.get(Calendar.HOUR_OF_DAY)) { + in 0..5 -> "Chilling" + in 5..11 -> "Good Morning" + in 12..17 -> "Hey there" + in 17..23 -> "Good Evening" + else -> "Hello" + } + Header(text = text) + Icon( + modifier = Modifier.clickable(onClick = onSearchClicked), + imageVector = Icons.Default.Search, + contentDescription = null + ) + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/Header.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/Header.kt new file mode 100644 index 0000000..0bce557 --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/Header.kt @@ -0,0 +1,18 @@ +package com.rld.justlisten.android.ui.playlistscreen.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.rld.justlisten.android.ui.theme.typography + +@Composable +fun Header(text: String, modifier: Modifier = Modifier) { + Text( + text = text, + style = typography.h5.copy(fontWeight = FontWeight.ExtraBold), + modifier = modifier.padding(start = 8.dp, end = 4.dp, bottom = 8.dp, top = 24.dp) + ) +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/ListOfCollections.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/ListOfCollections.kt new file mode 100644 index 0000000..7e60bb7 --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/ListOfCollections.kt @@ -0,0 +1,48 @@ +package com.rld.justlisten.android.ui.playlistscreen.components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.util.fastForEachIndexed +import com.rld.justlisten.datalayer.utils.Constants +import com.rld.justlisten.viewmodel.screens.playlist.PlayListEnum +import com.rld.justlisten.viewmodel.screens.playlist.PlaylistState + +@Composable +fun ListOfCollections( + playlistState: PlaylistState, lasItemReached: (Int, PlayListEnum) -> Unit, + onPlaylistClicked: (String, String, String, String, Boolean) -> Unit +) { + val list = remember { + mutableListOf( + "Top Playlist", + Constants.list[playlistState.queryIndex], + Constants.list[playlistState.queryIndex2] + ) + } + list.fastForEachIndexed { index, item -> + Header(text = item) + when (index) { + 0 -> PlaylistRow( + playlist = playlistState.playlistItems, + lasItemReached = lasItemReached, + PlayListEnum.TOP_PLAYLIST, + onPlaylistClicked, + playlistState.lastFetchPlaylist + ) + 1 -> PlaylistRow( + playlist = playlistState.remixPlaylist, + lasItemReached = lasItemReached, + PlayListEnum.REMIX, + onPlaylistClicked, + playlistState.lastFetchRemix + ) + 2 -> PlaylistRow( + playlist = playlistState.hotPlaylist, + lasItemReached = lasItemReached, + PlayListEnum.HOT, + onPlaylistClicked, + playlistState.lastFetchHot + ) + } + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/PlaylistRow.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/PlaylistRow.kt new file mode 100644 index 0000000..08ebda4 --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/PlaylistRow.kt @@ -0,0 +1,48 @@ +package com.rld.justlisten.android.ui.playlistscreen.components + +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import com.rld.justlisten.viewmodel.screens.playlist.PlayListEnum +import com.rld.justlisten.viewmodel.screens.playlist.PlaylistItem + +@Composable +fun PlaylistRow( + playlist: List, lasItemReached: (Int, PlayListEnum) -> Unit, + playlistEnum: PlayListEnum, + onPlaylistClicked: (String, String, String, String, Boolean) -> Unit, + lastIndexReached: Boolean = false +) { + val fetchMore = remember { mutableStateOf(false) } + LazyRow(verticalAlignment = Alignment.CenterVertically) { + itemsIndexed(items = playlist) { index, playlistItem -> + + if (index == playlist.lastIndex && !lastIndexReached) { + LaunchedEffect(key1 = playlist.lastIndex) + { + lasItemReached(index + 20, playlistEnum) + fetchMore.value = true + } + } + + if (lastIndexReached) { + fetchMore.value = false + } + + PlaylistRowItem( + playlistItem = playlistItem, + onPlaylistClicked = onPlaylistClicked + ) + } + if (fetchMore.value) { + item { + CircularProgressIndicator() + } + } + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/ScrollableContent.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/ScrollableContent.kt new file mode 100644 index 0000000..d218c20 --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/playlistscreen/components/ScrollableContent.kt @@ -0,0 +1,151 @@ +package com.rld.justlisten.android.ui.playlistscreen.components + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEachIndexed +import com.rld.justlisten.android.ui.extensions.customTabIndicatorOffset +import com.rld.justlisten.android.ui.searchscreen.components.SearchGridTracks +import com.rld.justlisten.datalayer.models.SongIconList +import com.rld.justlisten.viewmodel.screens.playlist.* + +@Composable +fun ScrollableContent( + lasItemReached: (Int, PlayListEnum) -> Unit, + scrollState: ScrollState, + playlistState: PlaylistState, + onPlaylistClicked: (String, String, String, String, Boolean) -> Unit, + onSongPressed: (String, String, String, SongIconList) -> Unit, + getNewTracks: (TracksCategory, TimeRange) -> Unit +) { + Column( + modifier = Modifier + .padding(8.dp) + .verticalScroll(scrollState) + ) + { + Spacer(modifier = Modifier.height(50.dp)) + ListOfCollections( + playlistState = playlistState, lasItemReached = lasItemReached, + onPlaylistClicked = onPlaylistClicked + ) + Spacer(modifier = Modifier.height(25.dp)) + + + val density = LocalDensity.current + val list = getTrackCategory() + val timeRangeList = getTimeRange() + + val tabWidths = remember { + val tabWidthStateList = mutableStateListOf() + repeat(list.size) { + tabWidthStateList.add(0.dp) + } + tabWidthStateList + } + + val tabWidthsTimeRange = remember { + val tabWidthStateList = mutableStateListOf() + repeat(timeRangeList.size) { + tabWidthStateList.add(0.dp) + } + tabWidthStateList + } + + + var selectedTab by remember { mutableStateOf(0) } + var selectedTabTimeRange by remember { mutableStateOf(0) } + + ScrollableTabRow( + selectedTabIndex = selectedTab, + backgroundColor = Color.Transparent, + modifier = Modifier.padding(8.dp), + edgePadding = 0.dp, + indicator = { tabPositions -> + TabRowDefaults.Indicator( + modifier = Modifier.customTabIndicatorOffset( + currentTabPosition = tabPositions[selectedTab], + tabWidth = tabWidths[selectedTab] + ) + ) + } + ) { + list.fastForEachIndexed { index, item -> + Tab( + modifier = Modifier.padding(bottom = 10.dp), + selected = index == selectedTab, + onClick = { + selectedTab = index + getNewTracks(item, timeRangeList[selectedTabTimeRange]) + } + ) + { + Text( + item.value, + onTextLayout = { textLayoutResult -> + tabWidths[selectedTab] = + with(density) { textLayoutResult.size.width.toDp() } + } + ) + } + } + } + Column( + Modifier.padding(5.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + ScrollableTabRow(selectedTabIndex = selectedTabTimeRange, + backgroundColor = Color.Transparent, + indicator = { tabPositions -> + TabRowDefaults.Indicator( + modifier = Modifier.customTabIndicatorOffset( + currentTabPosition = tabPositions[selectedTabTimeRange], + tabWidth = tabWidthsTimeRange[selectedTabTimeRange] + ) + ) + }) { + timeRangeList.fastForEachIndexed { index, item -> + Tab( + modifier = Modifier.padding(bottom = 10.dp), + selected = index == selectedTabTimeRange, + onClick = { + selectedTabTimeRange = index + getNewTracks(list[selectedTab], item) + } + ) + { + Text( + item.value, + onTextLayout = { textLayoutResult -> + tabWidthsTimeRange[selectedTabTimeRange] = + with(density) { textLayoutResult.size.width.toDp() } + } + ) + } + } + } + } + + Column( + modifier = if (playlistState.tracksLoading) Modifier + .height(500.dp) + .fillMaxWidth() else Modifier, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (playlistState.tracksLoading) { + CircularProgressIndicator() + } else { + SearchGridTracks(list = playlistState.tracksList, onSongPressed = onSongPressed) + } + } + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/screenpicker/ScreenPicker.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/screenpicker/ScreenPicker.kt index fc31d93..3b05644 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/screenpicker/ScreenPicker.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/screenpicker/ScreenPicker.kt @@ -7,6 +7,7 @@ import com.rld.justlisten.Navigation import com.rld.justlisten.ScreenIdentifier import com.rld.justlisten.android.exoplayer.MusicService import com.rld.justlisten.android.exoplayer.MusicServiceConnection +import com.rld.justlisten.android.exoplayer.library.extension.artist import com.rld.justlisten.android.exoplayer.library.extension.displayIconUri import com.rld.justlisten.android.exoplayer.library.extension.id import com.rld.justlisten.android.exoplayer.library.extension.title @@ -58,7 +59,8 @@ fun Navigation.ScreenPicker( LaunchedEffect(musicServiceConnection.currentPlayingSong.value?.id) { val title = musicServiceConnection.currentPlayingSong.value?.title ?: "title" val newId = musicServiceConnection.currentPlayingSong.value?.id ?: "id" - val user = UserModel("asd") + val user = musicServiceConnection.currentPlayingSong.value?.artist?.let { UserModel(it) } + ?: UserModel("glitch") val songIcon = musicServiceConnection.currentPlayingSong.value?.displayIconUri.toString() val icon = SongIconList( @@ -74,7 +76,8 @@ fun Navigation.ScreenPicker( LaunchedEffect(key1 = MusicService.songHasRepeated) { val title = musicServiceConnection.currentPlayingSong.value?.title ?: "title" val newId = musicServiceConnection.currentPlayingSong.value?.id ?: "id" - val user = UserModel("asd") + val user = musicServiceConnection.currentPlayingSong.value?.artist?.let { UserModel(it) } + ?: UserModel("glitch") val songIcon = musicServiceConnection.currentPlayingSong.value?.displayIconUri.toString() val icon = SongIconList( @@ -91,6 +94,7 @@ fun Navigation.ScreenPicker( Library -> LibraryScreen( + isPlayerReady = isPlayerReady.value, musicServiceConnection = musicServiceConnection, libraryState = stateProvider.get(screenIdentifier), onFavoritePlaylistPressed = { playlistId, playlistIcon, playlistTitle, playlistCreatedBy -> diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/searchscreen/components/ShowSearchResults.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/searchscreen/components/ShowSearchResults.kt index 58f0364..134ae6d 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/searchscreen/components/ShowSearchResults.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/searchscreen/components/ShowSearchResults.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.rld.justlisten.android.ui.playlistscreen.Header +import com.rld.justlisten.android.ui.playlistscreen.components.Header import com.rld.justlisten.datalayer.models.SongIconList import com.rld.justlisten.viewmodel.screens.playlist.PlaylistItem import com.rld.justlisten.viewmodel.screens.search.TrackItem diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt index b4e3b17..2faa948 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt @@ -1,126 +1,104 @@ package com.rld.justlisten.android.ui.settingsscreen +import android.widget.Toast import androidx.compose.foundation.layout.* -import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.util.fastForEach -import com.rld.justlisten.android.ui.theme.ColorPallet -import com.rld.justlisten.android.ui.utils.getColorPallet +import androidx.work.WorkManager +import com.rld.justlisten.android.ui.settingsscreen.components.BottomSheetSettings +import com.rld.justlisten.android.ui.settingsscreen.components.SettingsContent import com.rld.justlisten.viewmodel.screens.settings.SettingsState +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) @Composable fun SettingsScreen( settings: SettingsState, - updateSettings: (SettingsState) -> Unit, + updateSettings: (SettingsState) -> Unit ) { - Box(Modifier.fillMaxSize()) { - Column(Modifier.fillMaxWidth()) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + val scaffoldState = + rememberBottomSheetScaffoldState(bottomSheetState = rememberBottomSheetState(initialValue = BottomSheetValue.Collapsed)) + val coroutineScope = rememberCoroutineScope() - ) { - Text( - text = "Night Mode", - style = MaterialTheme.typography.h6.copy(fontSize = 14.sp) - ) - Switch( - checked = settings.isDarkThemeOn, - modifier = Modifier.padding(8.dp), - onCheckedChange = { - updateSettings( - SettingsState( - isDarkThemeOn = !settings.isDarkThemeOn, - hasDonationNavigationOn = settings.hasDonationNavigationOn, - palletColor = settings.palletColor - ) - ) - } - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Has Bottom Donation Navigation", - style = MaterialTheme.typography.h6.copy(fontSize = 14.sp) - ) - Switch( - checked = settings.hasDonationNavigationOn, - modifier = Modifier.padding(8.dp), - onCheckedChange = { - updateSettings( - SettingsState( - hasDonationNavigationOn = !settings.hasDonationNavigationOn, - isDarkThemeOn = settings.isDarkThemeOn, - palletColor = settings.palletColor - ) - ) - } - ) - } + val hasTimerSetup = rememberSaveable { + mutableStateOf(false) + } + val hourTime = rememberSaveable { + mutableStateOf("") + } + val minuteTime = rememberSaveable { + mutableStateOf("") + } + val context = LocalContext.current + val workManager = WorkManager.getInstance(context) - val palletOptions = listOf( - ColorPallet.Dark, - ColorPallet.Green, - ColorPallet.Pink, - ColorPallet.Purple, - ColorPallet.Orange, - ColorPallet.Blue + BottomSheetScaffold( + sheetContent = { + BottomSheetSettings(workManager, scaffoldState, coroutineScope) { hours, minute -> + hasTimerSetup.value = true + hourTime.value = hours + minuteTime.value = minute + } + }, + sheetPeekHeight = 0.dp, + scaffoldState = scaffoldState + ) { + val nestedScroll = rememberScrollState() + Column( + Modifier + .fillMaxSize() + .verticalScroll(nestedScroll), + verticalArrangement = Arrangement.SpaceBetween + ) { + SettingsContent( + settings, + updateSettings, + sleepTimerClicked = { coroutineScope.launch { scaffoldState.bottomSheetState.expand() } }, ) - val (selectedOption, onOptionSelected) = remember { - mutableStateOf(palletOptions.first { - it == getColorPallet( - settings.palletColor + if (hasTimerSetup.value) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Text( + "Timer to close the app has been added: ${hourTime.value}:${minuteTime.value}" ) - }) - } - palletOptions.fastForEach { pallet -> + } Row( - Modifier - .fillMaxWidth() - .selectable(selected = (pallet == selectedOption), - onClick = { - onOptionSelected(pallet) - updateSettings( - SettingsState( - isDarkThemeOn = settings.isDarkThemeOn, - hasDonationNavigationOn = settings.hasDonationNavigationOn, - palletColor = pallet.name - ) - ) - }), - verticalAlignment = Alignment.CenterVertically + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center ) { - RadioButton( - selected = (pallet == selectedOption), + Button( onClick = { - onOptionSelected(pallet) - updateSettings( - SettingsState( - isDarkThemeOn = settings.isDarkThemeOn, - hasDonationNavigationOn = settings.hasDonationNavigationOn, - palletColor = pallet.name - ) - ) - }) - Text(pallet.name, modifier = Modifier.padding(start = 8.dp)) + workManager.cancelUniqueWork("SleepWorker") + hasTimerSetup.value = false + Toast.makeText(context, "Sleeper has been canceled", Toast.LENGTH_SHORT) + .show() + }, + modifier = Modifier.clip(CircleShape) + ) { + Text("Cancel sleeper") + + } } } + Row(modifier = Modifier.fillMaxWidth().weight(1f, false), + horizontalArrangement = Arrangement.Center) + { + Text(text ="App version:1.0.6") + } } - } } \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/BottomSheetSettings.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/BottomSheetSettings.kt new file mode 100644 index 0000000..654c785 --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/BottomSheetSettings.kt @@ -0,0 +1,52 @@ +package com.rld.justlisten.android.ui.settingsscreen.components + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material.BottomSheetScaffoldState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.unit.dp +import androidx.work.WorkManager +import kotlinx.coroutines.CoroutineScope + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun BottomSheetSettings( + workManager: WorkManager, + scaffoldState: BottomSheetScaffoldState, + coroutineScope: CoroutineScope, + onConfirmClicked: (String, String) -> Unit +) { + BoxWithConstraints( + modifier = Modifier + .height(175.dp) + .fillMaxWidth() + ) { + val maxHeight = this.maxHeight + + Canvas(modifier = Modifier.fillMaxWidth()) { + val width = size.width + val height = 25.dp + val newSize = Size(width, height.toPx()) + drawRoundRect( + color = Color.LightGray.copy(alpha = 0.40f), + size = newSize, + style = Fill, + topLeft = Offset(0f, (maxHeight.toPx() / 2) + ((height - 8.dp) / 2).toPx()), + cornerRadius = CornerRadius( + x = 5.dp.toPx(), + y = 10.dp.toPx() + ) + ) + } + TimerSetup(workManager, onConfirmClicked, coroutineScope, scaffoldState) + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/SettingsContent.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/SettingsContent.kt new file mode 100644 index 0000000..d28746d --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/SettingsContent.kt @@ -0,0 +1,136 @@ +package com.rld.justlisten.android.ui.settingsscreen.components + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.selection.selectable +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastForEach +import com.rld.justlisten.android.ui.theme.ColorPallet +import com.rld.justlisten.android.ui.utils.getColorPallet +import com.rld.justlisten.viewmodel.screens.settings.SettingsState + +@Composable +fun SettingsContent( + settings: SettingsState, + updateSettings: (SettingsState) -> Unit, + sleepTimerClicked: () -> Unit, +) { + + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + + ) { + Text( + text = "Night Mode", + style = MaterialTheme.typography.h6.copy(fontSize = 14.sp) + ) + Switch( + checked = settings.isDarkThemeOn, + modifier = Modifier.padding(8.dp), + onCheckedChange = { + updateSettings( + SettingsState( + isDarkThemeOn = !settings.isDarkThemeOn, + hasDonationNavigationOn = settings.hasDonationNavigationOn, + palletColor = settings.palletColor + ) + ) + } + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Has Bottom Donation Navigation", + style = MaterialTheme.typography.h6.copy(fontSize = 14.sp) + ) + Switch( + checked = settings.hasDonationNavigationOn, + modifier = Modifier.padding(8.dp), + onCheckedChange = { + updateSettings( + SettingsState( + hasDonationNavigationOn = !settings.hasDonationNavigationOn, + isDarkThemeOn = settings.isDarkThemeOn, + palletColor = settings.palletColor + ) + ) + } + ) + } + + val palletOptions = listOf( + ColorPallet.Dark, + ColorPallet.Green, + ColorPallet.Pink, + ColorPallet.Purple, + ColorPallet.Orange, + ColorPallet.Blue + ) + + val (selectedOption, onOptionSelected) = remember { + mutableStateOf(palletOptions.first { + it == getColorPallet( + settings.palletColor + ) + }) + } + palletOptions.fastForEach { pallet -> + Row( + Modifier + .fillMaxWidth() + .selectable(selected = (pallet == selectedOption), + onClick = { + onOptionSelected(pallet) + updateSettings( + SettingsState( + isDarkThemeOn = settings.isDarkThemeOn, + hasDonationNavigationOn = settings.hasDonationNavigationOn, + palletColor = pallet.name + ) + ) + }), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = (pallet == selectedOption), + onClick = { + onOptionSelected(pallet) + updateSettings( + SettingsState( + isDarkThemeOn = settings.isDarkThemeOn, + hasDonationNavigationOn = settings.hasDonationNavigationOn, + palletColor = pallet.name + ) + ) + }) + Text(pallet.name, modifier = Modifier.padding(start = 8.dp)) + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Button(onClick = sleepTimerClicked) { + Text("Set sleep timer") + } + } + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/TimerSetup.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/TimerSetup.kt new file mode 100644 index 0000000..3a63135 --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/components/TimerSetup.kt @@ -0,0 +1,153 @@ +package com.rld.justlisten.android.ui.settingsscreen.components + +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.BottomSheetScaffoldState +import androidx.compose.material.Button +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import com.rld.justlisten.android.workers.SleepWorker +import com.rld.justlisten.util.delay +import dev.chrisbanes.snapper.ExperimentalSnapperApi +import dev.chrisbanes.snapper.SnapOffsets +import dev.chrisbanes.snapper.rememberSnapperFlingBehavior +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.util.* +import java.util.concurrent.TimeUnit + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun TimerSetup( + workManager: WorkManager, + onConfirmClicked: (String, String) -> Unit, + coroutineScope: CoroutineScope, + scaffoldState: BottomSheetScaffoldState +) { + val hours = + (0..23).map { number -> if (number < 10) "0$number" else "$number" }.toList() + val minutes = + (0..59).map { number -> if (number < 10) "0$number" else "$number" }.toList() + val hourListState = rememberLazyListState(Int.MAX_VALUE / 2) + val minutesListState = rememberLazyListState(Int.MAX_VALUE / 2) + + Column { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + ) { + val context = LocalContext.current + Button( + onClick = { + val closeTimeHour = + hours[(hourListState.firstVisibleItemIndex + 2) % hours.size] + val closeTimeMinute = + minutes[(minutesListState.firstVisibleItemIndex + 2) % minutes.size] + onConfirmClicked(closeTimeHour, closeTimeMinute) + val rightNow = Calendar.getInstance() + val currentHour: Int = rightNow.get(Calendar.HOUR_OF_DAY) + val currentMinute: Int = rightNow.get(Calendar.MINUTE) + val delay = delay( + currentHour, + closeTimeHour.toInt(), + currentMinute, + closeTimeMinute.toInt() + ) + val myWorkRequest = OneTimeWorkRequestBuilder() + .setInitialDelay(delay, TimeUnit.MINUTES) + .build() + workManager.beginUniqueWork( + "SleepWorker", + ExistingWorkPolicy.REPLACE, + myWorkRequest + ).enqueue() + coroutineScope.launch { scaffoldState.bottomSheetState.collapse() } + Toast.makeText(context, "If you close the app, the sleeper will be canceled", Toast.LENGTH_SHORT).show() + }, + modifier = Modifier + .weight(0.45f) + .clip(CircleShape) + ) { + Text("Confirm") + } + Spacer(modifier = Modifier.weight(0.1f)) + Button( + onClick = { coroutineScope.launch { scaffoldState.bottomSheetState.collapse() } }, + modifier = Modifier + .weight(0.45f) + .clip( + CircleShape + ) + ) { + Text("Cancel") + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 2.dp) + ) { + CircularList( + hours, + modifier = Modifier.weight(0.45f), + isEndless = true, + alignment = Alignment.End, + hourListState + ) + Spacer(modifier = Modifier.weight(0.1f)) + CircularList( + minutes, + modifier = Modifier.weight(0.45f), + isEndless = true, + alignment = Alignment.Start, + minutesListState + ) + } + } +} + +@OptIn(ExperimentalSnapperApi::class) +@Composable +fun CircularList( + items: List, + modifier: Modifier = Modifier, + isEndless: Boolean = false, + alignment: Alignment.Horizontal, + listState: LazyListState +) { + + val contentPadding = PaddingValues(2.dp) + LazyColumn( + state = listState, + modifier = modifier, + horizontalAlignment = alignment, + flingBehavior = rememberSnapperFlingBehavior( + lazyListState = listState, + snapOffsetForItem = SnapOffsets.Start, + endContentPadding = contentPadding.calculateBottomPadding(), + ), + ) { + items( + count = if (isEndless) Int.MAX_VALUE else items.size, + itemContent = { + val index = it % items.size + Text(text = items[index], modifier = Modifier.padding(1.dp)) + } + ) + } + +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/workers/SleepWorker.kt b/androidApp/src/main/java/com/rld/justlisten/android/workers/SleepWorker.kt new file mode 100644 index 0000000..5a272fe --- /dev/null +++ b/androidApp/src/main/java/com/rld/justlisten/android/workers/SleepWorker.kt @@ -0,0 +1,12 @@ +package com.rld.justlisten.android.workers + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import kotlin.system.exitProcess + +class SleepWorker(val context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) { + override suspend fun doWork(): Result { + exitProcess(0) + } +} \ No newline at end of file diff --git a/shared/src/androidTest/kotlin/com/rld/audius/androidTest.kt b/shared/src/androidTest/kotlin/com/rld/audius/androidTest.kt deleted file mode 100644 index 848bd05..0000000 --- a/shared/src/androidTest/kotlin/com/rld/audius/androidTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.rld.justlisten - -import org.junit.Assert.assertTrue -import org.junit.Test - -class AndroidGreetingTest { - - @Test - fun testExample() { - assertTrue("Check Android is mentioned", com.rld.justlisten.Greeting().greeting().contains("Android")) - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/rld/justlisten/util/Sleeper.kt b/shared/src/commonMain/kotlin/com/rld/justlisten/util/Sleeper.kt new file mode 100644 index 0000000..04e4426 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/rld/justlisten/util/Sleeper.kt @@ -0,0 +1,25 @@ +package com.rld.justlisten.util + + +fun delay( + currentTimeHour: Int, + closeTimeHour: Int, + currentTimeMinute: Int, + closeTimeMinute: Int +): Long { + val delayHour = + if (currentTimeHour > closeTimeHour) { + 24 - currentTimeHour + closeTimeHour + } else if (currentTimeHour == closeTimeHour && currentTimeMinute > closeTimeMinute) { + 23 + } else { + closeTimeHour - currentTimeHour + } + val delayMinute = if (currentTimeMinute > closeTimeMinute) { + 60 - currentTimeMinute + closeTimeMinute + } else { + closeTimeMinute - currentTimeMinute + } + return (delayHour * 60 + delayMinute).toLong() +} + diff --git a/shared/src/commonTest/kotlin/com/rld/audius/commonTest.kt b/shared/src/commonTest/kotlin/com/rld/audius/commonTest.kt deleted file mode 100644 index 3e0793c..0000000 --- a/shared/src/commonTest/kotlin/com/rld/audius/commonTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.rld.justlisten - -import kotlin.test.Test -import kotlin.test.assertTrue - -class CommonGreetingTest { - - @Test - fun testExample() { - assertTrue(com.rld.justlisten.Greeting().greeting().contains("Hello"), "Check 'Hello' is mentioned") - } -} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/com/rld/justlisten/SleeperTest.kt b/shared/src/commonTest/kotlin/com/rld/justlisten/SleeperTest.kt new file mode 100644 index 0000000..56d6280 --- /dev/null +++ b/shared/src/commonTest/kotlin/com/rld/justlisten/SleeperTest.kt @@ -0,0 +1,44 @@ +package com.rld.justlisten + +import com.rld.justlisten.util.delay +import kotlin.test.Test +import kotlin.test.assertEquals + +class SleeperTest { + + @Test + fun `test 2 hour behind and minutes behind`() { + val currentHour = 23 + val selectedHour = 21 + + val currentMinute = 30 + val selectedMinute = 10 + + val delay = delay(currentHour, selectedHour, currentMinute, selectedMinute) + val expectedDelay = (24 - 23 + 21) * 60 + 40 + assertEquals(expectedDelay.toLong(), delay) + } + + @Test + fun `test 10 minutes difference`() { + val currentHour = 23 + val selectedHour = 23 + + val currentMinute = 30 + val selectedMinute = 40 + val delay = delay(currentHour, selectedHour, currentMinute, selectedMinute) + assertEquals(10L, delay) + } + + + @Test + fun `test 2 hours and 10 minutes difference`() { + val currentHour = 14 + val selectedHour = 16 + + val currentMinute = 30 + val selectedMinute = 40 + val delay = delay(currentHour, selectedHour, currentMinute, selectedMinute) + assertEquals(130L, delay) + } +} \ No newline at end of file