From df5f10e8c2764ad40e3357ee756c2251664f52f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 12 Nov 2022 15:01:44 +0100 Subject: [PATCH 01/12] Started the downloader implementation. Just created files and made some modifications. --- app/build.gradle.kts | 12 ++++-- app/src/main/AndroidManifest.xml | 1 + .../bobbyesp/spowlo/database/AppDatabase.kt | 12 ++++++ .../spowlo/database/CommandTemplate.kt | 12 ++++++ .../spowlo/database/DownloadedSongInfo.kt | 15 ++++++++ .../bobbyesp/spowlo/database/SongInfoDao.kt | 7 ++++ .../spowlo/presentation/ui/common/Route.kt | 2 + .../ui/common/SettingsProvider.kt | 8 ++++ .../pages/downloader_page/DownloaderPage.kt | 21 ++++++++++ .../downloader_page/DownloaderViewModel.kt | 38 +++++++++++++++++++ build.gradle.kts | 1 + 11 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/bobbyesp/spowlo/database/AppDatabase.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/database/CommandTemplate.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d1e8ec56..5e45974b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,6 +22,7 @@ val navigationVersion: String by rootProject.extra val roomVersion: String by rootProject.extra val accompanistVersion: String by rootProject.extra val composeMd3Version: String by rootProject.extra +val youtubedlAndroidVersion: String by rootProject.extra val coilVersion: String by rootProject.extra val okhttpVersion: String by rootProject.extra val hiltVersion: String by rootProject.extra @@ -165,11 +166,11 @@ dependencies { kapt("androidx.hilt:hilt-compiler:1.0.0") implementation("com.google.dagger:hilt-android:$hiltVersion") kapt("com.google.dagger:hilt-android-compiler:$hiltVersion") - //Room (Databases) - /*implementation("androidx.room:room-runtime:$roomVersion") + //Room (Databases) + implementation("androidx.room:room-runtime:$roomVersion") implementation("androidx.room:room-ktx:$roomVersion") - kapt("androidx.room:room-compiler:$roomVersion")*/ + kapt("androidx.room:room-compiler:$roomVersion") // Retrofit and okhttp implementation("com.squareup.retrofit2:retrofit:2.9.0") @@ -184,6 +185,11 @@ dependencies { //SimpleStorage (SAF Simplifier) implementation("com.anggrayudi:storage:1.5.0") + //Yt-dlp + implementation("com.github.xibr.youtubedl-android:library:$youtubedlAndroidVersion") + implementation("com.github.xibr.youtubedl-android:ffmpeg:$youtubedlAndroidVersion") + implementation("com.github.xibr.youtubedl-android:aria2c:$youtubedlAndroidVersion") + //Unit testing testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a15cd5fa..c4c2d91c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ android:name=".Spowlo" android:allowBackup="true" android:extractNativeLibs="true" + android:requestLegacyExternalStorage="true" android:icon="@drawable/spowlo_icon" android:label="@string/app_name" android:roundIcon="@drawable/spowlo_icon" diff --git a/app/src/main/java/com/bobbyesp/spowlo/database/AppDatabase.kt b/app/src/main/java/com/bobbyesp/spowlo/database/AppDatabase.kt new file mode 100644 index 00000000..d572c402 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/database/AppDatabase.kt @@ -0,0 +1,12 @@ +package com.bobbyesp.spowlo.database + +import androidx.room.Database +import androidx.room.RoomDatabase + +@Database( + entities = [DownloadedSongInfo::class, CommandTemplate::class], version = 3, + exportSchema = false +) +abstract class AppDatabase : RoomDatabase() { + abstract fun songInfoDao(): SongInfoDao +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/database/CommandTemplate.kt b/app/src/main/java/com/bobbyesp/spowlo/database/CommandTemplate.kt new file mode 100644 index 00000000..175043e2 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/database/CommandTemplate.kt @@ -0,0 +1,12 @@ +package com.bobbyesp.spowlo.database + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +@kotlinx.serialization.Serializable +data class CommandTemplate( + @PrimaryKey(autoGenerate = true) val id: Int, + val name: String, + val template: String +) diff --git a/app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt b/app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt new file mode 100644 index 00000000..336fe908 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt @@ -0,0 +1,15 @@ +package com.bobbyesp.spowlo.database + +import androidx.room.ColumnInfo +import androidx.room.PrimaryKey + +data class DownloadedSongInfo( + @PrimaryKey(autoGenerate = true) val id: Int, + val songTitle: String, + val songArtist: String, + val songUrl: String, + val thumbnailUrl: String, + val songPath: String, + @ColumnInfo(defaultValue = "Unknown") + val extractor: String = "Unknown" +) \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt b/app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt new file mode 100644 index 00000000..74d28e8e --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt @@ -0,0 +1,7 @@ +package com.bobbyesp.spowlo.database + +import androidx.room.Dao + +@Dao +interface SongInfoDao { +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt index c2a95c87..05d0031f 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt @@ -11,5 +11,7 @@ class Route { const val DISPLAY_SETTINGS = "display_settings" const val ABOUT_SETTINGS = "about_settings" const val DARK_THEME_SELECTOR = "dark_theme_selector" + const val DOWNLOADER = "downloader" + const val TEMPLATE = "template" } } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/SettingsProvider.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/SettingsProvider.kt index 08ccee42..b815efce 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/SettingsProvider.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/SettingsProvider.kt @@ -2,11 +2,17 @@ package com.bobbyesp.spowlo.presentation.ui.common import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalContext +import coil.ImageLoader +import com.bobbyesp.spowlo.Spowlo.Companion.context import com.bobbyesp.spowlo.presentation.ui.theme.ColorScheme.DEFAULT_SEED_COLOR import com.bobbyesp.spowlo.util.PreferencesUtil val LocalDarkTheme = compositionLocalOf { PreferencesUtil.DarkThemePreference() } val LocalSeedColor = compositionLocalOf { DEFAULT_SEED_COLOR } +val LocalVideoThumbnailLoader = staticCompositionLocalOf { + ImageLoader.Builder(context).build() +} val LocalWindowWidthState = staticCompositionLocalOf { WindowWidthSizeClass.Compact } val settingFlow = PreferencesUtil.AppSettingsStateFlow val LocalDynamicColorSwitch = compositionLocalOf { false } @@ -16,6 +22,8 @@ fun SettingsProvider(windowWidthSizeClass: WindowWidthSizeClass, content: @Compo val appSettingsState = settingFlow.collectAsState().value CompositionLocalProvider( LocalDarkTheme provides appSettingsState.darkTheme, + LocalVideoThumbnailLoader provides ImageLoader.Builder(LocalContext.current) + .build(), LocalSeedColor provides appSettingsState.seedColor, LocalWindowWidthState provides windowWidthSizeClass, LocalDynamicColorSwitch provides appSettingsState.isDynamicColorEnabled, diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt new file mode 100644 index 00000000..7a523dda --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt @@ -0,0 +1,21 @@ +package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page + +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.google.accompanist.permissions.ExperimentalPermissionsApi + +@OptIn( + ExperimentalPermissionsApi::class, ExperimentalMaterialApi::class, + ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class +) +@Composable +fun DownloaderPage( + navController: NavController, + downloadViewModel: DownloaderViewModel = hiltViewModel(), +) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt new file mode 100644 index 00000000..10aed393 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt @@ -0,0 +1,38 @@ +package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +class DownloaderViewModel @Inject constructor() : ViewModel() { + private val mutableStateFlow = MutableStateFlow(DownloaderViewState()) + val stateFlow = mutableStateFlow.asStateFlow() + private var currentJob: Job? = null + + data class DownloaderViewState( + val spotUrl: String = "", + val ytUrl: String = "", + val progress: Float = 0f, + val isDownloading: Boolean = false, + val isCancelled: Boolean = false, + val songTitle: String = "", + val songArtist: String = "", + val isDownloadError: Boolean = false, + val debugMode: Boolean = false, + val showDownloadSettingDialog: Boolean = false, + val downloadingTaskId: String = "", + val isUrlSharingTriggered: Boolean = false, + ) + + fun updateUrl(url: String, isUrlSharingTriggered: Boolean = false) = + mutableStateFlow.update { + it.copy( + ytUrl = url, + isUrlSharingTriggered = isUrlSharingTriggered + ) + } + +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f68be3de..f52a6e24 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ buildscript { val accompanistVersion by extra("0.25.1") val composeMd3Version by extra("1.0.0-beta03") val coilVersion by extra("2.2.1") + val youtubedlAndroidVersion by extra("17d16d78f6") val okhttpVersion by extra("5.0.0-alpha.10") val kotlinVersion by extra("1.7.10") val hiltVersion by extra("2.44") From 25438b2ace84c95aa073bbb402c7432bda309d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Mon, 14 Nov 2022 23:16:42 +0100 Subject: [PATCH 02/12] Added some downloader things and implemented bottom nav bar --- app/build.gradle.kts | 3 - .../main/java/com/bobbyesp/spowlo/Spowlo.kt | 37 ++++++- .../spowlo/database/DownloadedSongInfo.kt | 2 + .../bobbyesp/spowlo/database/SongInfoDao.kt | 38 +++++++- .../spowlo/presentation/MainActivity.kt | 52 ++++++++-- .../components/BottomNavBar/BottomAppBar.kt | 51 ++++++++++ .../ui/components/BottomNavBar/NavBarItem.kt | 11 +++ .../presentation/ui/pages/InitialEntry.kt | 96 +++++++++++-------- .../downloader_page/DownloaderViewModel.kt | 26 +++++ .../presentation/ui/pages/home/HomePage.kt | 17 ++-- .../com/bobbyesp/spowlo/util/DatabaseUtil.kt | 75 +++++++++++++++ .../com/bobbyesp/spowlo/util/DownloadUtil.kt | 22 +++++ .../bobbyesp/spowlo/util/PreferencesUtil.kt | 3 + app/src/main/res/values/strings.xml | 2 + build.gradle.kts | 4 +- 15 files changed, 376 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/NavBarItem.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/util/DatabaseUtil.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5e45974b..b52309d9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -186,9 +186,6 @@ dependencies { implementation("com.anggrayudi:storage:1.5.0") //Yt-dlp - implementation("com.github.xibr.youtubedl-android:library:$youtubedlAndroidVersion") - implementation("com.github.xibr.youtubedl-android:ffmpeg:$youtubedlAndroidVersion") - implementation("com.github.xibr.youtubedl-android:aria2c:$youtubedlAndroidVersion") //Unit testing testImplementation("junit:junit:4.13.2") diff --git a/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt b/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt index 41893106..d4882226 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt @@ -2,13 +2,21 @@ package com.bobbyesp.spowlo import android.annotation.SuppressLint import android.app.Application +import android.content.ClipboardManager import android.content.Context -import com.anggrayudi.storage.SimpleStorageHelper +import android.net.ConnectivityManager +import com.bobbyesp.spowlo.database.CommandTemplate +import com.bobbyesp.spowlo.util.DatabaseUtil +import com.bobbyesp.spowlo.util.PreferencesUtil +import com.bobbyesp.spowlo.util.PreferencesUtil.AUDIO_DIRECTORY +import com.bobbyesp.spowlo.util.PreferencesUtil.TEMPLATE_INDEX import com.google.android.material.color.DynamicColors import com.tencent.mmkv.MMKV import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch @HiltAndroidApp class Spowlo : Application() { @@ -18,11 +26,38 @@ class Spowlo : Application() { context = applicationContext applicationScope = CoroutineScope(SupervisorJob()) DynamicColors.applyToActivitiesIfAvailable(this) + + clipboard = getSystemService(ClipboardManager::class.java) + connectivityManager = getSystemService(ConnectivityManager::class.java) + + applicationScope.launch((Dispatchers.IO)) { + if (!PreferencesUtil.containsKey(TEMPLATE_INDEX)) { + PreferencesUtil.updateInt(TEMPLATE_INDEX, 0) + DatabaseUtil.insertTemplate( + CommandTemplate( + 0, + context.getString(R.string.custom_command_template), + PreferencesUtil.getString( + PreferencesUtil.TEMPLATE, context.getString(R.string.template_example) + ) + ) + ) + } + } } companion object{ private const val TAG = "Spowlo" lateinit var applicationScope: CoroutineScope + lateinit var clipboard: ClipboardManager + lateinit var audioDownloadDir: String + var ytdlpVersion = "" + lateinit var connectivityManager: ConnectivityManager + + fun updateDownloadDir(path: String) { + audioDownloadDir = path + PreferencesUtil.updateString(AUDIO_DIRECTORY, path) + } @SuppressLint("StaticFieldLeak") lateinit var context: Context diff --git a/app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt b/app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt index 336fe908..90482e7c 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/database/DownloadedSongInfo.kt @@ -1,8 +1,10 @@ package com.bobbyesp.spowlo.database import androidx.room.ColumnInfo +import androidx.room.Entity import androidx.room.PrimaryKey +@Entity(tableName = "DownloadedSongInfo") data class DownloadedSongInfo( @PrimaryKey(autoGenerate = true) val id: Int, val songTitle: String, diff --git a/app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt b/app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt index 74d28e8e..b0438437 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/database/SongInfoDao.kt @@ -1,7 +1,43 @@ package com.bobbyesp.spowlo.database -import androidx.room.Dao +import androidx.room.* +import kotlinx.coroutines.flow.Flow @Dao interface SongInfoDao { + @Insert + suspend fun insertAll(vararg info: DownloadedSongInfo) + + @Query("select * from DownloadedSongInfo") + fun getAllMedia(): Flow> + + @Query("select * from DownloadedSongInfo where id=:id") + suspend fun getInfoById(id: Int): DownloadedSongInfo + + @Delete + suspend fun delete(info: DownloadedSongInfo) + + @Query("DELETE FROM DownloadedSongInfo WHERE id = :id") + suspend fun deleteInfoById(id: Int) + + @Query("DELETE FROM DownloadedSongInfo WHERE songPath = :path") + suspend fun deleteInfoByPath(path: String) + + @Query("SELECT * FROM CommandTemplate") + fun getTemplateFlow(): Flow> + + @Query("SELECT * FROM CommandTemplate") + suspend fun getTemplateList(): List + + @Insert + suspend fun insertTemplate(template: CommandTemplate) + + @Update + suspend fun updateTemplate(template: CommandTemplate) + + @Delete + suspend fun deleteTemplate(template: CommandTemplate) + + @Query("SELECT * FROM CommandTemplate where id = :id") + suspend fun getTemplateById(id: Int): CommandTemplate } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index e7c41c7d..dff55154 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -1,5 +1,6 @@ package com.bobbyesp.spowlo.presentation +import android.annotation.SuppressLint import android.os.Build import android.os.Bundle import android.util.Log @@ -7,21 +8,33 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.ui.Modifier import androidx.core.os.LocaleListCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import com.bobbyesp.spowlo.Spowlo.Companion.applicationScope import com.bobbyesp.spowlo.Spowlo.Companion.context -import com.bobbyesp.spowlo.presentation.ui.common.LocalDarkTheme -import com.bobbyesp.spowlo.presentation.ui.common.LocalDynamicColorSwitch -import com.bobbyesp.spowlo.presentation.ui.common.LocalSeedColor -import com.bobbyesp.spowlo.presentation.ui.common.SettingsProvider +import com.bobbyesp.spowlo.presentation.ui.common.* +import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.BottomNavBar +import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.NavBarItem import com.bobbyesp.spowlo.presentation.ui.pages.InitialEntry import com.bobbyesp.spowlo.presentation.ui.pages.home.HomeViewModel import com.bobbyesp.spowlo.presentation.ui.theme.SpowloTheme import com.bobbyesp.spowlo.util.PreferencesUtil +import com.google.accompanist.navigation.animation.rememberAnimatedNavController import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -32,7 +45,10 @@ class MainActivity : ComponentActivity() { private val homeViewModel: HomeViewModel by viewModels() - @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) + @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalMaterial3Api::class, + ExperimentalAnimationApi::class + ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) @@ -48,6 +64,7 @@ class MainActivity : ComponentActivity() { } context = this.baseContext setContent { + val navController = rememberAnimatedNavController() val windowSizeClass = calculateWindowSizeClass(this) SettingsProvider(windowSizeClass.widthSizeClass){ SpowloTheme( @@ -56,7 +73,30 @@ class MainActivity : ComponentActivity() { seedColor = LocalSeedColor.current, isDynamicColorEnabled = LocalDynamicColorSwitch.current, ) { - InitialEntry(homeViewModel) + Scaffold( + bottomBar = { + BottomNavBar(items = listOf( + NavBarItem( + name = "Home", + icon = Icons.Filled.Home, + route = Route.HOME + ), + NavBarItem( + name = "Settings", + icon = Icons.Filled.Settings, + route = Route.SETTINGS, + ), + ), navController = navController, onItemClicked = { + //if the current route is the same as the one we are trying to navigate to, do nothing + if (navController.currentDestination?.route != it.route) { + navController.navigate(it.route) + } + }) + }){ + + InitialEntry(homeViewModel, modifier = Modifier.padding(paddingValues = it), navController = navController) + + } } } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt new file mode 100644 index 00000000..70b49e75 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt @@ -0,0 +1,51 @@ +package com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.* +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.compose.currentBackStackEntryAsState +import com.bobbyesp.spowlo.presentation.ui.common.Route + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BottomNavBar( + modifier: Modifier = Modifier, + items: List, + navController: NavController, + onItemClicked: (NavBarItem) -> Unit +) { + val backStackEntry = navController.currentBackStackEntryAsState() + NavigationBar( + modifier = modifier, + ){ + items.forEach { item -> + val selected = item.route == backStackEntry.value?.destination?.route + NavigationBarItem( + icon = { + Column(horizontalAlignment = CenterHorizontally) { + if(item.badgeCount > 0){ + BadgedBox(badge = { + Text(text = item.badgeCount.toString()) + }) { + Icon(imageVector = item.icon, contentDescription = item.name) + } + } + else { + Icon(imageVector = item.icon, contentDescription = item.name) + } + } + }, + label = { Text(text = item.name) }, + selected = selected, + onClick = { onItemClicked(item) } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/NavBarItem.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/NavBarItem.kt new file mode 100644 index 00000000..a9f577f8 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/NavBarItem.kt @@ -0,0 +1,11 @@ +package com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar + +import androidx.compose.ui.graphics.vector.ImageVector +import com.bobbyesp.spowlo.presentation.ui.common.Route + +data class NavBarItem( + val name: String, + val icon: ImageVector, + val route: String, + val badgeCount: Int = 0 +) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt index 0535a422..3dbc3a9e 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt @@ -12,7 +12,15 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.MaterialTheme +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -20,6 +28,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController import com.bobbyesp.spowlo.presentation.ui.common.LocalWindowWidthState import com.bobbyesp.spowlo.presentation.ui.common.Route import com.bobbyesp.spowlo.presentation.ui.common.animatedComposable @@ -40,13 +49,15 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import com.bobbyesp.spowlo.R +import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.BottomNavBar +import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.NavBarItem private const val TAG = "InitialEntry" -@OptIn(ExperimentalAnimationApi::class) +@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class) @Composable -fun InitialEntry(homeViewModel: HomeViewModel) { - val navController = rememberAnimatedNavController() +fun InitialEntry(homeViewModel: HomeViewModel, modifier: Modifier = Modifier, navController: NavHostController) { + val context = LocalContext.current val scope = rememberCoroutineScope() var updateJob: Job? = null @@ -77,57 +88,62 @@ fun InitialEntry(homeViewModel: HomeViewModel) { } } } + val viewState = homeViewModel.stateFlow.collectAsState() - Box( - modifier = Modifier - .fillMaxSize() - .background(androidx.compose.material3.MaterialTheme.colorScheme.background) - ){ - AnimatedNavHost( + + Box(modifier = modifier){ + Box( modifier = Modifier - .fillMaxWidth( - when (LocalWindowWidthState.current) { - WindowWidthSizeClass.Compact -> 1f - WindowWidthSizeClass.Expanded -> 0.5f - else -> 0.8f + .fillMaxSize() + .background(androidx.compose.material3.MaterialTheme.colorScheme.background) + ){ + AnimatedNavHost( + modifier = Modifier + .fillMaxWidth( + when (LocalWindowWidthState.current) { + WindowWidthSizeClass.Compact -> 1f + WindowWidthSizeClass.Expanded -> 0.5f + else -> 0.8f + } + ) + .align(Alignment.Center), + navController = navController, + startDestination = Route.HOME) { + + animatedComposable(Route.HOME){ + HomePage(navController = navController, homeViewModel = homeViewModel) + if (!viewState.value.loaded){ + homeViewModel.setup() } - ) - .align(Alignment.Center), - navController = navController, - startDestination = Route.HOME) { - - animatedComposable(Route.HOME){ - HomePage(navController = navController, homeViewModel = homeViewModel) - if (!viewState.value.loaded){ - homeViewModel.setup() } - } - animatedComposable(Route.SETTINGS){ - SettingsPage(navController) - } + animatedComposable(Route.SETTINGS){ + SettingsPage(navController) + } - animatedComposable(Route.ABOUT){ - } + animatedComposable(Route.ABOUT){ + } - animatedComposable(Route.DISPLAY_SETTINGS){ - AppearancePreferences(navController) - } + animatedComposable(Route.DISPLAY_SETTINGS){ + AppearancePreferences(navController) + } - animatedComposable(Route.LANGUAGES){ - LanguagesPreferences{ - onBackPressed() + animatedComposable(Route.LANGUAGES){ + LanguagesPreferences{ + onBackPressed() + } } - } - animatedComposable(Route.DARK_THEME_SELECTOR){ - DarkThemePreferences { - onBackPressed() + animatedComposable(Route.DARK_THEME_SELECTOR){ + DarkThemePreferences { + onBackPressed() + } } - } + } } } + LaunchedEffect(Unit) { launch(Dispatchers.IO) { kotlin.runCatching { diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt index 10aed393..de7f01d7 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt @@ -1,10 +1,14 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue import androidx.lifecycle.ViewModel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject class DownloaderViewModel @Inject constructor() : ViewModel() { @@ -25,6 +29,11 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { val showDownloadSettingDialog: Boolean = false, val downloadingTaskId: String = "", val isUrlSharingTriggered: Boolean = false, + val drawerState: Boolean = false, + /*val drawerState: ModalBottomSheetState = ModalBottomSheetState( + ModalBottomSheetValue.Hidden, + isSkipHalfExpanded = true + ),*/ ) fun updateUrl(url: String, isUrlSharingTriggered: Boolean = false) = @@ -34,5 +43,22 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { isUrlSharingTriggered = isUrlSharingTriggered ) } + fun hideDialog(scope: CoroutineScope, isDialog: Boolean) { + scope.launch { + if (isDialog) + mutableStateFlow.update { it.copy(showDownloadSettingDialog = false) } + else + stateFlow.value.drawerState//.hide() + } + } + + fun showDialog(scope: CoroutineScope, isDialog: Boolean) { + scope.launch { + if (isDialog) + mutableStateFlow.update { it.copy(showDownloadSettingDialog = true) } + else + stateFlow.value.drawerState//.show() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt index a747fd52..c091f008 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt @@ -49,14 +49,6 @@ fun HomePage(navController: NavController, homeViewModel: HomeViewModel = hiltVi val amoledVersions = viewState.value.amoled_versions val amoledClonedVersions = viewState.value.amoled_cloned_versions - val archs by remember{ - mutableStateOf( - listOf( - ArchType.Arm64, - ArchType.Arm - ).random() - ) - } with(viewState.value){ Box( modifier = Modifier @@ -65,13 +57,16 @@ fun HomePage(navController: NavController, homeViewModel: HomeViewModel = hiltVi ){ Scaffold(modifier = Modifier .align(Alignment.Center) - .fillMaxSize(), + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), topBar = { TopAppBar( title = {}, modifier = Modifier.padding(horizontal = 8.dp), navigationIcon = { - IconButton(onClick = {navController.navigate(Route.SETTINGS) }) { + IconButton(onClick = { + navController.navigate(Route.SETTINGS) + }) { Icon( imageVector = Icons.Outlined.Settings, contentDescription = stringResource(id = R.string.settings) @@ -90,7 +85,7 @@ fun HomePage(navController: NavController, homeViewModel: HomeViewModel = hiltVi Row(modifier = Modifier .fillMaxWidth() .align(Alignment.Start) - .padding(start = 8.dp, top = 12.dp, bottom = 12.dp)) + .padding(start = 8.dp, top = 12.dp, bottom = 12.dp, end = 8.dp)) { Text( modifier = Modifier, diff --git a/app/src/main/java/com/bobbyesp/spowlo/util/DatabaseUtil.kt b/app/src/main/java/com/bobbyesp/spowlo/util/DatabaseUtil.kt new file mode 100644 index 00000000..5fa503d1 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/util/DatabaseUtil.kt @@ -0,0 +1,75 @@ +package com.bobbyesp.spowlo.util + +import androidx.room.Room +import com.bobbyesp.spowlo.Spowlo.Companion.applicationScope +import com.bobbyesp.spowlo.Spowlo.Companion.context +import com.bobbyesp.spowlo.database.AppDatabase +import com.bobbyesp.spowlo.database.CommandTemplate +import com.bobbyesp.spowlo.database.DownloadedSongInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +object DatabaseUtil { + val format = Json { prettyPrint = true } + private const val DATABASE_NAME = "app_database" + private val db = Room.databaseBuilder( + context, + AppDatabase::class.java, DATABASE_NAME + ).build() + private val dao = db.songInfoDao() + fun insertInfo(vararg infoList: DownloadedSongInfo) { + applicationScope.launch(Dispatchers.IO) { + for (info in infoList) { + dao.deleteInfoByPath(info.songPath) + dao.insertAll(info) + } + } + } + + fun getMediaInfo() = dao.getAllMedia() + + fun getTemplateFlow() = dao.getTemplateFlow() + + suspend fun getTemplateList() = dao.getTemplateList() + + suspend fun getInfoById(id: Int): DownloadedSongInfo = dao.getInfoById(id) + suspend fun deleteInfoById(id: Int) = dao.deleteInfoById(id) + + suspend fun insertTemplate(commandTemplate: CommandTemplate) { + dao.insertTemplate(commandTemplate) + } + + suspend fun updateTemplate(commandTemplate: CommandTemplate) { + dao.updateTemplate(commandTemplate) + } + + suspend fun deleteTemplate(commandTemplate: CommandTemplate) { + dao.deleteTemplate(commandTemplate) + } + + suspend fun exportTemplatesToJson(): String { + return format.encodeToString(getTemplateList()) + } + + suspend fun importTemplatesFromJson(json: String): Int { + val list = getTemplateList() + var cnt = 0 + try { + format.decodeFromString>(json) + .forEach { + if (!list.contains(it)) { + cnt++ + dao.insertTemplate(it.copy(id = 0)) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return cnt + } + + private const val TAG = "DatabaseUtil" +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/util/DownloadUtil.kt b/app/src/main/java/com/bobbyesp/spowlo/util/DownloadUtil.kt index 9dab8cd7..380831e0 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/util/DownloadUtil.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/util/DownloadUtil.kt @@ -10,6 +10,28 @@ import java.io.File object DownloadUtil { + enum class ResultCode { + SUCCESS, EXCEPTION + } + + data class PlaylistInfo( + val url: String = "", + val size: Int = 0, + val title: String = "" + ) + + class Result(val resultCode: ResultCode, val filePath: List?) { + companion object { + fun failure(): Result { + return Result(ResultCode.EXCEPTION, null) + } + + fun success(filePaths: List?): Result { + return Result(ResultCode.SUCCESS, filePaths) + } + } + } + private const val TAG = "DownloadUtil" private var apkUrl: String = "" diff --git a/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt b/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt index db3ea89f..95656e9f 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt @@ -39,6 +39,9 @@ object PreferencesUtil { const val DYNAMIC_COLOR = "dynamic_color" const val LANGUAGE = "language" const val SPOTIFY_URL = "spotify_url" + const val AUDIO_DIRECTORY = "audio_directory" + const val TEMPLATE_INDEX = "template_index" + const val TEMPLATE = "template" const val SYSTEM_DEFAULT = 0 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 42da0c2d..43ec90c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,4 +44,6 @@ This type of architecture is based on 64 bit. It can run any Spotify package (32 bit ones are emulated) This type of architecture is based on 32 bit. It can JUST run ARMEABI packages. To check your processor architecture, scroll down till the end of the main page. + Command template + --no-mtime -f \"bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4] / bv*+ba/b\" \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f52a6e24..58460b49 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,13 +6,15 @@ buildscript { val accompanistVersion by extra("0.25.1") val composeMd3Version by extra("1.0.0-beta03") val coilVersion by extra("2.2.1") - val youtubedlAndroidVersion by extra("17d16d78f6") + val youtubedlAndroidVersion by extra("0.14.0") val okhttpVersion by extra("5.0.0-alpha.10") val kotlinVersion by extra("1.7.10") val hiltVersion by extra("2.44") repositories { mavenCentral() + //add jitpack repository + maven("https://jitpack.io") } dependencies { From 65d1f8963613809b7ed8d92e14978fbef608927a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Tue, 15 Nov 2022 21:52:46 +0100 Subject: [PATCH 03/12] Finished implementing nav bar. --- .../spowlo/presentation/MainActivity.kt | 66 ++++++++++++------- .../components/BottomNavBar/BottomAppBar.kt | 59 ++++++++++------- .../ui/components/RelevantInfoItem.kt | 7 +- .../presentation/ui/pages/home/HomePage.kt | 4 +- .../ui/pages/home/HomeViewModel.kt | 2 - 5 files changed, 83 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index dff55154..5f0390a7 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -5,15 +5,18 @@ import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.LocalActivityResultRegistryOwner.current import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ScaffoldState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Settings @@ -21,10 +24,13 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.core.os.LocaleListCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat +import androidx.navigation.NavGraph import com.bobbyesp.spowlo.Spowlo.Companion.applicationScope import com.bobbyesp.spowlo.Spowlo.Companion.context import com.bobbyesp.spowlo.presentation.ui.common.* @@ -66,6 +72,13 @@ class MainActivity : ComponentActivity() { setContent { val navController = rememberAnimatedNavController() val windowSizeClass = calculateWindowSizeClass(this) + //if the current route is not in the list of routes, then hide the nav bar modifying the visible var + val visible = remember { mutableStateOf(true) } + //if current route is not home or settings, change the visible var to false + navController.addOnDestinationChangedListener { _, destination, _ -> + visible.value = destination.route in listOf(Route.HOME, Route.SETTINGS) + } + SettingsProvider(windowSizeClass.widthSizeClass){ SpowloTheme( darkTheme = LocalDarkTheme.current.isDarkTheme(), @@ -73,35 +86,40 @@ class MainActivity : ComponentActivity() { seedColor = LocalSeedColor.current, isDynamicColorEnabled = LocalDynamicColorSwitch.current, ) { - Scaffold( - bottomBar = { - BottomNavBar(items = listOf( - NavBarItem( - name = "Home", - icon = Icons.Filled.Home, - route = Route.HOME - ), - NavBarItem( - name = "Settings", - icon = Icons.Filled.Settings, - route = Route.SETTINGS, - ), - ), navController = navController, onItemClicked = { - //if the current route is the same as the one we are trying to navigate to, do nothing - if (navController.currentDestination?.route != it.route) { - navController.navigate(it.route) - } - }) - }){ - - InitialEntry(homeViewModel, modifier = Modifier.padding(paddingValues = it), navController = navController) - + Scaffold( + bottomBar = { + BottomNavBar( + items = listOf( + NavBarItem( + name = "Home", + icon = Icons.Filled.Home, + route = Route.HOME + ), + NavBarItem( + name = "Settings", + icon = Icons.Filled.Settings, + route = Route.SETTINGS, + ), + ), navController = navController, + onItemClicked = { + //if the current route is the same as the one we are trying to navigate to, do nothing + if (navController.currentDestination?.route != it.route) { + navController.navigate(it.route) + } + }, + visible = visible.value + ) + }){ + //If the user is at a route different from home or settings, hide the bottom nav bar + InitialEntry(homeViewModel, + modifier = Modifier.padding(paddingValues = it), + navController = navController) + } } } } } - } companion object { private const val TAG = "MainActivity" diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt index 70b49e75..fde47a17 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt @@ -1,5 +1,8 @@ package com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column import androidx.compose.material3.* import androidx.compose.ui.Modifier @@ -19,33 +22,39 @@ fun BottomNavBar( modifier: Modifier = Modifier, items: List, navController: NavController, - onItemClicked: (NavBarItem) -> Unit + onItemClicked: (NavBarItem) -> Unit, + visible: Boolean = true ) { val backStackEntry = navController.currentBackStackEntryAsState() - NavigationBar( - modifier = modifier, - ){ - items.forEach { item -> - val selected = item.route == backStackEntry.value?.destination?.route - NavigationBarItem( - icon = { - Column(horizontalAlignment = CenterHorizontally) { - if(item.badgeCount > 0){ - BadgedBox(badge = { - Text(text = item.badgeCount.toString()) - }) { - Icon(imageVector = item.icon, contentDescription = item.name) - } - } - else { - Icon(imageVector = item.icon, contentDescription = item.name) - } - } - }, - label = { Text(text = item.name) }, - selected = selected, - onClick = { onItemClicked(item) } - ) + AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut()) { + NavigationBar( + modifier = modifier, + tonalElevation = 3.dp + ){ + items.forEach { item -> + val selected = item.route == backStackEntry.value?.destination?.route + NavigationBarItem( + icon = { + Column(horizontalAlignment = CenterHorizontally) { + if(item.badgeCount > 0){ + BadgedBox(badge = { + Text(text = item.badgeCount.toString()) + }) { + Icon(imageVector = item.icon, contentDescription = item.name) + } + } + else { + Icon(imageVector = item.icon, contentDescription = item.name) + } + } + }, + label = { + Text(text = item.name) }, + selected = selected, + onClick = { onItemClicked(item) } + ) + } } } + } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/RelevantInfoItem.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/RelevantInfoItem.kt index 5e3eb67f..e10af26b 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/RelevantInfoItem.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/RelevantInfoItem.kt @@ -48,6 +48,7 @@ fun RelevantInfoItem( ) } } + Divider() Row( @@ -62,8 +63,10 @@ fun RelevantInfoItem( ) Box(modifier = Modifier .fillMaxWidth() - .wrapContentSize(Alignment.CenterEnd)){ - Row(modifier = Modifier) { + .wrapContentSize(Alignment.CenterEnd), + contentAlignment = Alignment.Center) + { + Row(modifier = Modifier.padding(start = 4.dp, end = 4.dp)) { Text( text = originalSpotifyVersion, style = MaterialTheme.typography.bodyMedium, diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt index c091f008..dc7c1256 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomePage.kt @@ -56,13 +56,13 @@ fun HomePage(navController: NavController, homeViewModel: HomeViewModel = hiltVi .background(MaterialTheme.colorScheme.background) ){ Scaffold(modifier = Modifier - .align(Alignment.Center) .fillMaxSize() .background(MaterialTheme.colorScheme.background), topBar = { TopAppBar( title = {}, - modifier = Modifier.padding(horizontal = 8.dp), + modifier = Modifier.padding(horizontal = 8.dp) + .fillMaxWidth(), navigationIcon = { IconButton(onClick = { navController.navigate(Route.SETTINGS) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomeViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomeViewModel.kt index 5de82c53..38939dbd 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomeViewModel.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/home/HomeViewModel.kt @@ -85,8 +85,6 @@ class HomeViewModel @Inject constructor( ), isLoading = false ) - println(state.value.APIResponse) - val localAPIResponse = state.value.APIResponse mutableStateFlow.update { From ba0e817aa06ccb18a43c1b2859872f7e1051176e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 17 Nov 2022 18:50:46 +0100 Subject: [PATCH 04/12] Started adding a lot of new things related to Spotify library and more! --- app/build.gradle.kts | 26 +++++++ app/src/main/AndroidManifest.xml | 31 +++++++++ .../main/java/com/bobbyesp/spowlo/Spowlo.kt | 4 ++ .../bobbyesp/spowlo/data/auth/AuthModel.kt | 15 +++++ .../auth/SpotifyImplicitLoginActivityImpl.kt | 29 ++++++++ .../auth/SpotifyPkceLoginActivityImpl.kt | 31 +++++++++ .../web_api/utilities/VerifyLoggedInUtils.kt | 67 +++++++++++++++++++ .../spowlo/presentation/MainActivity.kt | 32 ++++----- .../BottomAppBar.kt | 6 +- .../NavBarItem.kt | 3 +- .../presentation/ui/pages/InitialEntry.kt | 28 ++++---- .../pages/downloader_page/DownloaderPage.kt | 48 +++++++++++++ .../downloader_page/DownloaderViewModel.kt | 15 +++++ app/src/main/res/values-es/strings.xml | 3 + app/src/main/res/values/strings.xml | 4 +- build.gradle.kts | 1 + 16 files changed, 305 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/bobbyesp/spowlo/data/auth/AuthModel.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt rename app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/{BottomNavBar => bottomNavBar}/BottomAppBar.kt (89%) rename app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/{BottomNavBar => bottomNavBar}/NavBarItem.kt (60%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b52309d9..abfc0f55 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,6 +26,7 @@ val youtubedlAndroidVersion: String by rootProject.extra val coilVersion: String by rootProject.extra val okhttpVersion: String by rootProject.extra val hiltVersion: String by rootProject.extra +val spotifyLibrary: String by rootProject.extra val keystorePropertiesFile = rootProject.file("keystore.properties") @@ -87,10 +88,32 @@ android { ) if (keystorePropertiesFile.exists()) signingConfig = signingConfigs.getByName("debug") + buildConfigField("String", + "SPOTIFY_CLIENT_ID", + "\"abcad8ba647d4b0ebae797a8f444ac9b\"") + buildConfigField("String", + "SPOTIFY_REDIRECT_URI", + "\"spowlo://spotify-auth\"") + buildConfigField( + "String", + "SPOTIFY_REDIRECT_URI_PKCE", + "\"spowlo://spotify-pkce\"" + ) } debug { if (keystorePropertiesFile.exists()) signingConfig = signingConfigs.getByName("debug") + buildConfigField("String", + "SPOTIFY_CLIENT_ID", + "\"abcad8ba647d4b0ebae797a8f444ac9b\"") + buildConfigField("String", + "SPOTIFY_REDIRECT_URI_AUTH", + "\"spowlo://spotify-auth\"") + buildConfigField( + "String", + "SPOTIFY_REDIRECT_URI_PKCE", + "\"spowlo://spotify-pkce\"" + ) } } compileOptions { @@ -187,6 +210,9 @@ dependencies { //Yt-dlp + //Spotify SDK Integration library + implementation("com.adamratzman:spotify-api-kotlin-core:$spotifyLibrary") + //Unit testing testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c4c2d91c..8d5670c2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,6 +65,37 @@ android:name="autoStoreLocales" android:value="true" /> + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt b/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt index d4882226..b6c58b2c 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/Spowlo.kt @@ -5,6 +5,7 @@ import android.app.Application import android.content.ClipboardManager import android.content.Context import android.net.ConnectivityManager +import com.bobbyesp.spowlo.data.auth.AuthModel import com.bobbyesp.spowlo.database.CommandTemplate import com.bobbyesp.spowlo.util.DatabaseUtil import com.bobbyesp.spowlo.util.PreferencesUtil @@ -20,10 +21,13 @@ import kotlinx.coroutines.launch @HiltAndroidApp class Spowlo : Application() { + lateinit var model: AuthModel + override fun onCreate() { super.onCreate() MMKV.initialize(this) context = applicationContext + this.model = AuthModel applicationScope = CoroutineScope(SupervisorJob()) DynamicColors.applyToActivitiesIfAvailable(this) diff --git a/app/src/main/java/com/bobbyesp/spowlo/data/auth/AuthModel.kt b/app/src/main/java/com/bobbyesp/spowlo/data/auth/AuthModel.kt new file mode 100644 index 00000000..526a259e --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/data/auth/AuthModel.kt @@ -0,0 +1,15 @@ +package com.bobbyesp.spowlo.data.auth + +import com.adamratzman.spotify.auth.SpotifyDefaultCredentialStore +import com.bobbyesp.spowlo.BuildConfig +import com.bobbyesp.spowlo.Spowlo + +object AuthModel { + val credentialStore by lazy { + SpotifyDefaultCredentialStore( + clientId = BuildConfig.SPOTIFY_CLIENT_ID, + redirectUri = BuildConfig.SPOTIFY_REDIRECT_URI_PKCE, + applicationContext = Spowlo.context + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt new file mode 100644 index 00000000..494d833b --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt @@ -0,0 +1,29 @@ +package com.bobbyesp.spowlo.domain.spotify.web_api.auth + +import android.content.Intent +import com.adamratzman.spotify.SpotifyImplicitGrantApi +import com.adamratzman.spotify.SpotifyScope +import com.adamratzman.spotify.auth.implicit.AbstractSpotifyAppImplicitLoginActivity +import com.bobbyesp.spowlo.BuildConfig +import com.bobbyesp.spowlo.Spowlo +import com.bobbyesp.spowlo.presentation.MainActivity +import com.bobbyesp.spowlo.util.Utils.makeToast + +class SpotifyImplicitLoginActivityImpl: AbstractSpotifyAppImplicitLoginActivity() { + override val state: Int = 1337 + override val clientId: String = BuildConfig.SPOTIFY_CLIENT_ID + override val redirectUri: String = BuildConfig.SPOTIFY_REDIRECT_URI_AUTH + override val useDefaultRedirectHandler: Boolean = false + override fun getRequestingScopes(): List = SpotifyScope.values().toList() + + override fun onFailure(errorMessage: String) { + makeToast("Auth failed: $errorMessage") + } + + override fun onSuccess(spotifyApi: SpotifyImplicitGrantApi) { + val model = (application as Spowlo).model + model.credentialStore.setSpotifyApi(spotifyApi) + startActivity(Intent(this, MainActivity::class.java)) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt new file mode 100644 index 00000000..bdaf695c --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt @@ -0,0 +1,31 @@ +package com.bobbyesp.spowlo.domain.spotify.web_api.auth + +import android.app.Activity +import com.adamratzman.spotify.SpotifyClientApi +import com.adamratzman.spotify.SpotifyScope +import com.adamratzman.spotify.auth.pkce.AbstractSpotifyPkceLoginActivity +import com.bobbyesp.spowlo.BuildConfig +import com.bobbyesp.spowlo.Spowlo +import com.bobbyesp.spowlo.presentation.MainActivity +import com.bobbyesp.spowlo.util.Utils.makeToast + +internal var pkceClassBackTo: Class? = null + +class SpotifyPkceLoginActivityImpl: AbstractSpotifyPkceLoginActivity() { + override val clientId = BuildConfig.SPOTIFY_CLIENT_ID + override val redirectUri = BuildConfig.SPOTIFY_REDIRECT_URI_PKCE + override val scopes = SpotifyScope.values().toList() + + override fun onFailure(exception: Exception) { + exception.printStackTrace() + pkceClassBackTo = null + makeToast("Auth failed: ${exception.message}") + } + + override fun onSuccess(api: SpotifyClientApi) { + val model = (application as Spowlo).model + model.credentialStore.setSpotifyApi(api) + val classBackTo = pkceClassBackTo ?: MainActivity::class.java + pkceClassBackTo = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt new file mode 100644 index 00000000..8080f7dc --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt @@ -0,0 +1,67 @@ +package com.bobbyesp.spowlo.domain.spotify.web_api.utilities + +import android.app.Activity +import com.adamratzman.spotify.SpotifyClientApi +import com.adamratzman.spotify.SpotifyException +import com.adamratzman.spotify.auth.SpotifyDefaultCredentialStore +import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity +import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity +import com.bobbyesp.spowlo.data.auth.AuthModel +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.pkceClassBackTo +import kotlinx.coroutines.runBlocking + +fun Activity.guardValidSpotifyApi( + classBackTo: Class, + alreadyTriedToReauthenticate: Boolean = false, + block: suspend (api: SpotifyClientApi) -> T +): T? { + return runBlocking { + try { + val token = AuthModel.credentialStore.spotifyToken + ?: throw SpotifyException.ReAuthenticationNeededException() + val usesPkceAuth = token.refreshToken != null + val api = (if (usesPkceAuth) AuthModel.credentialStore.getSpotifyClientPkceApi() + else AuthModel.credentialStore.getSpotifyImplicitGrantApi()) + ?: throw SpotifyException.ReAuthenticationNeededException() + + block(api) + } catch (e: SpotifyException) { + e.printStackTrace() + val usesPkceAuth = AuthModel.credentialStore.spotifyToken?.refreshToken != null + if (usesPkceAuth) { + val api = AuthModel.credentialStore.getSpotifyClientPkceApi()!! + if (!alreadyTriedToReauthenticate) { + try { + api.refreshToken() + AuthModel.credentialStore.spotifyToken = api.token + block(api) + } catch (e: SpotifyException.ReAuthenticationNeededException) { + e.printStackTrace() + return@runBlocking guardValidSpotifyApi( + classBackTo = classBackTo, + alreadyTriedToReauthenticate = true, + block = block + ) + } catch (e: IllegalArgumentException) { + e.printStackTrace() + return@runBlocking guardValidSpotifyApi( + classBackTo = classBackTo, + alreadyTriedToReauthenticate = true, + block = block + ) + } + } else { + pkceClassBackTo = classBackTo + startSpotifyClientPkceLoginActivity(SpotifyPkceLoginActivityImpl::class.java) + null + } + } else { + SpotifyDefaultCredentialStore.activityBackOnImplicitAuth = classBackTo + startSpotifyImplicitLoginActivity(SpotifyImplicitLoginActivityImpl::class.java) + null + } + } + } +} diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index 5f0390a7..b8721db6 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -5,19 +5,13 @@ import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity -import androidx.activity.compose.LocalActivityResultRegistryOwner.current import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatDelegate -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ScaffoldState import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.ExperimentalMaterial3Api @@ -27,16 +21,19 @@ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.core.os.LocaleListCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat -import androidx.navigation.NavGraph +import com.bobbyesp.spowlo.R import com.bobbyesp.spowlo.Spowlo.Companion.applicationScope import com.bobbyesp.spowlo.Spowlo.Companion.context +import com.bobbyesp.spowlo.data.auth.AuthModel import com.bobbyesp.spowlo.presentation.ui.common.* -import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.BottomNavBar -import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.NavBarItem +import com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar.BottomNavBar +import com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar.NavBarItem import com.bobbyesp.spowlo.presentation.ui.pages.InitialEntry +import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderViewModel import com.bobbyesp.spowlo.presentation.ui.pages.home.HomeViewModel import com.bobbyesp.spowlo.presentation.ui.theme.SpowloTheme import com.bobbyesp.spowlo.util.PreferencesUtil @@ -48,8 +45,8 @@ import kotlinx.coroutines.runBlocking @AndroidEntryPoint class MainActivity : ComponentActivity() { - private val homeViewModel: HomeViewModel by viewModels() + private val downloaderViewModel: DownloaderViewModel by viewModels() @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalMaterial3Api::class, @@ -76,7 +73,7 @@ class MainActivity : ComponentActivity() { val visible = remember { mutableStateOf(true) } //if current route is not home or settings, change the visible var to false navController.addOnDestinationChangedListener { _, destination, _ -> - visible.value = destination.route in listOf(Route.HOME, Route.SETTINGS) + visible.value = destination.route in listOf(Route.HOME, Route.SETTINGS, Route.DOWNLOADER) } SettingsProvider(windowSizeClass.widthSizeClass){ @@ -91,15 +88,20 @@ class MainActivity : ComponentActivity() { BottomNavBar( items = listOf( NavBarItem( - name = "Home", + name = stringResource(id = R.string.home), icon = Icons.Filled.Home, route = Route.HOME ), NavBarItem( - name = "Settings", + name = stringResource(id = R.string.settings), icon = Icons.Filled.Settings, route = Route.SETTINGS, ), + NavBarItem( + name = stringResource(id = R.string.downloader), + icon = Icons.Filled.Download, + route = Route.DOWNLOADER, + ), ), navController = navController, onItemClicked = { //if the current route is the same as the one we are trying to navigate to, do nothing @@ -113,7 +115,7 @@ class MainActivity : ComponentActivity() { //If the user is at a route different from home or settings, hide the bottom nav bar InitialEntry(homeViewModel, modifier = Modifier.padding(paddingValues = it), - navController = navController) + navController = navController, downloaderViewModel = downloaderViewModel, activity = this@MainActivity) } } } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/bottomNavBar/BottomAppBar.kt similarity index 89% rename from app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt rename to app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/bottomNavBar/BottomAppBar.kt index fde47a17..3d560664 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/BottomAppBar.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/bottomNavBar/BottomAppBar.kt @@ -1,4 +1,4 @@ -package com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar +package com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -8,13 +8,9 @@ import androidx.compose.material3.* import androidx.compose.ui.Modifier import androidx.navigation.NavController import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.navigation.compose.currentBackStackEntryAsState -import com.bobbyesp.spowlo.presentation.ui.common.Route @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/NavBarItem.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/bottomNavBar/NavBarItem.kt similarity index 60% rename from app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/NavBarItem.kt rename to app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/bottomNavBar/NavBarItem.kt index a9f577f8..95938c5c 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/BottomNavBar/NavBarItem.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/bottomNavBar/NavBarItem.kt @@ -1,7 +1,6 @@ -package com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar +package com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar import androidx.compose.ui.graphics.vector.ImageVector -import com.bobbyesp.spowlo.presentation.ui.common.Route data class NavBarItem( val name: String, diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt index 3dbc3a9e..dcea31ba 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt @@ -12,22 +12,13 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Home import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import com.bobbyesp.spowlo.presentation.ui.common.LocalWindowWidthState import com.bobbyesp.spowlo.presentation.ui.common.Route @@ -35,28 +26,30 @@ import com.bobbyesp.spowlo.presentation.ui.common.animatedComposable import com.bobbyesp.spowlo.presentation.ui.components.UpdateDialog import com.bobbyesp.spowlo.presentation.ui.pages.home.HomePage import com.bobbyesp.spowlo.presentation.ui.pages.home.HomeViewModel -import com.bobbyesp.spowlo.presentation.ui.pages.placeholders.PagePlaceholder import com.bobbyesp.spowlo.presentation.ui.pages.settings.SettingsPage import com.bobbyesp.spowlo.presentation.ui.pages.settings.appearence.AppearancePreferences import com.bobbyesp.spowlo.presentation.ui.pages.settings.appearence.DarkThemePreferences import com.bobbyesp.spowlo.presentation.ui.pages.settings.appearence.LanguagesPreferences -import com.bobbyesp.spowlo.util.PreferencesUtil import com.bobbyesp.spowlo.util.UpdateUtil import com.bobbyesp.spowlo.util.Utils import com.google.accompanist.navigation.animation.AnimatedNavHost -import com.google.accompanist.navigation.animation.rememberAnimatedNavController import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import com.bobbyesp.spowlo.R -import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.BottomNavBar -import com.bobbyesp.spowlo.presentation.ui.components.BottomNavBar.NavBarItem +import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderPage +import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderViewModel private const val TAG = "InitialEntry" @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class) @Composable -fun InitialEntry(homeViewModel: HomeViewModel, modifier: Modifier = Modifier, navController: NavHostController) { +fun InitialEntry(homeViewModel: HomeViewModel, + modifier: Modifier = Modifier, + navController: NavHostController, + downloaderViewModel: DownloaderViewModel, + activity: androidx.activity.ComponentActivity? = null) +{ val context = LocalContext.current val scope = rememberCoroutineScope() @@ -139,6 +132,11 @@ fun InitialEntry(homeViewModel: HomeViewModel, modifier: Modifier = Modifier, na onBackPressed() } } + animatedComposable(Route.DOWNLOADER){ + DownloaderPage(navController = navController, + downloadViewModel = downloaderViewModel, + activity = activity) + } } } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt index 7a523dda..a86b834b 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt @@ -1,11 +1,28 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page +import android.app.Activity +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Surface +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity +import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity +import com.bobbyesp.spowlo.data.auth.AuthModel +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl import com.google.accompanist.permissions.ExperimentalPermissionsApi @OptIn( @@ -16,6 +33,37 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi fun DownloaderPage( navController: NavController, downloadViewModel: DownloaderViewModel = hiltViewModel(), + activity: Activity? = null ) { + lateinit var model: AuthModel + val viewState = downloadViewModel.stateFlow.collectAsState() + with(viewState.value){ + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ){ + Surface( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + color = MaterialTheme.colorScheme.background + ){ + Column(modifier = Modifier.align(Alignment.Center)) { + Button(onClick = { + downloadViewModel.spotifyImplicitLogin(activity) + }) { + Text("Connect to Spotify (spotify-auth integration, Implicit Grant)") + } + Button(onClick = { + downloadViewModel.spotifyPkceLogin(activity) + }) { + Text("Connect to Spotify (spotify-web-api-kotlin integration, PKCE auth)") + } + } + + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt index de7f01d7..f39f7859 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt @@ -1,8 +1,14 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page +import android.app.Activity import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.lifecycle.ViewModel +import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity +import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity +import com.bobbyesp.spowlo.data.auth.AuthModel +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -16,6 +22,7 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { val stateFlow = mutableStateFlow.asStateFlow() private var currentJob: Job? = null + data class DownloaderViewState( val spotUrl: String = "", val ytUrl: String = "", @@ -36,6 +43,14 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { ),*/ ) + fun spotifyImplicitLogin(activity: Activity? = null){ + activity?.startSpotifyImplicitLoginActivity() + } + + fun spotifyPkceLogin(activity: Activity? = null){ + activity?.startSpotifyClientPkceLoginActivity(SpotifyPkceLoginActivityImpl::class.java) + } + fun updateUrl(url: String, isUrlSharingTriggered: Boolean = false) = mutableStateFlow.update { it.copy( diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7f1d8dc7..b07d5e2e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -41,4 +41,7 @@ Este tipo de arquitectura está basada en 64 bits. Esta puede correr cualquier tipo de app de Spotify (las de 32 bits son emuladas) Este tipo de arquitectura está basada en 32 bits. SOLO puede correr aplicaciones ARMEABI (32 bits) Para conocer la arquitectura de tu procesador, vé a la página principal de la app y desliza hasta abajo del todo. + Plantilla de comando + Inicio + Descargador \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43ec90c7..6229b3f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,5 +45,7 @@ This type of architecture is based on 32 bit. It can JUST run ARMEABI packages. To check your processor architecture, scroll down till the end of the main page. Command template - --no-mtime -f \"bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4] / bv*+ba/b\" + --no-mtime -f \"bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4] / bv*+ba/b\" + Home + Downloader \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 58460b49..ed83a3ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,7 @@ buildscript { val okhttpVersion by extra("5.0.0-alpha.10") val kotlinVersion by extra("1.7.10") val hiltVersion by extra("2.44") + val spotifyLibrary by extra("3.8.8") repositories { mavenCentral() From 189a71ac0b96bd23337aca05d3f2e175ed27a930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 01:36:10 +0100 Subject: [PATCH 05/12] More Spotify library implementations and created a WIP Welcome Screen. --- app/build.gradle.kts | 15 +++- app/src/main/AndroidManifest.xml | 18 ++-- .../auth/SpotifyImplicitLoginActivityImpl.kt | 20 ++++- .../web_api/utilities/VerifyLoggedInUtils.kt | 18 +--- .../spowlo/presentation/MainActivity.kt | 53 +++++++----- .../spowlo/presentation/ui/common/Route.kt | 1 + .../presentation/ui/pages/InitialEntry.kt | 32 +++++++- .../pages/downloader_page/DownloaderPage.kt | 24 +++--- .../downloader_page/DownloaderViewModel.kt | 27 +++++- .../ui/pages/welcome_page/WelcomePage.kt | 82 +++++++++++++++++++ .../welcome_page/WelcomePageViewModel.kt | 34 ++++++++ .../bobbyesp/spowlo/util/PreferencesUtil.kt | 1 + app/src/main/res/values/strings.xml | 2 + 13 files changed, 264 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePage.kt create mode 100644 app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePageViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index abfc0f55..2e9800da 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -99,21 +99,32 @@ android { "SPOTIFY_REDIRECT_URI_PKCE", "\"spowlo://spotify-pkce\"" ) + packagingOptions { + resources.excludes.add("META-INF/*.kotlin_module") + } + matchingFallbacks.add(0, "debug") + matchingFallbacks.add(1, "release") } debug { if (keystorePropertiesFile.exists()) signingConfig = signingConfigs.getByName("debug") - buildConfigField("String", + buildConfigField( + "String", "SPOTIFY_CLIENT_ID", "\"abcad8ba647d4b0ebae797a8f444ac9b\"") - buildConfigField("String", + + buildConfigField( + "String", "SPOTIFY_REDIRECT_URI_AUTH", "\"spowlo://spotify-auth\"") + buildConfigField( "String", "SPOTIFY_REDIRECT_URI_PKCE", "\"spowlo://spotify-pkce\"" ) + matchingFallbacks.add(0, "debug") + matchingFallbacks.add(1, "release") } } compileOptions { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d5670c2..d95e167c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,6 +65,7 @@ android:name="autoStoreLocales" android:value="true" /> + @@ -76,6 +77,15 @@ + + + + + + @@ -87,15 +97,7 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt index 494d833b..3868643d 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt @@ -9,6 +9,24 @@ import com.bobbyesp.spowlo.Spowlo import com.bobbyesp.spowlo.presentation.MainActivity import com.bobbyesp.spowlo.util.Utils.makeToast +/*class SpotifyImplicitLoginActivityImpl: AbstractSpotifyAppImplicitLoginActivity() { + override val state: Int = 1338 + override val clientId: String = BuildConfig.SPOTIFY_CLIENT_ID + override val redirectUri: String = BuildConfig.SPOTIFY_REDIRECT_URI_AUTH + override val useDefaultRedirectHandler: Boolean = false + override fun getRequestingScopes(): List = SpotifyScope.values().toList() + + override fun onSuccess(spotifyApi: SpotifyImplicitGrantApi) { + val model = (application as Spowlo).model + model.credentialStore.setSpotifyApi(spotifyApi) + startActivity(Intent(this, MainActivity::class.java)) + } + + override fun onFailure(errorMessage: String) { + makeToast("Auth failed: $errorMessage") + } +}*/ + class SpotifyImplicitLoginActivityImpl: AbstractSpotifyAppImplicitLoginActivity() { override val state: Int = 1337 override val clientId: String = BuildConfig.SPOTIFY_CLIENT_ID @@ -23,7 +41,7 @@ class SpotifyImplicitLoginActivityImpl: AbstractSpotifyAppImplicitLoginActivity( override fun onSuccess(spotifyApi: SpotifyImplicitGrantApi) { val model = (application as Spowlo).model model.credentialStore.setSpotifyApi(spotifyApi) + makeToast("Authentication via spotify-auth has completed. Launching MainActivity..") startActivity(Intent(this, MainActivity::class.java)) } - } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt index 8080f7dc..2760021c 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt @@ -1,6 +1,7 @@ package com.bobbyesp.spowlo.domain.spotify.web_api.utilities import android.app.Activity +import android.util.Log import com.adamratzman.spotify.SpotifyClientApi import com.adamratzman.spotify.SpotifyException import com.adamratzman.spotify.auth.SpotifyDefaultCredentialStore @@ -19,18 +20,10 @@ fun Activity.guardValidSpotifyApi( ): T? { return runBlocking { try { - val token = AuthModel.credentialStore.spotifyToken - ?: throw SpotifyException.ReAuthenticationNeededException() - val usesPkceAuth = token.refreshToken != null - val api = (if (usesPkceAuth) AuthModel.credentialStore.getSpotifyClientPkceApi() - else AuthModel.credentialStore.getSpotifyImplicitGrantApi()) - ?: throw SpotifyException.ReAuthenticationNeededException() - + val api = AuthModel.credentialStore.getSpotifyClientPkceApi() ?: throw SpotifyException.ReAuthenticationNeededException() block(api) } catch (e: SpotifyException) { e.printStackTrace() - val usesPkceAuth = AuthModel.credentialStore.spotifyToken?.refreshToken != null - if (usesPkceAuth) { val api = AuthModel.credentialStore.getSpotifyClientPkceApi()!! if (!alreadyTriedToReauthenticate) { try { @@ -57,11 +50,6 @@ fun Activity.guardValidSpotifyApi( startSpotifyClientPkceLoginActivity(SpotifyPkceLoginActivityImpl::class.java) null } - } else { - SpotifyDefaultCredentialStore.activityBackOnImplicitAuth = classBackTo - startSpotifyImplicitLoginActivity(SpotifyImplicitLoginActivityImpl::class.java) - null } } - } -} + } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index b8721db6..35bce985 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -25,10 +25,12 @@ import androidx.compose.ui.res.stringResource import androidx.core.os.LocaleListCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat +import com.adamratzman.spotify.SpotifyException import com.bobbyesp.spowlo.R import com.bobbyesp.spowlo.Spowlo.Companion.applicationScope import com.bobbyesp.spowlo.Spowlo.Companion.context import com.bobbyesp.spowlo.data.auth.AuthModel +import com.bobbyesp.spowlo.domain.spotify.web_api.utilities.guardValidSpotifyApi import com.bobbyesp.spowlo.presentation.ui.common.* import com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar.BottomNavBar import com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar.NavBarItem @@ -66,23 +68,26 @@ class MainActivity : ComponentActivity() { ) } context = this.baseContext - setContent { - val navController = rememberAnimatedNavController() - val windowSizeClass = calculateWindowSizeClass(this) - //if the current route is not in the list of routes, then hide the nav bar modifying the visible var - val visible = remember { mutableStateOf(true) } - //if current route is not home or settings, change the visible var to false - navController.addOnDestinationChangedListener { _, destination, _ -> - visible.value = destination.route in listOf(Route.HOME, Route.SETTINGS, Route.DOWNLOADER) - } - - SettingsProvider(windowSizeClass.widthSizeClass){ - SpowloTheme( - darkTheme = LocalDarkTheme.current.isDarkTheme(), - isHighContrastModeEnabled = LocalDarkTheme.current.isHighContrastModeEnabled, - seedColor = LocalSeedColor.current, - isDynamicColorEnabled = LocalDynamicColorSwitch.current, - ) { + guardValidSpotifyApi(MainActivity::class.java) { api -> + if (!api.isTokenValid(true).isValid) + throw SpotifyException.ReAuthenticationNeededException() + setContent { + val navController = rememberAnimatedNavController() + val windowSizeClass = calculateWindowSizeClass(this) + //if the current route is not in the list of routes, then hide the nav bar modifying the visible var + val visible = remember { mutableStateOf(true) } + //if current route is not home or settings, change the visible var to false + navController.addOnDestinationChangedListener { _, destination, _ -> + visible.value = + destination.route in listOf(Route.HOME, Route.SETTINGS, Route.DOWNLOADER) + } + SettingsProvider(windowSizeClass.widthSizeClass) { + SpowloTheme( + darkTheme = LocalDarkTheme.current.isDarkTheme(), + isHighContrastModeEnabled = LocalDarkTheme.current.isHighContrastModeEnabled, + seedColor = LocalSeedColor.current, + isDynamicColorEnabled = LocalDynamicColorSwitch.current, + ) { Scaffold( bottomBar = { BottomNavBar( @@ -109,19 +114,23 @@ class MainActivity : ComponentActivity() { navController.navigate(it.route) } }, - visible = visible.value + visible = visible.value ) - }){ + }) { //If the user is at a route different from home or settings, hide the bottom nav bar - InitialEntry(homeViewModel, + InitialEntry( + homeViewModel, modifier = Modifier.padding(paddingValues = it), - navController = navController, downloaderViewModel = downloaderViewModel, activity = this@MainActivity) + navController = navController, + downloaderViewModel = downloaderViewModel, + activity = this@MainActivity + ) } } } } - } + } companion object { private const val TAG = "MainActivity" diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt index 05d0031f..706607f6 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt @@ -13,5 +13,6 @@ class Route { const val DARK_THEME_SELECTOR = "dark_theme_selector" const val DOWNLOADER = "downloader" const val TEMPLATE = "template" + const val WELCOME_PAGE = "welcome_page" } } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt index dcea31ba..87a2c270 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt @@ -12,6 +12,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.* @@ -19,6 +21,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.bobbyesp.spowlo.presentation.ui.common.LocalWindowWidthState import com.bobbyesp.spowlo.presentation.ui.common.Route @@ -37,8 +40,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import com.bobbyesp.spowlo.R +import com.bobbyesp.spowlo.presentation.MainActivity import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderPage import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderViewModel +import com.bobbyesp.spowlo.presentation.ui.pages.welcome_page.WelcomePage +import com.bobbyesp.spowlo.util.PreferencesUtil +import com.bobbyesp.spowlo.util.PreferencesUtil.IS_LOGGED private const val TAG = "InitialEntry" @@ -58,6 +65,7 @@ fun InitialEntry(homeViewModel: HomeViewModel, var showUpdateDialog by rememberSaveable { mutableStateOf(false) } var currentDownloadStatus by remember { mutableStateOf(UpdateUtil.DownloadStatus.NotYet as UpdateUtil.DownloadStatus) } var latestRelease by remember { mutableStateOf(UpdateUtil.LatestRelease()) } + val downloaderPageLoaded by remember { mutableStateOf(downloaderViewModel.stateFlow.value.loaded) } val settings = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { UpdateUtil.installLatestApk() @@ -82,7 +90,9 @@ fun InitialEntry(homeViewModel: HomeViewModel, } } - val viewState = homeViewModel.stateFlow.collectAsState() + val homeviewState = homeViewModel.stateFlow.collectAsState() + val downloaderViewState = downloaderViewModel.stateFlow.collectAsState() + Box(modifier = modifier){ Box( @@ -101,11 +111,11 @@ fun InitialEntry(homeViewModel: HomeViewModel, ) .align(Alignment.Center), navController = navController, - startDestination = Route.HOME) { + startDestination = routeIfLogged()) { animatedComposable(Route.HOME){ HomePage(navController = navController, homeViewModel = homeViewModel) - if (!viewState.value.loaded){ + if (!homeviewState.value.loaded){ homeViewModel.setup() } } @@ -132,12 +142,18 @@ fun InitialEntry(homeViewModel: HomeViewModel, onBackPressed() } } + animatedComposable(Route.WELCOME_PAGE){ + WelcomePage(navController = navController, activity = activity) + } animatedComposable(Route.DOWNLOADER){ DownloaderPage(navController = navController, downloadViewModel = downloaderViewModel, activity = activity) - } + if(!downloaderPageLoaded){ + downloaderViewModel.setup() + } + } } } } @@ -185,4 +201,12 @@ fun InitialEntry(homeViewModel: HomeViewModel, downloadStatus = currentDownloadStatus ) } +} + +fun routeIfLogged(): String{ + return if (PreferencesUtil.getValue(IS_LOGGED)){ + Route.HOME + } else { + Route.WELCOME_PAGE + } } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt index a86b834b..20c38708 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt @@ -2,6 +2,7 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page import android.app.Activity import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -50,19 +51,22 @@ fun DownloaderPage( .background(MaterialTheme.colorScheme.background), color = MaterialTheme.colorScheme.background ){ - Column(modifier = Modifier.align(Alignment.Center)) { - Button(onClick = { - downloadViewModel.spotifyImplicitLogin(activity) - }) { - Text("Connect to Spotify (spotify-auth integration, Implicit Grant)") + if(!logged) { + Column(modifier = Modifier.fillMaxSize()) { + Button(onClick = { + downloadViewModel.spotifyPkceLogin(activity) + }) { + Text("Connect to Spotify (spotify-web-api-kotlin integration, PKCE auth)") + } } - Button(onClick = { - downloadViewModel.spotifyPkceLogin(activity) - }) { - Text("Connect to Spotify (spotify-web-api-kotlin integration, PKCE auth)") + } else{ + Column(modifier = Modifier.fillMaxSize().align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center) { + Text(text = "Logged", + modifier = Modifier.align(Alignment.CenterHorizontally)) } } - } } } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt index f39f7859..949b6d3e 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt @@ -1,14 +1,17 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page import android.app.Activity +import android.util.Log import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.lifecycle.ViewModel import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity +import com.adamratzman.spotify.notifications.SpotifyBroadcastEventData import com.bobbyesp.spowlo.data.auth.AuthModel import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl +import com.bobbyesp.spowlo.util.Utils.makeToast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -22,7 +25,6 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { val stateFlow = mutableStateFlow.asStateFlow() private var currentJob: Job? = null - data class DownloaderViewState( val spotUrl: String = "", val ytUrl: String = "", @@ -37,12 +39,35 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { val downloadingTaskId: String = "", val isUrlSharingTriggered: Boolean = false, val drawerState: Boolean = false, + val logged: Boolean = false, + val loaded: Boolean = false, + val recentBroadcasts: List = mutableListOf() /*val drawerState: ModalBottomSheetState = ModalBottomSheetState( ModalBottomSheetValue.Hidden, isSkipHalfExpanded = true ),*/ ) + fun setup(){ + currentJob = CoroutineScope(Job()).launch { + mutableStateFlow.update { + if(AuthModel.credentialStore.spotifyToken != null){ + it.copy(logged = true) + } else { + it.copy(logged = false) + } + } + if(AuthModel.credentialStore.spotifyToken == null) + { + spotifyPkceLogin() + Log.d("DownloaderViewModel", "Spotify token is null, relogging") + } + mutableStateFlow.update { + it.copy(loaded = true) + } + } + } + fun spotifyImplicitLogin(activity: Activity? = null){ activity?.startSpotifyImplicitLoginActivity() } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePage.kt new file mode 100644 index 00000000..c8740c9a --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePage.kt @@ -0,0 +1,82 @@ +package com.bobbyesp.spowlo.presentation.ui.pages.welcome_page + +import android.app.Activity +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.Surface +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.bobbyesp.spowlo.R +import com.bobbyesp.spowlo.presentation.ui.common.Route + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun WelcomePage( + navController: NavController, + viewModel: WelcomePageViewModel = hiltViewModel(), + activity: Activity? = null){ + + val viewState = viewModel.stateFlow.collectAsState() + /*Welcome Page with one button to login to spotify or enter without login + *If logged, navigate to main page + If not logged, navigate to login page*/ + with(viewState.value) { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + ) { + Surface( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + color = MaterialTheme.colorScheme.background, + ) { + Column( + modifier = Modifier + .fillMaxSize() + .align(Alignment.Center) + .padding(25.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + Text( + modifier = Modifier, + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineLarge + ) + } + Column( + modifier = Modifier + .align(Alignment.Center) + .padding(bottom = 20.dp, top = 20.dp, start = 10.dp, end = 10.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Bottom + ) { + Button(onClick = { + viewModel.loginToSpotify(activity, navController) + }) { + Text(text = stringResource(R.string.login)) + } + Spacer(modifier = Modifier.height(10.dp)) + + Button(onClick = { navController.navigate(Route.HOME) }) { + Text(text = stringResource(R.string.enter_without_login)) + } + + } + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePageViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePageViewModel.kt new file mode 100644 index 00000000..18f392fe --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/welcome_page/WelcomePageViewModel.kt @@ -0,0 +1,34 @@ +package com.bobbyesp.spowlo.presentation.ui.pages.welcome_page + +import android.app.Activity +import androidx.lifecycle.ViewModel +import androidx.navigation.NavController +import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity +import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl +import com.bobbyesp.spowlo.presentation.ui.common.Route +import com.bobbyesp.spowlo.util.PreferencesUtil +import com.bobbyesp.spowlo.util.PreferencesUtil.IS_LOGGED +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +class WelcomePageViewModel@Inject constructor() : ViewModel() { + private val mutableStateFlow = MutableStateFlow(WelcomePageViewState()) + val stateFlow = mutableStateFlow.asStateFlow() + private var currentJob: Job? = null + + data class WelcomePageViewState( + val isLogged: Boolean = false, + val isLoaded: Boolean = false + ) + + fun loginToSpotify(activity: Activity? = null, navController: NavController){ + activity?.startSpotifyClientPkceLoginActivity(SpotifyPkceLoginActivityImpl::class.java) + PreferencesUtil.updateValue(IS_LOGGED, true) + navController.navigate(Route.HOME) + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt b/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt index 95656e9f..94a14fa9 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/util/PreferencesUtil.kt @@ -42,6 +42,7 @@ object PreferencesUtil { const val AUDIO_DIRECTORY = "audio_directory" const val TEMPLATE_INDEX = "template_index" const val TEMPLATE = "template" + const val IS_LOGGED = "isLogged" const val SYSTEM_DEFAULT = 0 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6229b3f1..7f2216b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,4 +48,6 @@ --no-mtime -f \"bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4] / bv*+ba/b\" Home Downloader + Log in into Spotify + Enter without login \ No newline at end of file From f2d95e0291bf5980130469ca60359041d09297aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 01:54:41 +0100 Subject: [PATCH 06/12] Fixed App crashing --- .../java/com/bobbyesp/spowlo/presentation/MainActivity.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index 35bce985..b4ca3c28 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -68,9 +68,7 @@ class MainActivity : ComponentActivity() { ) } context = this.baseContext - guardValidSpotifyApi(MainActivity::class.java) { api -> - if (!api.isTokenValid(true).isValid) - throw SpotifyException.ReAuthenticationNeededException() + setContent { val navController = rememberAnimatedNavController() val windowSizeClass = calculateWindowSizeClass(this) @@ -130,7 +128,6 @@ class MainActivity : ComponentActivity() { } } } - } companion object { private const val TAG = "MainActivity" From 394a043b2b9efd28e267c9b151b21d0a36031a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 12:12:36 +0100 Subject: [PATCH 07/12] Implemented song searching but UI showing not made yet --- .../web_api/utilities/VerifyLoggedInUtils.kt | 2 +- .../spowlo/presentation/MainActivity.kt | 5 +- .../pages/downloader_page/DownloaderPage.kt | 73 ++++++++++++++++--- .../downloader_page/DownloaderViewModel.kt | 54 ++++++++++++-- 4 files changed, 114 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt index 2760021c..ad6c89ff 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt @@ -14,7 +14,7 @@ import com.bobbyesp.spowlo.domain.spotify.web_api.auth.pkceClassBackTo import kotlinx.coroutines.runBlocking fun Activity.guardValidSpotifyApi( - classBackTo: Class, + classBackTo: Class? = null, alreadyTriedToReauthenticate: Boolean = false, block: suspend (api: SpotifyClientApi) -> T ): T? { diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index b4ca3c28..3d199257 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -61,14 +61,13 @@ class MainActivity : ComponentActivity() { v.setPadding(0, 0, 0, 0) insets } - runBlocking { + context = this.baseContext + runBlocking { if (Build.VERSION.SDK_INT < 33) AppCompatDelegate.setApplicationLocales( LocaleListCompat.forLanguageTags(PreferencesUtil.getLanguageConfiguration()) ) } - context = this.baseContext - setContent { val navController = rememberAnimatedNavController() val windowSizeClass = calculateWindowSizeClass(this) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt index 20c38708..de1d5717 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt @@ -2,12 +2,11 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page import android.app.Activity import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Surface +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Clear +import androidx.compose.material.icons.rounded.Search import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -17,6 +16,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.focusTarget import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity @@ -60,14 +60,69 @@ fun DownloaderPage( } } } else{ - Column(modifier = Modifier.fillMaxSize().align(Alignment.Center), + Column(modifier = Modifier + .fillMaxSize() + .align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { - Text(text = "Logged", - modifier = Modifier.align(Alignment.CenterHorizontally)) + Column(modifier = Modifier.fillMaxWidth()) { + SearchSongTextBox( + songName = downloadViewModel.searchQuery.value, + onValueChange = downloadViewModel::onSearch + ) + } } } } } } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SearchSongTextBox( + songName: String, + onValueChange: (String) -> Unit +){ + OutlinedTextField( + value = songName, + onValueChange = onValueChange, + modifier = Modifier.fillMaxWidth(), + placeholder = { + Text("Search song") + }, + leadingIcon = { + Icon(imageVector = Icons.Rounded.Search, contentDescription = null) + }, + trailingIcon = { + if(songName.isNotEmpty()){ + IconButton(onClick = { + onValueChange("") + }) { + Icon(imageVector = Icons.Rounded.Clear, contentDescription = null) + } + } + }, + singleLine = true, + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedBorderColor = MaterialTheme.colorScheme.primary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.primary, + cursorColor = MaterialTheme.colorScheme.primary, + leadingIconColor = MaterialTheme.colorScheme.primary, + trailingIconColor = MaterialTheme.colorScheme.primary, + textColor = MaterialTheme.colorScheme.primary, + disabledLabelColor = MaterialTheme.colorScheme.primary, + disabledBorderColor = MaterialTheme.colorScheme.primary, + disabledLeadingIconColor = MaterialTheme.colorScheme.primary, + disabledTrailingIconColor = MaterialTheme.colorScheme.primary, + errorBorderColor = MaterialTheme.colorScheme.primary, + errorLabelColor = MaterialTheme.colorScheme.primary, + errorCursorColor = MaterialTheme.colorScheme.primary, + errorLeadingIconColor = MaterialTheme.colorScheme.primary, + errorTrailingIconColor = MaterialTheme.colorScheme.primary, + backgroundColor = MaterialTheme.colorScheme.background, + ) + ) } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt index 949b6d3e..f0c63596 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt @@ -4,16 +4,23 @@ import android.app.Activity import android.util.Log import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity +import com.adamratzman.spotify.models.Track import com.adamratzman.spotify.notifications.SpotifyBroadcastEventData import com.bobbyesp.spowlo.data.auth.AuthModel import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl +import com.bobbyesp.spowlo.domain.spotify.web_api.utilities.guardValidSpotifyApi +import com.bobbyesp.spowlo.presentation.MainActivity import com.bobbyesp.spowlo.util.Utils.makeToast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -25,6 +32,10 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { val stateFlow = mutableStateFlow.asStateFlow() private var currentJob: Job? = null + private val _searchQuery = mutableStateOf("") + val searchQuery: State = _searchQuery + + data class DownloaderViewState( val spotUrl: String = "", val ytUrl: String = "", @@ -41,24 +52,25 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { val drawerState: Boolean = false, val logged: Boolean = false, val loaded: Boolean = false, - val recentBroadcasts: List = mutableListOf() + val recentBroadcasts: List = mutableListOf(), + val listOfTracks: List = mutableListOf(), + val activity: Activity? = MainActivity(), /*val drawerState: ModalBottomSheetState = ModalBottomSheetState( ModalBottomSheetValue.Hidden, isSkipHalfExpanded = true ),*/ ) - fun setup(){ + fun setup() { currentJob = CoroutineScope(Job()).launch { mutableStateFlow.update { - if(AuthModel.credentialStore.spotifyToken != null){ + if (AuthModel.credentialStore.spotifyToken != null) { it.copy(logged = true) } else { it.copy(logged = false) } } - if(AuthModel.credentialStore.spotifyToken == null) - { + if (AuthModel.credentialStore.spotifyToken == null) { spotifyPkceLogin() Log.d("DownloaderViewModel", "Spotify token is null, relogging") } @@ -68,11 +80,38 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { } } - fun spotifyImplicitLogin(activity: Activity? = null){ + fun onSearch(query: String, activity: Activity? = DownloaderViewState().activity) { + Log.d("DownloaderViewModel", "onSearch: $activity") + _searchQuery.value = query + currentJob?.cancel() + currentJob = viewModelScope.launch { + delay(500L) + try { + val tracks = + activity?.guardValidSpotifyApi(classBackTo = MainActivity::class.java) { api -> + //if query is not empty, search for it + if (query.isNotEmpty()) { + api.search.searchTrack(query).items + } else { + //if query is empty, make the tracks list empty + listOf() + } + } + mutableStateFlow.update { + it.copy(listOfTracks = tracks ?: listOf()) + } + Log.d("DownloaderViewModel", "Search query: $tracks") + } catch (e: Exception) { + Log.d("DownloaderViewModel", "Error: $e") + } + } + } + + fun spotifyImplicitLogin(activity: Activity? = null) { activity?.startSpotifyImplicitLoginActivity() } - fun spotifyPkceLogin(activity: Activity? = null){ + fun spotifyPkceLogin(activity: Activity? = null) { activity?.startSpotifyClientPkceLoginActivity(SpotifyPkceLoginActivityImpl::class.java) } @@ -83,6 +122,7 @@ class DownloaderViewModel @Inject constructor() : ViewModel() { isUrlSharingTriggered = isUrlSharingTriggered ) } + fun hideDialog(scope: CoroutineScope, isDialog: Boolean) { scope.launch { if (isDialog) From 505a5dbcbbd31dc93398f9c101e617dcf7ad83bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 17:10:04 +0100 Subject: [PATCH 08/12] Song searching made! --- .../ui/components/songs/TrackItem.kt | 63 +++++++++++++++++ .../pages/downloader_page/DownloaderPage.kt | 70 ++++++++++++++----- 2 files changed, 114 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/songs/TrackItem.kt diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/songs/TrackItem.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/songs/TrackItem.kt new file mode 100644 index 00000000..ae9d753c --- /dev/null +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/components/songs/TrackItem.kt @@ -0,0 +1,63 @@ +package com.bobbyesp.spowlo.presentation.ui.components.songs + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.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.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.adamratzman.spotify.models.Track + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TrackItem( + modifier: Modifier = Modifier, + track: Track, + onClick: (Track) -> Unit +) { + Surface( + modifier = modifier.clickable(onClick = { onClick(track) }) + ) { + Row( + modifier = Modifier.padding(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + AsyncImage( + modifier = Modifier + .size(100.dp) + .padding(start = 10.dp, end = 15.dp, top = 5.dp, bottom = 5.dp) + .clip(MaterialTheme.shapes.medium), + contentScale = ContentScale.Fit, + model = ImageRequest.Builder(LocalContext.current) + .data(track.album.images.firstOrNull()?.url) + .crossfade(true) + .build(), + contentDescription = "Album cover" + ) + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = track.name, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge + ) + Text( + text = track.artists.joinToString(", ") { it.name }, + style = MaterialTheme.typography.bodySmall + ) + + } + } + + } +} diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt index de1d5717..adf0212f 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt @@ -1,8 +1,12 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page import android.app.Activity +import android.content.Intent +import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Clear @@ -16,14 +20,15 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusTarget + +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController -import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity -import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity +import com.bobbyesp.spowlo.Spowlo.Companion.context + import com.bobbyesp.spowlo.data.auth.AuthModel -import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl -import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl +import com.bobbyesp.spowlo.presentation.ui.components.songs.TrackItem import com.google.accompanist.permissions.ExperimentalPermissionsApi @OptIn( @@ -39,19 +44,19 @@ fun DownloaderPage( lateinit var model: AuthModel val viewState = downloadViewModel.stateFlow.collectAsState() - with(viewState.value){ + with(viewState.value) { Box( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) - ){ + ) { Surface( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background), - color = MaterialTheme.colorScheme.background - ){ - if(!logged) { + color = MaterialTheme.colorScheme.background, + ) { + if (!logged) { Column(modifier = Modifier.fillMaxSize()) { Button(onClick = { downloadViewModel.spotifyPkceLogin(activity) @@ -59,17 +64,43 @@ fun DownloaderPage( Text("Connect to Spotify (spotify-web-api-kotlin integration, PKCE auth)") } } - } else{ - Column(modifier = Modifier - .fillMaxSize() - .align(Alignment.Center), + } else { + Column( + modifier = Modifier + .fillMaxSize() + .align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center) { - Column(modifier = Modifier.fillMaxWidth()) { + verticalArrangement = Arrangement.Top + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + verticalArrangement = Arrangement.Top + ) { SearchSongTextBox( songName = downloadViewModel.searchQuery.value, onValueChange = downloadViewModel::onSearch ) + Spacer(modifier = Modifier.height(8.dp)) + LazyColumn { + items( + items = listOfTracks, itemContent = { track -> + TrackItem(track = track, onClick = { + val browserIntent = + Intent( + Intent.ACTION_VIEW, + Uri.parse(track.externalUrls.first { it.name == "spotify" }.url) + ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ContextCompat.startActivity( + context, + browserIntent, + null + ) + }) + Divider() + }) + } } } } @@ -78,12 +109,13 @@ fun DownloaderPage( } } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchSongTextBox( songName: String, onValueChange: (String) -> Unit -){ +) { OutlinedTextField( value = songName, onValueChange = onValueChange, @@ -95,7 +127,7 @@ fun SearchSongTextBox( Icon(imageVector = Icons.Rounded.Search, contentDescription = null) }, trailingIcon = { - if(songName.isNotEmpty()){ + if (songName.isNotEmpty()) { IconButton(onClick = { onValueChange("") }) { @@ -122,7 +154,7 @@ fun SearchSongTextBox( errorCursorColor = MaterialTheme.colorScheme.primary, errorLeadingIconColor = MaterialTheme.colorScheme.primary, errorTrailingIconColor = MaterialTheme.colorScheme.primary, - backgroundColor = MaterialTheme.colorScheme.background, + backgroundColor = MaterialTheme.colorScheme.surfaceVariant, ) ) } \ No newline at end of file From a704773a4ce094014a707d64383318261852fc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 17:25:05 +0100 Subject: [PATCH 09/12] Renamed downloader feature to searcher. --- .../spowlo/presentation/MainActivity.kt | 23 +++++++++--------- .../spowlo/presentation/ui/common/Route.kt | 1 + .../presentation/ui/pages/InitialEntry.kt | 24 ++++++++----------- .../{DownloaderPage.kt => SearcherPage.kt} | 20 +++++++++------- ...oaderViewModel.kt => SearcherViewModel.kt} | 5 +--- app/src/main/res/values-es/strings.xml | 4 ++++ app/src/main/res/values/strings.xml | 2 ++ 7 files changed, 42 insertions(+), 37 deletions(-) rename app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/{DownloaderPage.kt => SearcherPage.kt} (91%) rename app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/{DownloaderViewModel.kt => SearcherViewModel.kt} (96%) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index 3d199257..66cefffd 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold @@ -25,17 +26,14 @@ import androidx.compose.ui.res.stringResource import androidx.core.os.LocaleListCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat -import com.adamratzman.spotify.SpotifyException import com.bobbyesp.spowlo.R import com.bobbyesp.spowlo.Spowlo.Companion.applicationScope import com.bobbyesp.spowlo.Spowlo.Companion.context -import com.bobbyesp.spowlo.data.auth.AuthModel -import com.bobbyesp.spowlo.domain.spotify.web_api.utilities.guardValidSpotifyApi import com.bobbyesp.spowlo.presentation.ui.common.* import com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar.BottomNavBar import com.bobbyesp.spowlo.presentation.ui.components.bottomNavBar.NavBarItem import com.bobbyesp.spowlo.presentation.ui.pages.InitialEntry -import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderViewModel +import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.SearcherViewModel import com.bobbyesp.spowlo.presentation.ui.pages.home.HomeViewModel import com.bobbyesp.spowlo.presentation.ui.theme.SpowloTheme import com.bobbyesp.spowlo.util.PreferencesUtil @@ -48,7 +46,7 @@ import kotlinx.coroutines.runBlocking @AndroidEntryPoint class MainActivity : ComponentActivity() { private val homeViewModel: HomeViewModel by viewModels() - private val downloaderViewModel: DownloaderViewModel by viewModels() + private val searcherViewModel: SearcherViewModel by viewModels() @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalMaterial3Api::class, @@ -73,10 +71,13 @@ class MainActivity : ComponentActivity() { val windowSizeClass = calculateWindowSizeClass(this) //if the current route is not in the list of routes, then hide the nav bar modifying the visible var val visible = remember { mutableStateOf(true) } - //if current route is not home or settings, change the visible var to false + + /*if current route is not home or settings, change the visible var to false + * INFO: Hide the navbar when the user is in a page that is not the ones that are in the navbar + */ navController.addOnDestinationChangedListener { _, destination, _ -> visible.value = - destination.route in listOf(Route.HOME, Route.SETTINGS, Route.DOWNLOADER) + destination.route in listOf(Route.HOME, Route.SETTINGS, Route.SEARCHER_PAGE) } SettingsProvider(windowSizeClass.widthSizeClass) { SpowloTheme( @@ -100,9 +101,9 @@ class MainActivity : ComponentActivity() { route = Route.SETTINGS, ), NavBarItem( - name = stringResource(id = R.string.downloader), - icon = Icons.Filled.Download, - route = Route.DOWNLOADER, + name = stringResource(id = R.string.searcher), + icon = Icons.Filled.Search, + route = Route.SEARCHER_PAGE, ), ), navController = navController, onItemClicked = { @@ -119,7 +120,7 @@ class MainActivity : ComponentActivity() { homeViewModel, modifier = Modifier.padding(paddingValues = it), navController = navController, - downloaderViewModel = downloaderViewModel, + searcherViewModel = searcherViewModel, activity = this@MainActivity ) } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt index 706607f6..d99fe165 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/common/Route.kt @@ -12,6 +12,7 @@ class Route { const val ABOUT_SETTINGS = "about_settings" const val DARK_THEME_SELECTOR = "dark_theme_selector" const val DOWNLOADER = "downloader" + const val SEARCHER_PAGE = "searcher_page" const val TEMPLATE = "template" const val WELCOME_PAGE = "welcome_page" } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt index 87a2c270..d8cf3979 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/InitialEntry.kt @@ -12,8 +12,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.* @@ -21,7 +19,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.bobbyesp.spowlo.presentation.ui.common.LocalWindowWidthState import com.bobbyesp.spowlo.presentation.ui.common.Route @@ -40,9 +37,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import com.bobbyesp.spowlo.R -import com.bobbyesp.spowlo.presentation.MainActivity -import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderPage -import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.DownloaderViewModel +import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.SearcherPage +import com.bobbyesp.spowlo.presentation.ui.pages.downloader_page.SearcherViewModel import com.bobbyesp.spowlo.presentation.ui.pages.welcome_page.WelcomePage import com.bobbyesp.spowlo.util.PreferencesUtil import com.bobbyesp.spowlo.util.PreferencesUtil.IS_LOGGED @@ -54,7 +50,7 @@ private const val TAG = "InitialEntry" fun InitialEntry(homeViewModel: HomeViewModel, modifier: Modifier = Modifier, navController: NavHostController, - downloaderViewModel: DownloaderViewModel, + searcherViewModel: SearcherViewModel, activity: androidx.activity.ComponentActivity? = null) { @@ -65,7 +61,7 @@ fun InitialEntry(homeViewModel: HomeViewModel, var showUpdateDialog by rememberSaveable { mutableStateOf(false) } var currentDownloadStatus by remember { mutableStateOf(UpdateUtil.DownloadStatus.NotYet as UpdateUtil.DownloadStatus) } var latestRelease by remember { mutableStateOf(UpdateUtil.LatestRelease()) } - val downloaderPageLoaded by remember { mutableStateOf(downloaderViewModel.stateFlow.value.loaded) } + val searcherPageLoaded by remember { mutableStateOf(searcherViewModel.stateFlow.value.loaded) } val settings = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { UpdateUtil.installLatestApk() @@ -91,7 +87,7 @@ fun InitialEntry(homeViewModel: HomeViewModel, } val homeviewState = homeViewModel.stateFlow.collectAsState() - val downloaderViewState = downloaderViewModel.stateFlow.collectAsState() + val searcherViewState = searcherViewModel.stateFlow.collectAsState() Box(modifier = modifier){ @@ -145,13 +141,13 @@ fun InitialEntry(homeViewModel: HomeViewModel, animatedComposable(Route.WELCOME_PAGE){ WelcomePage(navController = navController, activity = activity) } - animatedComposable(Route.DOWNLOADER){ - DownloaderPage(navController = navController, - downloadViewModel = downloaderViewModel, + animatedComposable(Route.SEARCHER_PAGE){ + SearcherPage(navController = navController, + searcherViewModel = searcherViewModel, activity = activity) - if(!downloaderPageLoaded){ - downloaderViewModel.setup() + if(!searcherPageLoaded){ + searcherViewModel.setup() } } } diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherPage.kt similarity index 91% rename from app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt rename to app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherPage.kt index adf0212f..caea087b 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderPage.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherPage.kt @@ -20,11 +20,13 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import com.bobbyesp.spowlo.R import com.bobbyesp.spowlo.Spowlo.Companion.context import com.bobbyesp.spowlo.data.auth.AuthModel @@ -36,13 +38,13 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class ) @Composable -fun DownloaderPage( +fun SearcherPage( navController: NavController, - downloadViewModel: DownloaderViewModel = hiltViewModel(), + searcherViewModel: SearcherViewModel = hiltViewModel(), activity: Activity? = null ) { lateinit var model: AuthModel - val viewState = downloadViewModel.stateFlow.collectAsState() + val viewState = searcherViewModel.stateFlow.collectAsState() with(viewState.value) { Box( @@ -59,7 +61,7 @@ fun DownloaderPage( if (!logged) { Column(modifier = Modifier.fillMaxSize()) { Button(onClick = { - downloadViewModel.spotifyPkceLogin(activity) + searcherViewModel.spotifyPkceLogin(activity) }) { Text("Connect to Spotify (spotify-web-api-kotlin integration, PKCE auth)") } @@ -79,8 +81,8 @@ fun DownloaderPage( verticalArrangement = Arrangement.Top ) { SearchSongTextBox( - songName = downloadViewModel.searchQuery.value, - onValueChange = downloadViewModel::onSearch + songName = searcherViewModel.searchQuery.value, + onValueChange = searcherViewModel::onSearch, ) Spacer(modifier = Modifier.height(8.dp)) LazyColumn { @@ -119,9 +121,11 @@ fun SearchSongTextBox( OutlinedTextField( value = songName, onValueChange = onValueChange, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(4.dp), placeholder = { - Text("Search song") + Text(stringResource(id = R.string.search_song)) }, leadingIcon = { Icon(imageVector = Icons.Rounded.Search, contentDescription = null) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherViewModel.kt similarity index 96% rename from app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt rename to app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherViewModel.kt index f0c63596..8bb892f7 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/DownloaderViewModel.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherViewModel.kt @@ -2,8 +2,6 @@ package com.bobbyesp.spowlo.presentation.ui.pages.downloader_page import android.app.Activity import android.util.Log -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.ModalBottomSheetValue import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel @@ -17,7 +15,6 @@ import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActiv import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl import com.bobbyesp.spowlo.domain.spotify.web_api.utilities.guardValidSpotifyApi import com.bobbyesp.spowlo.presentation.MainActivity -import com.bobbyesp.spowlo.util.Utils.makeToast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -27,7 +24,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject -class DownloaderViewModel @Inject constructor() : ViewModel() { +class SearcherViewModel @Inject constructor() : ViewModel() { private val mutableStateFlow = MutableStateFlow(DownloaderViewState()) val stateFlow = mutableStateFlow.asStateFlow() private var currentJob: Job? = null diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b07d5e2e..d808865f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -44,4 +44,8 @@ Plantilla de comando Inicio Descargador + Inicia sesión en Spotify + Entra sin iniciar sesión + Buscador + Busca una canción \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f2216b9..0764b5ee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,4 +50,6 @@ Downloader Log in into Spotify Enter without login + Searcher + Search a song \ No newline at end of file From 033b580bbc9e7749c78b2f26881fa6f7a8e73ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 17:44:03 +0100 Subject: [PATCH 10/12] Fixed CI (maybe) --- app/src/main/AndroidManifest.xml | 12 ----- .../auth/SpotifyImplicitLoginActivityImpl.kt | 47 ------------------- .../auth/SpotifyPkceLoginActivityImpl.kt | 5 +- .../web_api/utilities/VerifyLoggedInUtils.kt | 4 -- .../downloader_page/SearcherViewModel.kt | 5 -- app/src/main/res/values/strings.xml | 3 ++ 6 files changed, 6 insertions(+), 70 deletions(-) delete mode 100644 app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyImplicitLoginActivityImpl.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d95e167c..1bcb69a9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,18 +65,6 @@ android:name="autoStoreLocales" android:value="true" /> - - - - - - - - - - = SpotifyScope.values().toList() - - override fun onSuccess(spotifyApi: SpotifyImplicitGrantApi) { - val model = (application as Spowlo).model - model.credentialStore.setSpotifyApi(spotifyApi) - startActivity(Intent(this, MainActivity::class.java)) - } - - override fun onFailure(errorMessage: String) { - makeToast("Auth failed: $errorMessage") - } -}*/ - -class SpotifyImplicitLoginActivityImpl: AbstractSpotifyAppImplicitLoginActivity() { - override val state: Int = 1337 - override val clientId: String = BuildConfig.SPOTIFY_CLIENT_ID - override val redirectUri: String = BuildConfig.SPOTIFY_REDIRECT_URI_AUTH - override val useDefaultRedirectHandler: Boolean = false - override fun getRequestingScopes(): List = SpotifyScope.values().toList() - - override fun onFailure(errorMessage: String) { - makeToast("Auth failed: $errorMessage") - } - - override fun onSuccess(spotifyApi: SpotifyImplicitGrantApi) { - val model = (application as Spowlo).model - model.credentialStore.setSpotifyApi(spotifyApi) - makeToast("Authentication via spotify-auth has completed. Launching MainActivity..") - startActivity(Intent(this, MainActivity::class.java)) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt index bdaf695c..6282c0f6 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt @@ -5,6 +5,7 @@ import com.adamratzman.spotify.SpotifyClientApi import com.adamratzman.spotify.SpotifyScope import com.adamratzman.spotify.auth.pkce.AbstractSpotifyPkceLoginActivity import com.bobbyesp.spowlo.BuildConfig +import com.bobbyesp.spowlo.R import com.bobbyesp.spowlo.Spowlo import com.bobbyesp.spowlo.presentation.MainActivity import com.bobbyesp.spowlo.util.Utils.makeToast @@ -12,8 +13,8 @@ import com.bobbyesp.spowlo.util.Utils.makeToast internal var pkceClassBackTo: Class? = null class SpotifyPkceLoginActivityImpl: AbstractSpotifyPkceLoginActivity() { - override val clientId = BuildConfig.SPOTIFY_CLIENT_ID - override val redirectUri = BuildConfig.SPOTIFY_REDIRECT_URI_PKCE + override val clientId = R.string.SPOTIFY_CLIENT_ID.toString() + override val redirectUri = R.string.SPOTIFY_REDIRECT_URI_PKCE.toString() override val scopes = SpotifyScope.values().toList() override fun onFailure(exception: Exception) { diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt index ad6c89ff..f4b37c3c 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/utilities/VerifyLoggedInUtils.kt @@ -1,14 +1,10 @@ package com.bobbyesp.spowlo.domain.spotify.web_api.utilities import android.app.Activity -import android.util.Log import com.adamratzman.spotify.SpotifyClientApi import com.adamratzman.spotify.SpotifyException -import com.adamratzman.spotify.auth.SpotifyDefaultCredentialStore -import com.adamratzman.spotify.auth.implicit.startSpotifyImplicitLoginActivity import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity import com.bobbyesp.spowlo.data.auth.AuthModel -import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl import com.bobbyesp.spowlo.domain.spotify.web_api.auth.pkceClassBackTo import kotlinx.coroutines.runBlocking diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherViewModel.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherViewModel.kt index 8bb892f7..6aaef123 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherViewModel.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/ui/pages/downloader_page/SearcherViewModel.kt @@ -11,7 +11,6 @@ import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity import com.adamratzman.spotify.models.Track import com.adamratzman.spotify.notifications.SpotifyBroadcastEventData import com.bobbyesp.spowlo.data.auth.AuthModel -import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyImplicitLoginActivityImpl import com.bobbyesp.spowlo.domain.spotify.web_api.auth.SpotifyPkceLoginActivityImpl import com.bobbyesp.spowlo.domain.spotify.web_api.utilities.guardValidSpotifyApi import com.bobbyesp.spowlo.presentation.MainActivity @@ -104,10 +103,6 @@ class SearcherViewModel @Inject constructor() : ViewModel() { } } - fun spotifyImplicitLogin(activity: Activity? = null) { - activity?.startSpotifyImplicitLoginActivity() - } - fun spotifyPkceLogin(activity: Activity? = null) { activity?.startSpotifyClientPkceLoginActivity(SpotifyPkceLoginActivityImpl::class.java) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0764b5ee..ae8ea0bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,4 +52,7 @@ Enter without login Searcher Search a song + spowlo://spotify-auth + spowlo://spotify-pkce + abcad8ba647d4b0ebae797a8f444ac9b \ No newline at end of file From 1e9ae4ddd2ab3f76ca81f3721807d25101f016bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 18:08:26 +0100 Subject: [PATCH 11/12] That fix broke the code kek so rollback [skip ci] --- app/build.gradle.kts | 4 ++-- .../spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt | 5 ++--- app/src/main/res/values/strings.xml | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2e9800da..c18b214d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ plugins { apply(plugin = "dagger.hilt.android.plugin") val versionMajor = 0 -val versionMinor = 1 -val versionPatch = 3 +val versionMinor = 2 +val versionPatch = 0 val versionBuild = 0 val isStable = true diff --git a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt index 6282c0f6..bdaf695c 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/domain/spotify/web_api/auth/SpotifyPkceLoginActivityImpl.kt @@ -5,7 +5,6 @@ import com.adamratzman.spotify.SpotifyClientApi import com.adamratzman.spotify.SpotifyScope import com.adamratzman.spotify.auth.pkce.AbstractSpotifyPkceLoginActivity import com.bobbyesp.spowlo.BuildConfig -import com.bobbyesp.spowlo.R import com.bobbyesp.spowlo.Spowlo import com.bobbyesp.spowlo.presentation.MainActivity import com.bobbyesp.spowlo.util.Utils.makeToast @@ -13,8 +12,8 @@ import com.bobbyesp.spowlo.util.Utils.makeToast internal var pkceClassBackTo: Class? = null class SpotifyPkceLoginActivityImpl: AbstractSpotifyPkceLoginActivity() { - override val clientId = R.string.SPOTIFY_CLIENT_ID.toString() - override val redirectUri = R.string.SPOTIFY_REDIRECT_URI_PKCE.toString() + override val clientId = BuildConfig.SPOTIFY_CLIENT_ID + override val redirectUri = BuildConfig.SPOTIFY_REDIRECT_URI_PKCE override val scopes = SpotifyScope.values().toList() override fun onFailure(exception: Exception) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae8ea0bc..9eb49683 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,7 +52,7 @@ Enter without login Searcher Search a song - spowlo://spotify-auth - spowlo://spotify-pkce - abcad8ba647d4b0ebae797a8f444ac9b + \"spowlo://spotify-auth\" + \"spowlo://spotify-pkce\" + \"abcad8ba647d4b0ebae797a8f444ac9b\" \ No newline at end of file From 25fd51161569319fba7e859fad5d2499c5478081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 19 Nov 2022 18:09:39 +0100 Subject: [PATCH 12/12] Deleted settings from nav bar [skip ci] --- .../java/com/bobbyesp/spowlo/presentation/MainActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt index 66cefffd..b6bca9c2 100644 --- a/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt +++ b/app/src/main/java/com/bobbyesp/spowlo/presentation/MainActivity.kt @@ -77,7 +77,7 @@ class MainActivity : ComponentActivity() { */ navController.addOnDestinationChangedListener { _, destination, _ -> visible.value = - destination.route in listOf(Route.HOME, Route.SETTINGS, Route.SEARCHER_PAGE) + destination.route in listOf(Route.HOME, /*Route.SETTINGS,*/ Route.SEARCHER_PAGE) } SettingsProvider(windowSizeClass.widthSizeClass) { SpowloTheme( @@ -95,11 +95,11 @@ class MainActivity : ComponentActivity() { icon = Icons.Filled.Home, route = Route.HOME ), - NavBarItem( + /*NavBarItem( name = stringResource(id = R.string.settings), icon = Icons.Filled.Settings, route = Route.SETTINGS, - ), + ),*/ NavBarItem( name = stringResource(id = R.string.searcher), icon = Icons.Filled.Search,