diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ff2586c8..267b7d8f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -190,6 +190,7 @@ dependencies { implementation(libs.compose.material.ripple) implementation(libs.compose.material.icons.core) implementation(libs.compose.material.icons.extended) + implementation(libs.compose.ui.viewbinding) implementation(libs.constraintlayout.compose) // Android Studio Preview support diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a9d403ad..91955251 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -49,7 +49,9 @@ android:localeConfig="@xml/locale_config" android:networkSecurityConfig="@xml/network_security_config" android:supportsRtl="true" - android:theme="@style/Theme.SimpMusic"> + android:theme="@style/Theme.SimpMusic" + android:enableOnBackInvokedCallback="true" + tools:targetApi="tiramisu"> @@ -66,6 +68,7 @@ android:name=".ui.MainActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:exported="true" + android:enableOnBackInvokedCallback="true" android:launchMode="singleTask"> @@ -191,7 +194,7 @@ diff --git a/app/src/main/java/com/maxrave/simpmusic/data/dataStore/DataStoreManager.kt b/app/src/main/java/com/maxrave/simpmusic/data/dataStore/DataStoreManager.kt index c143cf05..08a0ff71 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/dataStore/DataStoreManager.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/dataStore/DataStoreManager.kt @@ -556,6 +556,66 @@ class DataStoreManager( } } + val usingProxy = + settingsDataStore.data.map { preferences -> + preferences[USING_PROXY] ?: FALSE + } + suspend fun setUsingProxy(usingProxy: Boolean) { + withContext(Dispatchers.IO) { + if (usingProxy) { + settingsDataStore.edit { settings -> + settings[USING_PROXY] = TRUE + } + } else { + settingsDataStore.edit { settings -> + settings[USING_PROXY] = FALSE + } + } + } + } + val proxyType = + settingsDataStore.data.map { preferences -> + preferences[PROXY_TYPE] + }.map { + when (it) { + PROXY_TYPE_HTTP -> ProxyType.PROXY_TYPE_HTTP + PROXY_TYPE_SOCKS -> ProxyType.PROXY_TYPE_SOCKS + else -> ProxyType.PROXY_TYPE_HTTP + } + } + suspend fun setProxyType(proxyType: ProxyType) { + withContext(Dispatchers.IO) { + settingsDataStore.edit { settings -> + settings[PROXY_TYPE] = when (proxyType) { + ProxyType.PROXY_TYPE_HTTP -> PROXY_TYPE_HTTP + ProxyType.PROXY_TYPE_SOCKS -> PROXY_TYPE_SOCKS + } + } + } + } + val proxyHost = + settingsDataStore.data.map { preferences -> + preferences[PROXY_HOST] ?: "" + } + suspend fun setProxyHost(proxyHost: String) { + withContext(Dispatchers.IO) { + settingsDataStore.edit { settings -> + settings[PROXY_HOST] = proxyHost + } + } + } + val proxyPort = + settingsDataStore.data.map { preferences -> + preferences[PROXY_PORT] ?: 8000 + } + suspend fun setProxyPort(proxyPort: Int) { + withContext(Dispatchers.IO) { + settingsDataStore.edit { settings -> + settings[PROXY_PORT] = proxyPort + } + } + } + companion object Settings { val COOKIE = stringPreferencesKey("cookie") val LOGGED_IN = stringPreferencesKey("logged_in") @@ -592,10 +652,21 @@ class DataStoreManager( val HOME_LIMIT = intPreferencesKey("home_limit") val CHART_KEY = stringPreferencesKey("chart_key") val TRANSLUCENT_BOTTOM_BAR = stringPreferencesKey("translucent_bottom_bar") + val USING_PROXY = stringPreferencesKey("using_proxy") + val PROXY_TYPE = stringPreferencesKey("proxy_type") + val PROXY_HOST = stringPreferencesKey("proxy_host") + val PROXY_PORT = intPreferencesKey("proxy_port") const val REPEAT_MODE_OFF = "REPEAT_MODE_OFF" const val REPEAT_ONE = "REPEAT_ONE" const val REPEAT_ALL = "REPEAT_ALL" const val TRUE = "TRUE" const val FALSE = "FALSE" + const val PROXY_TYPE_HTTP = "http" + const val PROXY_TYPE_SOCKS = "socks" + // Proxy type + enum class ProxyType { + PROXY_TYPE_HTTP, + PROXY_TYPE_SOCKS + } } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt b/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt index 36d2baa9..2b2ebe89 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt @@ -38,6 +38,7 @@ import java.time.LocalDateTime class LocalPlaylistManager( context: Context, + private val youTube: YouTube ) : BaseManager(context) { override val tag: String = this.javaClass.simpleName @@ -134,7 +135,7 @@ class LocalPlaylistManager( emit(LocalResource.Success(getString(R.string.updated))) val localPlaylist = localDataSource.getLocalPlaylist(id) if (localPlaylist.youtubePlaylistId != null) { - YouTube + youTube .editPlaylist(localPlaylist.youtubePlaylistId, newTitle) .onSuccess { emit(LocalResource.Success(getString(R.string.updated_to_youtube_playlist))) @@ -198,7 +199,7 @@ class LocalPlaylistManager( } val ytPlaylistId = playlist.id val id = ytPlaylistId.verifyYouTubePlaylistId() - YouTube + youTube .customQuery(browseId = id, setLogin = true) .onSuccess { res -> val listContent: ArrayList = arrayListOf() @@ -222,7 +223,7 @@ class LocalPlaylistManager( ?.nextContinuationData ?.continuation while (continueParam != null) { - YouTube + youTube .customQuery( "", continuation = continueParam, @@ -280,7 +281,7 @@ class LocalPlaylistManager( emit(LocalResource.Loading()) val playlist = localDataSource.getLocalPlaylist(playlistId) val res = - YouTube.createPlaylist( + youTube.createPlaylist( playlist.title, playlist.tracks, ) @@ -288,7 +289,7 @@ class LocalPlaylistManager( if (res.isSuccess && value != null) { val ytId = value.playlistId Log.d(tag, "syncLocalPlaylistToYouTubePlaylist: $ytId") - YouTube + youTube .getYouTubePlaylistFullTracksWithSetVideoId(ytId) .onSuccess { list -> Log.d(tag, "syncLocalPlaylistToYouTubePlaylist: onSuccess song ${list.map { it.first.title }}") @@ -350,7 +351,7 @@ class LocalPlaylistManager( val currentTracks = tracks.toMutableList() localPlaylist.youtubePlaylistId?.let { ytId -> Log.d(tag, "updateListTrackSynced: $ytId") - YouTube + youTube .getYouTubePlaylistFullTracksWithSetVideoId(ytId) .onSuccess { list -> Log.d(tag, "updateListTrackSynced: onSuccess ${list.map { it.first.title }}") @@ -427,7 +428,7 @@ class LocalPlaylistManager( // Add to YouTube playlist if (localPlaylist.youtubePlaylistId != null) { - YouTube + youTube .addPlaylistItem(localPlaylist.youtubePlaylistId, song.videoId) .onSuccess { val data = it.playlistEditResults @@ -465,7 +466,7 @@ class LocalPlaylistManager( if (localPlaylist.youtubePlaylistId != null) { val setVideoId = localDataSource.getSetVideoId(song.videoId) if (setVideoId?.setVideoId != null) { - YouTube + youTube .removeItemYouTubePlaylist(localPlaylist.youtubePlaylistId, song.videoId, setVideoId.setVideoId) .onSuccess { emit(LocalResource.Success(getString(R.string.removed_from_YouTube_playlist))) @@ -483,7 +484,7 @@ class LocalPlaylistManager( val localPlaylist = localDataSource.getLocalPlaylist(id) val ytPlaylistId = localPlaylist.youtubePlaylistId ?: return@flow - YouTube + youTube .getSuggestionsTrackForPlaylist(ytPlaylistId) .onSuccess { data -> val listSongItem = data?.second?.map { it.toTrack() } diff --git a/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt b/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt index 69eee16e..d1ad35d2 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt @@ -11,6 +11,7 @@ import com.maxrave.kotlinytmusicscraper.models.MusicShelfRenderer import com.maxrave.kotlinytmusicscraper.models.SearchSuggestions import com.maxrave.kotlinytmusicscraper.models.SongItem import com.maxrave.kotlinytmusicscraper.models.WatchEndpoint +import com.maxrave.kotlinytmusicscraper.models.YouTubeLocale import com.maxrave.kotlinytmusicscraper.models.musixmatch.MusixmatchCredential import com.maxrave.kotlinytmusicscraper.models.musixmatch.MusixmatchTranslationLyricsResponse import com.maxrave.kotlinytmusicscraper.models.musixmatch.SearchMusixmatchResponse @@ -87,30 +88,123 @@ import com.maxrave.simpmusic.data.parser.toListThumbnail import com.maxrave.simpmusic.data.type.PlaylistType import com.maxrave.simpmusic.data.type.RecentlyType import com.maxrave.simpmusic.extension.bestMatchingIndex +import com.maxrave.simpmusic.extension.isNetworkAvailable import com.maxrave.simpmusic.extension.toListTrack import com.maxrave.simpmusic.extension.toLyrics import com.maxrave.simpmusic.extension.toTrack import com.maxrave.simpmusic.service.test.source.MergingMediaSourceFactory import com.maxrave.simpmusic.utils.Resource import com.maxrave.simpmusic.viewModel.FilterState +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import okhttp3.CacheControl +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import java.io.File import java.time.LocalDateTime import kotlin.math.abs class MainRepository( private val localDataSource: LocalDataSource, private val dataStoreManager: DataStoreManager, + private val youTube: YouTube, private val database: MusicDatabase, private val context: Context, ) { + fun initYouTube(scope: CoroutineScope) { + youTube.cacheControlInterceptor = + object : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val originalResponse = chain.proceed(chain.request()) + if (isNetworkAvailable(context)) { + val maxAge = 60 // read from cache for 1 minute + return originalResponse + .newBuilder() + .header("Cache-Control", "public, max-age=$maxAge") + .build() + } else { + val maxStale = 60 * 60 * 24 * 28 // tolerate 4-weeks stale + return originalResponse + .newBuilder() + .header("Cache-Control", "public, only-if-cached, max-stale=$maxStale") + .build() + } + } + } + youTube.forceCacheInterceptor = + Interceptor { chain -> + val builder: Request.Builder = chain.request().newBuilder() + if (!isNetworkAvailable(context)) { + builder.cacheControl(CacheControl.FORCE_CACHE) + } + chain.proceed(builder.build()) + } + youTube.cachePath = File(context.cacheDir, "http-cache") + scope.launch { + val localeJob = launch { + combine(dataStoreManager.location, dataStoreManager.language) { location, language -> + Pair(location, language) + }.collectLatest { (location, language) -> + youTube.locale = YouTubeLocale(location, language.substring(0..1)) + } + } + val ytCookieJob = launch { + dataStoreManager.cookie.collectLatest { cookie -> + if (cookie.isNotEmpty()) { + youTube.cookie = cookie + } else { + youTube.cookie = null + } + } + } + val musixmatchCookieJob = launch { + dataStoreManager.musixmatchCookie.collectLatest { cookie -> + youTube.musixMatchCookie = cookie + } + } + val usingProxy = launch { + combine(dataStoreManager.usingProxy, + dataStoreManager.proxyType, + dataStoreManager.proxyHost, + dataStoreManager.proxyPort) { usingProxy, proxyType, proxyHost, proxyPort -> + Pair(usingProxy == DataStoreManager.TRUE, Triple(proxyType, proxyHost, proxyPort)) + }.collectLatest { (usingProxy, data) -> + if (usingProxy) { + withContext(Dispatchers.IO) { + youTube.setProxy( + data.first == DataStoreManager.Settings.ProxyType.PROXY_TYPE_HTTP, + data.second, + data.third + ) + } + } else { + youTube.removeProxy() + } + } + } + + localeJob.join() + ytCookieJob.join() + musixmatchCookieJob.join() + usingProxy.join() + } + } + + fun getMusixmatchCookie() = youTube.getMusixmatchCookie() + fun getYouTubeCookie() = youTube.cookie + // Database fun closeDatabase() { if (database.isOpen) { @@ -461,7 +555,7 @@ class MainRepository( Dispatchers.IO, ) - suspend fun insertSetVideoId(setVideoId: SetVideoIdEntity) = withContext(Dispatchers.IO) { localDataSource.insertSetVideoId(setVideoId) } + private suspend fun insertSetVideoId(setVideoId: SetVideoIdEntity) = withContext(Dispatchers.IO) { localDataSource.insertSetVideoId(setVideoId) } suspend fun getSetVideoId(videoId: String): Flow = flow { emit(localDataSource.getSetVideoId(videoId)) }.flowOn(Dispatchers.IO) @@ -576,7 +670,7 @@ class MainRepository( suspend fun getAccountInfo() = flow { - YouTube + youTube .accountInfo() .onSuccess { accountInfo -> emit(accountInfo) @@ -590,7 +684,7 @@ class MainRepository( flow { runCatching { val limit = dataStoreManager.homeLimit.first() - YouTube + youTube .customQuery(browseId = "FEmusic_home", params = params) .onSuccess { result -> val list: ArrayList = arrayListOf() @@ -689,7 +783,7 @@ class MainRepository( list.addAll(parseMixedContent(data, context)) var count = 0 while (count < limit && continueParam != null) { - YouTube + youTube .customQuery(browseId = "", continuation = continueParam) .onSuccess { response -> continueParam = @@ -721,7 +815,7 @@ class MainRepository( suspend fun getNewRelease(): Flow>> = flow { - YouTube + youTube .newRelease() .onSuccess { result -> emit(Resource.Success>(parseNewRelease(result, context))) @@ -733,7 +827,7 @@ class MainRepository( suspend fun getChartData(countryCode: String = "KR"): Flow> = flow { runCatching { - YouTube + youTube .customQuery("FEmusic_charts", country = countryCode) .onSuccess { result -> val data = @@ -760,7 +854,7 @@ class MainRepository( suspend fun getMoodAndMomentsData(): Flow> = flow { runCatching { - YouTube + youTube .moodAndGenres() .onSuccess { result -> val listMoodMoments: ArrayList = arrayListOf() @@ -795,7 +889,7 @@ class MainRepository( suspend fun getMoodData(params: String): Flow> = flow { runCatching { - YouTube + youTube .customQuery( browseId = "FEmusic_moods_and_genres_category", params = params, @@ -815,7 +909,7 @@ class MainRepository( suspend fun getGenreData(params: String): Flow> = flow { kotlin.runCatching { - YouTube + youTube .customQuery( browseId = "FEmusic_moods_and_genres_category", params = params, @@ -840,7 +934,7 @@ class MainRepository( runCatching { var newContinuation: String? = null newContinuation = null - YouTube + youTube .next( if (playlistId.startsWith("RRDAMVM")) { WatchEndpoint(videoId = playlistId.removePrefix("RRDAMVM")) @@ -867,7 +961,7 @@ class MainRepository( ): Flow>> = flow { runCatching { - YouTube + youTube .next(endpoint = WatchEndpoint(playlistId = radioId)) .onSuccess { next -> Log.w("Radio", "Title: ${next.title}") @@ -877,7 +971,7 @@ class MainRepository( Log.w("Radio", "data: ${data.size}") var count = 0 while (continuation != null && count < 3) { - YouTube + youTube .next( endpoint = WatchEndpoint(playlistId = radioId), continuation = continuation, @@ -900,7 +994,7 @@ class MainRepository( Log.w("Repository", "data: ${data.size}") val playlistBrowse = PlaylistBrowse( - author = Author(id = "", name = "YouTube Music"), + author = Author(id = "", name = "youTube Music"), description = context.getString( R.string.auto_created_by_youtube_music, @@ -938,7 +1032,7 @@ class MainRepository( suspend fun reloadSuggestionPlaylist(reloadParams: String): Flow?>?> = flow { runCatching { - YouTube + youTube .customQuery(browseId = "", continuation = reloadParams, setLogin = true) .onSuccess { values -> val data = values.continuationContents?.musicShelfContinuation?.contents @@ -987,7 +1081,7 @@ class MainRepository( } else { id += ytPlaylistId } - YouTube + youTube .customQuery(browseId = id, setLogin = true) .onSuccess { result -> println(result) @@ -1034,7 +1128,7 @@ class MainRepository( var reloadParams: String? = null println("continueParam: $continueParam") while (continueParam != null) { - YouTube + youTube .customQuery( browseId = "", continuation = continueParam, @@ -1106,7 +1200,7 @@ class MainRepository( suspend fun getPodcastData(podcastId: String): Flow> = flow { runCatching { - YouTube + youTube .customQuery(browseId = podcastId) .onSuccess { result -> val listEpisode = arrayListOf() @@ -1220,7 +1314,7 @@ class MainRepository( ?.nextContinuationData ?.continuation while (continueParam != null) { - YouTube + youTube .customQuery(continuation = continueParam, browseId = "") .onSuccess { continueData -> parsePodcastContinueData( @@ -1273,7 +1367,7 @@ class MainRepository( } else { "VL$radioId" } - YouTube + youTube .customQuery(browseId = id, setLogin = true) .onSuccess { result -> val listContent: ArrayList = arrayListOf() @@ -1365,7 +1459,7 @@ class MainRepository( // else { // var listTrack = playlistBrowse.tracks.toMutableList() while (count < 1 && continueParam != null) { - YouTube + youTube .customQuery( browseId = "", continuation = continueParam, @@ -1399,7 +1493,7 @@ class MainRepository( Resource.Success( Pair( playlist.copy( - author = Author("", "YouTube Music"), + author = Author("", "youTube Music"), ), finalContinueParam, ), @@ -1427,7 +1521,7 @@ class MainRepository( playlistId } Log.d("Repository", "playlist id: $id") - YouTube + youTube .customQuery(browseId = id, setLogin = true) .onSuccess { result -> val listContent: ArrayList = arrayListOf() @@ -1519,7 +1613,7 @@ class MainRepository( // else { // var listTrack = playlistBrowse.tracks.toMutableList() while (continueParam != null) { - YouTube + youTube .customQuery( browseId = "", continuation = continueParam, @@ -1560,7 +1654,7 @@ class MainRepository( suspend fun getAlbumData(browseId: String): Flow> = flow { runCatching { - YouTube + youTube .album(browseId, withSongs = true) .onSuccess { result -> emit(Resource.Success(parseAlbumData(result))) @@ -1577,7 +1671,7 @@ class MainRepository( ): Flow = flow { runCatching { - YouTube + youTube .browse(browseId = browseId, params = params) .onSuccess { result -> Log.w("Album More", "result: $result") @@ -1592,7 +1686,7 @@ class MainRepository( suspend fun getArtistData(channelId: String): Flow> = flow { runCatching { - YouTube + youTube .artist(channelId) .onSuccess { result -> emit(Resource.Success(parseArtistData(result, context))) @@ -1606,7 +1700,7 @@ class MainRepository( suspend fun getSearchDataSong(query: String): Flow>> = flow { runCatching { - YouTube + youTube .search(query, YouTube.SearchFilter.FILTER_SONG) .onSuccess { result -> val listSongs: ArrayList = arrayListOf() @@ -1616,7 +1710,7 @@ class MainRepository( } var count = 0 while (count < 2 && countinueParam != null) { - YouTube + youTube .searchContinuation(countinueParam) .onSuccess { values -> parseSearchSong(values).let { list -> @@ -1642,7 +1736,7 @@ class MainRepository( suspend fun getSearchDataVideo(query: String): Flow>> = flow { runCatching { - YouTube + youTube .search(query, YouTube.SearchFilter.FILTER_VIDEO) .onSuccess { result -> val listSongs: ArrayList = arrayListOf() @@ -1652,7 +1746,7 @@ class MainRepository( } var count = 0 while (count < 2 && countinueParam != null) { - YouTube + youTube .searchContinuation(countinueParam) .onSuccess { values -> parseSearchVideo(values).let { list -> @@ -1678,7 +1772,7 @@ class MainRepository( suspend fun getSearchDataPodcast(query: String): Flow>> = flow { runCatching { - YouTube + youTube .search(query, YouTube.SearchFilter.FILTER_PODCAST) .onSuccess { result -> println(query) @@ -1690,7 +1784,7 @@ class MainRepository( } var count = 0 while (count < 2 && countinueParam != null) { - YouTube + youTube .searchContinuation(countinueParam) .onSuccess { values -> parsePodcast(values.listPodcast).let { list -> @@ -1715,7 +1809,7 @@ class MainRepository( suspend fun getSearchDataFeaturedPlaylist(query: String): Flow>> = flow { runCatching { - YouTube + youTube .search(query, YouTube.SearchFilter.FILTER_FEATURED_PLAYLIST) .onSuccess { result -> val listPlaylist: ArrayList = arrayListOf() @@ -1725,7 +1819,7 @@ class MainRepository( } var count = 0 while (count < 2 && countinueParam != null) { - YouTube + youTube .searchContinuation(countinueParam) .onSuccess { values -> parseSearchPlaylist(values).let { list -> @@ -1750,7 +1844,7 @@ class MainRepository( suspend fun getSearchDataArtist(query: String): Flow>> = flow { runCatching { - YouTube + youTube .search(query, YouTube.SearchFilter.FILTER_ARTIST) .onSuccess { result -> val listArtist: ArrayList = arrayListOf() @@ -1760,7 +1854,7 @@ class MainRepository( } var count = 0 while (count < 2 && countinueParam != null) { - YouTube + youTube .searchContinuation(countinueParam) .onSuccess { values -> parseSearchArtist(values).let { list -> @@ -1785,7 +1879,7 @@ class MainRepository( suspend fun getSearchDataAlbum(query: String): Flow>> = flow { runCatching { - YouTube + youTube .search(query, YouTube.SearchFilter.FILTER_ALBUM) .onSuccess { result -> val listAlbum: ArrayList = arrayListOf() @@ -1795,7 +1889,7 @@ class MainRepository( } var count = 0 while (count < 2 && countinueParam != null) { - YouTube + youTube .searchContinuation(countinueParam) .onSuccess { values -> parseSearchAlbum(values).let { list -> @@ -1820,7 +1914,7 @@ class MainRepository( suspend fun getSearchDataPlaylist(query: String): Flow>> = flow { runCatching { - YouTube + youTube .search(query, YouTube.SearchFilter.FILTER_COMMUNITY_PLAYLIST) .onSuccess { result -> val listPlaylist: ArrayList = arrayListOf() @@ -1830,7 +1924,7 @@ class MainRepository( } var count = 0 while (count < 2 && countinueParam != null) { - YouTube + youTube .searchContinuation(countinueParam) .onSuccess { values -> parseSearchPlaylist(values).let { list -> @@ -1855,13 +1949,13 @@ class MainRepository( suspend fun getSuggestQuery(query: String): Flow> = flow { runCatching { -// YouTube.getSuggestQuery(query).onSuccess { +// youTube.getSuggestQuery(query).onSuccess { // emit(Resource.Success>(it)) // }.onFailure { e -> // Log.d("Suggest", "Error: ${e.message}") // emit(Resource.Error>(e.message.toString())) // } - YouTube + youTube .getYTMusicSearchSuggestions(query) .onSuccess { emit(Resource.Success(it)) @@ -1875,7 +1969,7 @@ class MainRepository( suspend fun getRelatedData(videoId: String): Flow, String?>>> = flow { runCatching { - YouTube + youTube .next(WatchEndpoint(videoId = videoId)) .onSuccess { next -> val data: ArrayList = arrayListOf() @@ -1899,7 +1993,7 @@ class MainRepository( suspend fun getYouTubeCaption(videoId: String): Flow> = flow { runCatching { - YouTube + youTube .getYouTubeCaption(videoId) .onSuccess { lyrics -> Log.w("Lyrics", "lyrics: ${lyrics.toLyrics()}") @@ -1940,7 +2034,7 @@ class MainRepository( spotifyPersonalToken = dataStoreManager.spotifyPersonalToken.first() Log.d("Canvas", "spotifyPersonalToken: $spotifyPersonalToken") } else if (dataStoreManager.spdc.first().isNotEmpty()) { - YouTube + youTube .getPersonalToken(dataStoreManager.spdc.first()) .onSuccess { spotifyPersonalToken = it.accessToken @@ -1956,7 +2050,7 @@ class MainRepository( } if (spotifyPersonalToken.isNotEmpty()) { val authToken = spotifyPersonalToken - YouTube + youTube .searchSpotifyTrack(q, authToken) .onSuccess { searchResponse -> Log.w("Canvas", "searchSpotifyResponse: $searchResponse") @@ -1989,7 +2083,7 @@ class MainRepository( ?.firstOrNull() } if (track != null) { - YouTube + youTube .getSpotifyCanvas( track.item?.data?.id ?: "", spotifyPersonalToken, @@ -2043,7 +2137,7 @@ class MainRepository( spotifyPersonalToken = dataStoreManager.spotifyPersonalToken.first() Log.d("Lyrics", "spotifyPersonalToken: $spotifyPersonalToken") } else if (dataStoreManager.spdc.first().isNotEmpty()) { - YouTube + youTube .getPersonalToken(dataStoreManager.spdc.first()) .onSuccess { spotifyPersonalToken = it.accessToken @@ -2060,7 +2154,7 @@ class MainRepository( if (spotifyPersonalToken.isNotEmpty()) { val authToken = spotifyPersonalToken Log.d("Lyrics", "authToken: $authToken") - YouTube + youTube .searchSpotifyTrack(q, authToken) .onSuccess { searchResponse -> val track = @@ -2093,7 +2187,7 @@ class MainRepository( } Log.d("Lyrics", "track: $track") if (track != null) { - YouTube + youTube .getSpotifyLyrics(track.item?.data?.id ?: "", spotifyPersonalToken) .onSuccess { emit(Resource.Success(it.toLyrics())) @@ -2146,12 +2240,12 @@ class MainRepository( .replace(".", " ") val q = "$qtrack $qartist" Log.d(tag, "query: $q") - var musixMatchUserToken = YouTube.musixmatchUserToken + var musixMatchUserToken = youTube.musixmatchUserToken if (musixMatchUserToken == null) { - YouTube + youTube .getMusixmatchUserToken() .onSuccess { usertoken -> - YouTube.musixmatchUserToken = usertoken.message.body.user_token + youTube.musixmatchUserToken = usertoken.message.body.user_token Log.d(tag, "musixMatchUserToken: ${usertoken.message.body.user_token}") musixMatchUserToken = usertoken.message.body.user_token }.onFailure { throwable -> @@ -2159,7 +2253,7 @@ class MainRepository( emit(Pair("", Resource.Error("Not found"))) } } - YouTube + youTube .searchMusixmatchTrackId(q, musixMatchUserToken!!) .onSuccess { searchResult -> Log.d( @@ -2299,7 +2393,7 @@ class MainRepository( "item lyrics $track", ) if (id != "" && track != null) { - YouTube + youTube .getMusixmatchLyricsByQ(track, musixMatchUserToken!!) .onSuccess { if (it != null) { @@ -2315,7 +2409,7 @@ class MainRepository( } }.onFailure { throwable -> throwable.printStackTrace() - YouTube + youTube .getLrclibLyrics(qtrack, qartist, durationInt) .onSuccess { it?.let { emit(Pair(id, Resource.Success(it.toLyrics()))) } @@ -2325,7 +2419,7 @@ class MainRepository( } } } else { - YouTube + youTube .fixSearchMusixmatch( q_artist = qartist, q_track = qtrack, @@ -2335,7 +2429,7 @@ class MainRepository( val trackX = it.message.body.track Log.w(tag, "Fix Search Musixmatch: $trackX") if (trackX != null && (abs(trackX.track_length - (durationInt ?: 0)) <= 10)) { - YouTube + youTube .getMusixmatchLyricsByQ(trackX, musixMatchUserToken!!) .onSuccess { Log.w(tag, "Item lyrics ${it?.lyrics?.syncType}") @@ -2348,7 +2442,7 @@ class MainRepository( ) } else { Log.w("Lyrics", "Error: Lỗi getLyrics $it") - YouTube.getLrclibLyrics(qtrack, qartist, durationInt) + youTube.getLrclibLyrics(qtrack, qartist, durationInt) emit(Pair(id, Resource.Error("Not found"))) } }.onFailure { @@ -2356,7 +2450,7 @@ class MainRepository( emit(Pair(id, Resource.Error("Not found"))) } } else { - YouTube + youTube .getLrclibLyrics(qtrack, qartist, durationInt) .onSuccess { it?.let { emit(Pair(trackX?.track_id.toString(), Resource.Success(it.toLyrics()))) } @@ -2367,7 +2461,7 @@ class MainRepository( } }.onFailure { Log.e(tag, "Fix musixmatch search" + it.message.toString()) - YouTube + youTube .getLrclibLyrics(qtrack, qartist, durationInt) .onSuccess { Log.w(tag, "Liblrc Item lyrics ${it?.lyrics?.syncType}") @@ -2386,9 +2480,9 @@ class MainRepository( emit(Pair("", Resource.Error("Not found"))) } -// YouTube.authentication().onSuccess { token -> +// youTube.authentication().onSuccess { token -> // if (token.accessToken != null) { -// YouTube.getSongId(token.accessToken!!, q).onSuccess { spotifyResult -> +// youTube.getSongId(token.accessToken!!, q).onSuccess { spotifyResult -> // Log.d("SongId", "id: ${spotifyResult.tracks?.items?.get(0)?.id}") // if (!spotifyResult.tracks?.items.isNullOrEmpty()) { // val list = arrayListOf() @@ -2417,7 +2511,7 @@ class MainRepository( // "Lyrics", // "token: ${dataStoreManager.spotifyAccessToken.first()}" // ) -// YouTube.getLyrics( +// youTube.getLyrics( // id, // dataStoreManager.spotifyAccessToken.first() // ).onSuccess { lyrics -> @@ -2428,7 +2522,7 @@ class MainRepository( // "Error: Lỗi getLyrics ${throwable.message}" // ) // spotifyResult.tracks?.items?.firstOrNull()?.id?.let { it2 -> -// YouTube.getLyrics( +// youTube.getLyrics( // it2, // dataStoreManager.spotifyAccessToken.first() // ).onSuccess { @@ -2446,7 +2540,7 @@ class MainRepository( // } // } // else { -// YouTube.getAccessToken() +// youTube.getAccessToken() // .onSuccess { value: AccessToken -> // dataStoreManager.setSpotifyAccessToken(value.accessToken!!) // dataStoreManager.setSpotifyAccessTokenExpire( @@ -2456,13 +2550,13 @@ class MainRepository( // "Lyrics", // "token: ${value.accessToken}" // ) -// YouTube.getLyrics(id, value.accessToken) +// youTube.getLyrics(id, value.accessToken) // .onSuccess { lyrics -> // emit(Resource.Success(lyrics.toLyrics())) // }.onFailure { throwable -> // throwable.printStackTrace() // spotifyResult.tracks?.items?.firstOrNull()?.id?.let { it2 -> -// YouTube.getLyrics( +// youTube.getLyrics( // it2, // value.accessToken // ).onSuccess { @@ -2511,7 +2605,7 @@ class MainRepository( // // "Lyrics", // // "token: ${dataStoreManager.spotifyAccessToken.first()}" // // ) -// // YouTube.getLyrics( +// // youTube.getLyrics( // // it1, // // dataStoreManager.spotifyAccessToken.first() // // ).onSuccess { lyrics -> @@ -2522,7 +2616,7 @@ class MainRepository( // // "Error: Lỗi getLyrics ${it.message}" // // ) // // spotifyResult.tracks?.items?.firstOrNull()?.id?.let { it2 -> -// // YouTube.getLyrics( +// // youTube.getLyrics( // // it2, // // dataStoreManager.spotifyAccessToken.first() // // ).onSuccess { @@ -2539,7 +2633,7 @@ class MainRepository( // // emit(Resource.Error("Not found")) // // } // // } else { -// // YouTube.getAccessToken() +// // youTube.getAccessToken() // // .onSuccess { value: AccessToken -> // // dataStoreManager.setSpotifyAccessToken(value.accessToken!!) // // dataStoreManager.setSpotifyAccessTokenExpire( @@ -2549,13 +2643,13 @@ class MainRepository( // // "Lyrics", // // "token: ${value.accessToken}" // // ) -// // YouTube.getLyrics(it1, value.accessToken) +// // youTube.getLyrics(it1, value.accessToken) // // .onSuccess { lyrics -> // // emit(Resource.Success(lyrics.toLyrics())) // // }.onFailure { // // it.printStackTrace() // // spotifyResult.tracks?.items?.firstOrNull()?.id?.let { it2 -> -// // YouTube.getLyrics( +// // youTube.getLyrics( // // it2, // // value.accessToken // // ).onSuccess { @@ -2608,8 +2702,8 @@ class MainRepository( suspend fun getTranslateLyrics(id: String): Flow = flow { runCatching { - YouTube.musixmatchUserToken?.let { - YouTube + youTube.musixmatchUserToken?.let { + youTube .getMusixmatchTranslateLyrics( id, it, @@ -2635,7 +2729,7 @@ class MainRepository( } else { videoId } - YouTube + youTube .getSongInfo(id) .onSuccess { songInfo -> val song = @@ -2665,7 +2759,7 @@ class MainRepository( suspend fun getLikeStatus(videoId: String): Flow = flow { runCatching { - YouTube + youTube .getLikedInfo(videoId) .onSuccess { if (it == LikeStatus.LIKE) emit(true) else emit(false) @@ -2683,7 +2777,7 @@ class MainRepository( Log.w("Stream", "now: ${LocalDateTime.now()}") Log.w("Stream", "isExpired: ${oldFormat.expiredTime.isBefore(LocalDateTime.now())}") if (oldFormat.expiredTime.isBefore(LocalDateTime.now())) { - YouTube + youTube .player(videoId) .onSuccess { triple -> val response = triple.second @@ -2723,7 +2817,7 @@ class MainRepository( ): Flow = flow { // 134, 136, 137 - YouTube + youTube .player(videoId) .onSuccess { data -> val itag = QUALITY.itags.getOrNull(QUALITY.items.indexOf(dataStoreManager.quality.first())) @@ -2855,7 +2949,7 @@ class MainRepository( suspend fun getLibraryPlaylist(): Flow?> = flow { - YouTube + youTube .getLibraryPlaylists() .onSuccess { data -> val input = @@ -2895,7 +2989,7 @@ class MainRepository( playlistId: String?, ): Flow> = flow { - YouTube + youTube .initPlayback(playback, atr, watchTime, cpn, playlistId) .onSuccess { response -> emit(response) @@ -2907,7 +3001,7 @@ class MainRepository( suspend fun getSkipSegments(videoId: String): Flow?> = flow { - YouTube + youTube .getSkipSegments(videoId) .onSuccess { emit(it) @@ -2919,7 +3013,7 @@ class MainRepository( fun getFullMetadata(videoId: String): Flow = flow { Log.w("getFullMetadata", "videoId: $videoId") - YouTube + youTube .getFullMetadata(videoId) .onSuccess { emit(it) @@ -2931,7 +3025,7 @@ class MainRepository( fun checkForUpdate(): Flow = flow { - YouTube + youTube .checkForUpdate() .onSuccess { emit(it) @@ -2950,7 +3044,7 @@ class MainRepository( id += youtubePlaylistId } Log.d("Repository", "playlist id: $id") - YouTube + youTube .customQuery(browseId = id, setLogin = true) .onSuccess { result -> val listContent: ArrayList = arrayListOf() @@ -2995,7 +3089,7 @@ class MainRepository( Log.d("Repository", "playlist data: ${listContent.size}") Log.d("Repository", "continueParam: $continueParam") while (continueParam != null) { - YouTube + youTube .customQuery( browseId = "", continuation = continueParam, @@ -3040,7 +3134,7 @@ class MainRepository( suspend fun createYouTubePlaylist(playlist: LocalPlaylistEntity): Flow = flow { runCatching { - YouTube + youTube .createPlaylist(playlist.title, playlist.tracks) .onSuccess { emit(it.playlistId) @@ -3057,7 +3151,7 @@ class MainRepository( ): Flow = flow { runCatching { - YouTube + youTube .editPlaylist(youtubePlaylistId, title) .onSuccess { response -> emit(response) @@ -3075,7 +3169,7 @@ class MainRepository( runCatching { getSetVideoId(videoId).collect { setVideoId -> if (setVideoId?.setVideoId != null) { - YouTube + youTube .removeItemYouTubePlaylist( youtubePlaylistId, videoId, @@ -3097,7 +3191,7 @@ class MainRepository( videoId: String, ) = flow { runCatching { - YouTube + youTube .addPlaylistItem(youtubePlaylistId.verifyYouTubePlaylistId(), videoId) .onSuccess { if (it.playlistEditResults.isNotEmpty()) { @@ -3125,12 +3219,12 @@ class MainRepository( ): Flow = flow { runCatching { - if (YouTube.musixmatchUserToken != null && YouTube.musixmatchUserToken != "") { - YouTube + if (youTube.musixmatchUserToken != null && youTube.musixmatchUserToken != "") { + youTube .postMusixmatchCredentials( email, password, - YouTube.musixmatchUserToken!!, + youTube.musixmatchUserToken!!, ).onSuccess { response -> emit(response) }.onFailure { @@ -3138,16 +3232,16 @@ class MainRepository( emit(null) } } else { - YouTube + youTube .getMusixmatchUserToken() .onSuccess { usertoken -> - YouTube.musixmatchUserToken = usertoken.message.body.user_token + youTube.musixmatchUserToken = usertoken.message.body.user_token delay(2000) - YouTube + youTube .postMusixmatchCredentials( email, password, - YouTube.musixmatchUserToken!!, + youTube.musixmatchUserToken!!, ).onSuccess { response -> emit(response) }.onFailure { @@ -3170,7 +3264,7 @@ class MainRepository( ): Flow = flow { runCatching { - YouTube + youTube .updateWatchTime( playbackTrackingVideostatsWatchtimeUrl, watchTimeList, @@ -3192,7 +3286,7 @@ class MainRepository( ): Flow = flow { runCatching { - YouTube + youTube .updateWatchTimeFull(watchTime, cpn, playlistId) .onSuccess { response -> emit(response) @@ -3207,7 +3301,7 @@ class MainRepository( flow { if (mediaId != null) { runCatching { - YouTube + youTube .addToLiked(mediaId) .onSuccess { Log.d("Liked", "Success: $it") @@ -3224,7 +3318,7 @@ class MainRepository( flow { if (mediaId != null) { runCatching { - YouTube + youTube .removeFromLiked(mediaId) .onSuccess { Log.d("Liked", "Success: $it") diff --git a/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt b/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt index d01ffaba..afae6921 100644 --- a/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt +++ b/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt @@ -11,6 +11,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.maxrave.kotlinytmusicscraper.YouTube import com.maxrave.simpmusic.common.DB_NAME import com.maxrave.simpmusic.data.dataStore.DataStoreManager import com.maxrave.simpmusic.data.db.Converters @@ -173,14 +174,20 @@ val databaseModule = single(createdAtStart = true) { DataStoreManager(get>()) } + + // Move YouTube from Singleton to Koin DI + single(createdAtStart = true) { + YouTube() + } + // MainRepository single(createdAtStart = true) { - MainRepository(get(), get(), get(), androidContext()) + MainRepository(get(), get(), get(), get(), androidContext()) } // List of managers single(createdAtStart = true) { - LocalPlaylistManager(androidContext()) + LocalPlaylistManager(androidContext(), get()) } // Notification Worker diff --git a/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt b/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt index 4cb73aa8..fd66673e 100644 --- a/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt +++ b/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt @@ -174,7 +174,7 @@ private fun provideResolvingDataSourceFactory( mainRepository: MainRepository, coroutineScope: CoroutineScope, ): DataSource.Factory { - val CHUNK_LENGTH = 512 * 1024L + val CHUNK_LENGTH = 5 * 512 * 1024L return ResolvingDataSource.Factory(cacheDataSourceFactory) { dataSpec -> val mediaId = dataSpec.key ?: error("No media id") Log.w("Stream", mediaId) diff --git a/app/src/main/java/com/maxrave/simpmusic/extension/AllExt.kt b/app/src/main/java/com/maxrave/simpmusic/extension/AllExt.kt index 8df21988..a0667471 100644 --- a/app/src/main/java/com/maxrave/simpmusic/extension/AllExt.kt +++ b/app/src/main/java/com/maxrave/simpmusic/extension/AllExt.kt @@ -5,6 +5,8 @@ import android.app.Service import android.content.Context import android.graphics.Color import android.graphics.Point +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.os.Bundle import android.text.Html import android.view.View @@ -934,4 +936,27 @@ fun getSizeOfFile(dir: File): Long { } } return dirSize +} + +fun isNetworkAvailable(context: Context?): Boolean { + val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + // Returns a Network object corresponding to + // the currently active default data network. + val network = connectivityManager.activeNetwork ?: return false + + // Representation of the capabilities of an active network. + val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false + + return when { + // Indicates this network uses a Wi-Fi transport, + // or WiFi has network connectivity + activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + + // Indicates this network uses a Cellular transport. or + // Cellular has network connectivity + activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + + // else return false + else -> false + } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/MainActivity.kt b/app/src/main/java/com/maxrave/simpmusic/ui/MainActivity.kt index c2c818d1..b45217dc 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/MainActivity.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/MainActivity.kt @@ -36,8 +36,6 @@ import androidx.media3.common.util.UnstableApi import androidx.navigation.fragment.findNavController import androidx.navigation.ui.setupWithNavController import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.maxrave.kotlinytmusicscraper.YouTube -import com.maxrave.kotlinytmusicscraper.models.YouTubeLocale import com.maxrave.simpmusic.R import com.maxrave.simpmusic.common.Config import com.maxrave.simpmusic.common.FIRST_TIME_MIGRATION @@ -59,12 +57,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import okhttp3.CacheControl -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response import pub.devrel.easypermissions.EasyPermissions -import java.io.File import java.text.SimpleDateFormat import java.util.Locale @@ -152,18 +145,8 @@ class MainActivity : AppCompatActivity() { } else { putString("location", "US") } - YouTube.locale = - YouTubeLocale( - gl = getString("location") ?: "US", - hl = Locale.getDefault().toLanguageTag().substring(0..1), - ) } else { putString(SELECTED_LANGUAGE, "en-US") - YouTube.locale = - YouTubeLocale( - gl = getString("location") ?: "US", - hl = "en-US".substring(0..1), - ) } // Fetch the selected language from wherever it was stored. In this case its SharedPref getString(SELECTED_LANGUAGE)?.let { @@ -185,11 +168,6 @@ class MainActivity : AppCompatActivity() { "onCreate: ${AppCompatDelegate.getApplicationLocales().toLanguageTags()}", ) putString(SELECTED_LANGUAGE, AppCompatDelegate.getApplicationLocales().toLanguageTags()) - YouTube.locale = - YouTubeLocale( - gl = getString("location") ?: "US", - hl = AppCompatDelegate.getApplicationLocales().toLanguageTags().substring(0..1), - ) } // // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { @@ -217,36 +195,7 @@ class MainActivity : AppCompatActivity() { ) } } - YouTube.cacheControlInterceptor = - object : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val originalResponse = chain.proceed(chain.request()) - if (isNetworkAvailable(applicationContext)) { - val maxAge = 60 // read from cache for 1 minute - return originalResponse - .newBuilder() - .header("Cache-Control", "public, max-age=$maxAge") - .build() - } else { - val maxStale = 60 * 60 * 24 * 28 // tolerate 4-weeks stale - return originalResponse - .newBuilder() - .header("Cache-Control", "public, only-if-cached, max-stale=$maxStale") - .build() - } - } - } - YouTube.forceCacheInterceptor = - Interceptor { chain -> - val builder: Request.Builder = chain.request().newBuilder() - if (!isNetworkAvailable(applicationContext)) { - builder.cacheControl(CacheControl.FORCE_CACHE) - } - chain.proceed(builder.build()) - } - YouTube.cachePath = File(application.cacheDir, "http-cache") viewModel.getLocation() - viewModel.checkAuth() viewModel.checkAllDownloadingSongs() runBlocking { delay(500) } @@ -675,29 +624,6 @@ class MainActivity : AppCompatActivity() { // } } - private fun isNetworkAvailable(context: Context?): Boolean { - val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - // Returns a Network object corresponding to - // the currently active default data network. - val network = connectivityManager.activeNetwork ?: return false - - // Representation of the capabilities of an active network. - val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false - - return when { - // Indicates this network uses a Wi-Fi transport, - // or WiFi has network connectivity - activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true - - // Indicates this network uses a Cellular transport. or - // Cellular has network connectivity - activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true - - // else return false - else -> false - } - } - override fun onDestroy() { super.onDestroy() // stopService() diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/component/LyricsView.kt b/app/src/main/java/com/maxrave/simpmusic/ui/component/LyricsView.kt index 15f8abcb..ebe30abd 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/component/LyricsView.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/component/LyricsView.kt @@ -643,7 +643,7 @@ fun FullscreenLyricsSheet( imageVector = Icons.Filled.PlayCircle, tint = Color.White, contentDescription = "", - modifier = Modifier.size(56.dp), + modifier = Modifier.size(72.dp), ) } else { Icon( diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt index d66d8941..a8983867 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels @@ -370,6 +371,7 @@ class HomeFragment : Fragment() { ) { super.onViewCreated(view, savedInstanceState) composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { Scaffold { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/MoodFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/MoodFragment.kt index b7bebe8d..2f800c71 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/MoodFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/MoodFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -65,11 +66,12 @@ class MoodFragment : Fragment() { // } // // binding.topAppBar.setNavigationOnClickListener { -// findNavController().popBackStack() +// findNavController().navigateUp() // } val params = requireArguments().getString("params") composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { Scaffold { @@ -98,7 +100,7 @@ class MoodFragment : Fragment() { // binding.contentLayout.visibility = View.GONE // binding.loadingLayout.visibility = View.GONE // Snackbar.make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG).show() -// findNavController().popBackStack() +// findNavController().navigateUp() // } // } // }) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/NotificationFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/NotificationFragment.kt index de75a81b..0344875f 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/NotificationFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/NotificationFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -36,6 +37,9 @@ class NotificationFragment : Fragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) + composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + } composeView.setContent { AppTheme { Scaffold { paddingValues -> diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/RecentlySongsFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/RecentlySongsFragment.kt index 4a65176f..01d5bf77 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/RecentlySongsFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/RecentlySongsFragment.kt @@ -79,7 +79,7 @@ class RecentlySongsFragment : Fragment() { } } binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } mainAdapter.setOnClickListener( diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/SettingsFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/SettingsFragment.kt index 77fdcb2a..c23a3c23 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/SettingsFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/SettingsFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.media3.common.util.UnstableApi import androidx.navigation.findNavController @@ -31,6 +32,7 @@ class SettingsFragment : Fragment() { ) { super.onViewCreated(view, savedInstanceState) composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { Scaffold { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt index d1b7c0f6..e4b8216e 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.media3.common.util.UnstableApi import androidx.navigation.findNavController @@ -46,6 +47,7 @@ class FavoriteFragment : Fragment() { } Log.w("FavoriteFragment", "type: $type") composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { Scaffold { paddingValue -> diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt index cbab932a..4734ac57 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.media3.common.util.UnstableApi import androidx.navigation.findNavController @@ -33,6 +34,7 @@ class LibraryFragment : Fragment() { ) { super.onViewCreated(view, savedInstanceState) composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { Scaffold { paddingValue -> diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/LogInFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/LogInFragment.kt index 174b41d4..39dd30c1 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/LogInFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/LogInFragment.kt @@ -93,7 +93,7 @@ class LogInFragment : Fragment() { R.string.login_success, Toast.LENGTH_SHORT, ).show() - findNavController().popBackStack() + findNavController().navigateUp() } } } @@ -104,7 +104,7 @@ class LogInFragment : Fragment() { loadUrl(Config.LOG_IN_URL) } binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } } diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/MusixmatchFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/MusixmatchFragment.kt index afe3214f..911bc27d 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/MusixmatchFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/MusixmatchFragment.kt @@ -1,7 +1,6 @@ package com.maxrave.simpmusic.ui.fragment.login import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -15,7 +14,6 @@ import androidx.lifecycle.lifecycleScope import androidx.media3.common.util.UnstableApi import androidx.navigation.fragment.findNavController import com.google.android.material.bottomnavigation.BottomNavigationView -import com.maxrave.kotlinytmusicscraper.YouTube import com.maxrave.simpmusic.R import com.maxrave.simpmusic.databinding.FragmentMusixmatchBinding import com.maxrave.simpmusic.extension.isMyServiceRunning @@ -89,40 +87,13 @@ class MusixmatchFragment : Fragment() { binding.progressBar.visibility = View.GONE Toast.makeText(requireContext(), getString(R.string.logged_in), Toast.LENGTH_SHORT).show() delay(1000) - findNavController().popBackStack() + findNavController().navigateUp() } } else { binding.progressBar.visibility = View.GONE } } } - val dataJob = - launch { - viewModel.data.collectLatest { data -> - if (data != null) { - Log.w("MusixmatchFragment", data.toString()) - if (data.message.body[0] - .credential.error == null && - data.message.body[0] - .credential.account != null - ) { - YouTube.musixMatchCookie?.let { viewModel.saveCookie(it) } - } else { - Toast - .makeText( - requireContext(), - data.message.body[0] - .credential.error - ?.description, - Toast.LENGTH_SHORT, - ).show() - } - } else { - Toast.makeText(requireContext(), getString(R.string.error), Toast.LENGTH_SHORT).show() - } - } - } - dataJob.join() loadingJob.join() } } diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/SpotifyLogInFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/SpotifyLogInFragment.kt index 574738a8..e4f6f10a 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/SpotifyLogInFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/login/SpotifyLogInFragment.kt @@ -93,7 +93,7 @@ class SpotifyLogInFragment : Fragment() { R.string.login_success, Toast.LENGTH_SHORT, ).show() - findNavController().popBackStack() + findNavController().navigateUp() } } } @@ -104,7 +104,7 @@ class SpotifyLogInFragment : Fragment() { loadUrl(Config.SPOTIFY_LOG_IN_URL) } binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } } diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt index 1306bd58..11529e37 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt @@ -153,7 +153,7 @@ class ArtistFragment : Fragment() { } binding.toolBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } binding.btShuffle.setOnClickListener { val id = @@ -667,7 +667,7 @@ class ArtistFragment : Fragment() { fetchData(channelId) } }.show() - findNavController().popBackStack() + findNavController().navigateUp() } else -> { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/CreditFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/CreditFragment.kt index bd0cf8b5..1c45b707 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/CreditFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/CreditFragment.kt @@ -60,7 +60,7 @@ class CreditFragment : Fragment() { startActivity(urlIntent) } binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt index 2cc04147..a2221138 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt @@ -8,6 +8,7 @@ import android.view.ViewGroup import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -49,6 +50,7 @@ class LocalPlaylistFragment : Fragment() { playlistId = arguments?.getLong("id") composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { Scaffold { @@ -467,7 +469,7 @@ class LocalPlaylistFragment : Fragment() { // ) // // binding.topAppBar.setNavigationOnClickListener { -// findNavController().popBackStack() +// findNavController().navigateUp() // } // binding.topAppBarLayout.addOnOffsetChangedListener { it, verticalOffset -> // Log.d("Local Fragment", "Offset: $verticalOffset" + "Total: ${it.totalScrollRange}") @@ -797,7 +799,7 @@ class LocalPlaylistFragment : Fragment() { // viewModel.deletePlaylist(id!!) // moreDialog.dismiss() // Toast.makeText(requireContext(), "Playlist deleted", Toast.LENGTH_SHORT).show() -// findNavController().popBackStack() +// findNavController().navigateUp() // } // // moreDialogView.btEditThumbnail.setOnClickListener { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/MoreAlbumsFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/MoreAlbumsFragment.kt index c96caff8..585ca76e 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/MoreAlbumsFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/MoreAlbumsFragment.kt @@ -93,7 +93,7 @@ class MoreAlbumsFragment : Fragment() { }, ) binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } lifecycleScope.launch { viewModel.browseResult.collect { data -> diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt index da41a9f2..ea5077c8 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt @@ -219,7 +219,7 @@ class PlaylistFragment : Fragment() { // } binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } binding.cbLove.setOnCheckedChangeListener { _, isChecked -> if (!isChecked) { @@ -1157,7 +1157,7 @@ class PlaylistFragment : Fragment() { // Snackbar // .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) // .show() -// findNavController().popBackStack() +// findNavController().navigateUp() // } // // else -> {} @@ -1520,7 +1520,7 @@ class PlaylistFragment : Fragment() { // Snackbar // .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) // .show() -// findNavController().popBackStack() +// findNavController().navigateUp() // } // // else -> {} @@ -1636,7 +1636,7 @@ class PlaylistFragment : Fragment() { // response.message.toString(), // Snackbar.LENGTH_LONG, // ).show() -// findNavController().popBackStack() +// findNavController().navigateUp() // } // // else -> {} diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PodcastFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PodcastFragment.kt index 4d78c042..ae7eca08 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PodcastFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PodcastFragment.kt @@ -99,7 +99,7 @@ class PodcastFragment : Fragment() { fetchData(id) } binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } binding.topAppBarLayout.addOnOffsetChangedListener { it, verticalOffset -> if (abs(it.totalScrollRange) == abs(verticalOffset)) { @@ -301,7 +301,7 @@ class PodcastFragment : Fragment() { Snackbar .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) .show() - findNavController().popBackStack() + findNavController().navigateUp() } else -> { @@ -358,7 +358,7 @@ class PodcastFragment : Fragment() { Snackbar .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) .show() - findNavController().popBackStack() + findNavController().navigateUp() } else -> { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt index 42b092c0..d3e69e7a 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt @@ -108,10 +108,10 @@ class FullscreenFragment : Fragment() { binding.subtitleView.visibility = View.GONE } toolbar.setNavigationOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } btOutFullScreen.setOnClickListener { - findNavController().popBackStack() + findNavController().navigateUp() } btNext.setOnClickListener { viewModel.onUIEvent(UIEvent.Next) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt index ea10eda8..e67e39d9 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt @@ -8,6 +8,7 @@ import android.view.ViewTreeObserver import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.view.marginBottom import androidx.core.view.marginEnd import androidx.core.view.marginStart @@ -152,14 +153,8 @@ class NowPlayingFragment : Fragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) -// val activity = requireActivity() -// val bottom = activity.findViewById(R.id.bottom_navigation_view) -// val miniplayer = activity.findViewById(R.id.miniplayer) -// -// bottom.visibility = View.GONE -// miniplayer.visibility = View.GONE - composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { Scaffold { paddingValues -> @@ -1358,7 +1353,7 @@ class NowPlayingFragment : Fragment() { // } // // binding.topAppBar.setNavigationOnClickListener { -// findNavController().popBackStack() +// findNavController().navigateUp() // } // binding.btQueue.setOnClickListener { // findNavController().navigateSafe(R.id.action_global_queueFragment) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/MoodScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/MoodScreen.kt index 66bd668e..5e0ddba4 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/MoodScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/MoodScreen.kt @@ -46,7 +46,7 @@ fun MoodScreen( Text(text = moodData?.header ?: "") }, leftIcon = { - IconButton(onClick = { navController.popBackStack() }) { + IconButton(onClick = { navController.navigateUp() }) { Icon( painterResource(id = R.drawable.baseline_arrow_back_ios_new_24), contentDescription = "Back", diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/NotificationScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/NotificationScreen.kt index 808ec92e..085f16fb 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/NotificationScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/NotificationScreen.kt @@ -73,7 +73,7 @@ fun NotificationScreen( }, navigationIcon = { RippleIconButton(resId = R.drawable.baseline_arrow_back_ios_new_24) { - navController.popBackStack() + navController.navigateUp() } }, ) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/SettingScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/SettingScreen.kt index a0dc635b..fce9d0ef 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/SettingScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/home/SettingScreen.kt @@ -7,12 +7,12 @@ import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -90,6 +90,7 @@ import com.maxrave.simpmusic.common.QUALITY import com.maxrave.simpmusic.common.SPONSOR_BLOCK import com.maxrave.simpmusic.common.SUPPORTED_LANGUAGE import com.maxrave.simpmusic.common.SUPPORTED_LOCATION +import com.maxrave.simpmusic.common.VIDEO_QUALITY import com.maxrave.simpmusic.data.dataStore.DataStoreManager import com.maxrave.simpmusic.data.dataStore.DataStoreManager.Settings.TRUE import com.maxrave.simpmusic.extension.bytesToMB @@ -108,6 +109,7 @@ import com.maxrave.simpmusic.viewModel.SharedViewModel import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.LibsBuilder import kotlinx.coroutines.flow.map +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.koin.androidx.compose.koinViewModel import java.text.SimpleDateFormat import java.time.Instant @@ -117,7 +119,7 @@ import java.time.format.DateTimeFormatter import java.util.Locale import java.util.Scanner -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class, ExperimentalCoilApi::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalCoilApi::class) @UnstableApi @Composable fun SettingScreen( @@ -179,6 +181,10 @@ fun SettingScreen( val githubResponse by viewModel.githubResponse.collectAsStateWithLifecycle() val lastCheckUpdate by viewModel.lastCheckForUpdate.collectAsStateWithLifecycle() val googleAccounts by viewModel.googleAccounts.collectAsStateWithLifecycle() + val usingProxy by viewModel.usingProxy.collectAsStateWithLifecycle() + val proxyType by viewModel.proxyType.collectAsStateWithLifecycle() + val proxyHost by viewModel.proxyHost.collectAsStateWithLifecycle() + val proxyPort by viewModel.proxyPort.collectAsStateWithLifecycle() var checkForUpdateSubtitle by rememberSaveable { mutableStateOf("") } @@ -189,6 +195,10 @@ fun SettingScreen( mutableStateOf(false) } + LaunchedEffect(true) { + viewModel.getAllGoogleAccount() + } + LaunchedEffect(lastCheckUpdate) { val lastCheckLong = lastCheckUpdate?.toLong() ?: 0L if (lastCheckLong > 0L) { @@ -380,6 +390,22 @@ fun SettingScreen( SettingItem( title = stringResource(R.string.video_quality), subtitle = videoQuality ?: "", + onClick = { + viewModel.setAlertData( + SettingAlertState( + title = context.getString(R.string.video_quality), + selectOne = SettingAlertState.SelectData( + listSelect = VIDEO_QUALITY.items.map { item -> + (item.toString() == videoQuality) to item.toString() + }, + ), + confirm = context.getString(R.string.change) to { state -> + viewModel.changeVideoQuality(state.selectOne?.getSelected() ?: "") + }, + dismiss = context.getString(R.string.cancel) + ) + ) + } ) SettingItem( title = stringResource(R.string.send_back_listening_data_to_google), @@ -391,6 +417,105 @@ fun SettingScreen( smallSubtitle = true, switch = (sendData to { viewModel.setSendBackToGoogle(it) }), ) + SettingItem( + title = stringResource(R.string.proxy), + subtitle = stringResource(R.string.proxy_description), + switch = (usingProxy to { viewModel.setUsingProxy(it) }), + ) + } + } + item(key = "proxy") { + Crossfade(usingProxy) { it -> + if (it) { + Column { + SettingItem( + title = stringResource(R.string.proxy_type), + subtitle = when (proxyType) { + DataStoreManager.Settings.ProxyType.PROXY_TYPE_HTTP -> stringResource(R.string.http) + DataStoreManager.Settings.ProxyType.PROXY_TYPE_SOCKS -> stringResource(R.string.socks) + }, + onClick = { + viewModel.setAlertData( + SettingAlertState( + title = context.getString(R.string.proxy_type), + selectOne = SettingAlertState.SelectData( + listSelect = listOf( + (proxyType == DataStoreManager.Settings.ProxyType.PROXY_TYPE_HTTP) to context.getString(R.string.http), + (proxyType == DataStoreManager.Settings.ProxyType.PROXY_TYPE_SOCKS) to context.getString(R.string.socks), + ), + ), + confirm = context.getString(R.string.change) to { state -> + viewModel.setProxy( + if (state.selectOne?.getSelected() == context.getString(R.string.socks)) { + DataStoreManager.Settings.ProxyType.PROXY_TYPE_SOCKS + } else { + DataStoreManager.Settings.ProxyType.PROXY_TYPE_HTTP + }, + proxyHost, + proxyPort, + ) + }, + dismiss = context.getString(R.string.cancel), + ) + ) + } + ) + SettingItem( + title = stringResource(R.string.proxy_host), + subtitle = proxyHost, + onClick = { + viewModel.setAlertData( + SettingAlertState( + title = context.getString(R.string.proxy_host), + message = context.getString(R.string.proxy_host_message), + textField = SettingAlertState.TextFieldData( + label = context.getString(R.string.proxy_host), + value = proxyHost, + verifyCodeBlock = { + (it.toHttpUrlOrNull() != null) to context.getString(R.string.invalid_host) + }, + ), + confirm = context.getString(R.string.change) to { state -> + viewModel.setProxy( + proxyType, + state.textField?.value ?: "", + proxyPort, + ) + }, + dismiss = context.getString(R.string.cancel), + ) + ) + } + ) + SettingItem( + title = stringResource(R.string.proxy_port), + subtitle = proxyPort.toString(), + onClick = { + viewModel.setAlertData( + SettingAlertState( + title = context.getString(R.string.proxy_port), + message = context.getString(R.string.proxy_port_message), + textField = SettingAlertState.TextFieldData( + label = context.getString(R.string.proxy_port), + value = proxyPort.toString(), + verifyCodeBlock = { + (it.toIntOrNull() != null) to context.getString(R.string.invalid_port) + }, + ), + confirm = context.getString(R.string.change) to { state -> + viewModel.setProxy( + proxyType, + proxyHost, + state.textField?.value?.toIntOrNull() ?: 0, + ) + }, + dismiss = context.getString(R.string.cancel), + ) + ) + } + ) + } + } } } item(key = "audio") { @@ -1012,7 +1137,10 @@ fun SettingScreen( AlertDialog( onDismissRequest = { viewModel.setBasicAlertData(null) }, title = { - Text(text = alertBasicState.title) + Text( + text = alertBasicState.title, + style = typo.titleSmall + ) }, text = { if (alertBasicState.message != null) { @@ -1167,7 +1295,10 @@ fun SettingScreen( AlertDialog( onDismissRequest = { viewModel.setAlertData(null) }, title = { - Text(text = alertState.title) + Text( + text = alertState.title, + style = typo.titleSmall + ) }, text = { if (alertState.message != null) { @@ -1220,25 +1351,32 @@ fun SettingScreen( .heightIn(0.dp, 500.dp), ) { items(alertState.selectOne.listSelect) { item -> - Row(Modifier.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) { + val onSelect = { + viewModel.setAlertData( + alertState.copy( + selectOne = + alertState.selectOne.copy( + listSelect = + alertState.selectOne.listSelect.toMutableList().map { + if (it == item) { + true to it.second + } else { + false to it.second + } + }, + ), + ), + ) + } + Row( + Modifier.padding(vertical = 4.dp).clickable { + onSelect.invoke() + }.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically) { RadioButton( selected = item.first, onClick = { - viewModel.setAlertData( - alertState.copy( - selectOne = - alertState.selectOne.copy( - listSelect = - alertState.selectOne.listSelect.toMutableList().map { - if (it == item) { - true to it.second - } else { - false to it.second - } - }, - ), - ), - ) + onSelect.invoke() }, ) Spacer(Modifier.width(8.dp)) @@ -1251,25 +1389,32 @@ fun SettingScreen( Modifier.padding(vertical = 6.dp), ) { items(alertState.multipleSelect.listSelect) { item -> - Row(Modifier.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) { + val onCheck = { + viewModel.setAlertData( + alertState.copy( + multipleSelect = + alertState.multipleSelect.copy( + listSelect = + alertState.multipleSelect.listSelect.toMutableList().map { + if (it == item) { + !it.first to it.second + } else { + it + } + }, + ), + ), + ) + } + Row( + Modifier.padding(vertical = 4.dp).clickable { + onCheck.invoke() + }.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically) { Checkbox( checked = item.first, onCheckedChange = { - viewModel.setAlertData( - alertState.copy( - multipleSelect = - alertState.multipleSelect.copy( - listSelect = - alertState.multipleSelect.listSelect.toMutableList().map { - if (it == item) { - !it.first to it.second - } else { - it - } - }, - ), - ), - ) + onCheck.invoke() }, ) Spacer(Modifier.width(8.dp)) @@ -1328,7 +1473,7 @@ fun SettingScreen( .size(32.dp), true, ) { - navController.popBackStack() + navController.navigateUp() } } }, diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt index 8445f181..520b9d86 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt @@ -129,7 +129,7 @@ fun LibraryDynamicPlaylistScreen( .size(32.dp), true, ) { - navController.popBackStack() + navController.navigateUp() } } }, diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/PlaylistScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/PlaylistScreen.kt index e8f304ce..f9744178 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/PlaylistScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/PlaylistScreen.kt @@ -362,7 +362,7 @@ fun PlaylistScreen( RippleIconButton( resId = R.drawable.baseline_arrow_back_ios_new_24, ) { - navController.popBackStack() + navController.navigateUp() } } Column( @@ -838,7 +838,7 @@ fun PlaylistScreen( }, onDelete = { viewModel.deletePlaylist(uiState.id) - navController.popBackStack() + navController.navigateUp() }, ) } @@ -906,7 +906,7 @@ fun PlaylistScreen( .size(32.dp), true, ) { - navController.popBackStack() + navController.navigateUp() } } }, diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/other/AlbumScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/other/AlbumScreen.kt index d7d629fe..888985bd 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/other/AlbumScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/other/AlbumScreen.kt @@ -210,7 +210,7 @@ fun AlbumScreen( RippleIconButton( resId = R.drawable.baseline_arrow_back_ios_new_24, ) { - navController.popBackStack() + navController.navigateUp() } } Column( @@ -453,7 +453,7 @@ fun AlbumScreen( .size(32.dp), true, ) { - navController.popBackStack() + navController.navigateUp() } } }, diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/LogInViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/LogInViewModel.kt index 437d9ffa..caa6297d 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/LogInViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/LogInViewModel.kt @@ -28,7 +28,6 @@ class LogInViewModel( Log.d("LogInViewModel", "saveCookie: $cookie") dataStoreManager.setCookie(cookie) dataStoreManager.setLoggedIn(true) - YouTube.cookie = cookie _status.postValue(true) } } diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/MusixmatchViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/MusixmatchViewModel.kt index 93c1f331..48ab7168 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/MusixmatchViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/MusixmatchViewModel.kt @@ -1,9 +1,10 @@ package com.maxrave.simpmusic.viewModel import android.app.Application +import android.util.Log import androidx.lifecycle.viewModelScope -import com.maxrave.kotlinytmusicscraper.YouTube -import com.maxrave.kotlinytmusicscraper.models.musixmatch.MusixmatchCredential +import androidx.media3.common.util.UnstableApi +import com.maxrave.simpmusic.R import com.maxrave.simpmusic.viewModel.base.BaseViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -11,6 +12,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.android.annotation.KoinViewModel +@UnstableApi @KoinViewModel class MusixmatchViewModel( application: Application, @@ -19,8 +21,6 @@ class MusixmatchViewModel( get() = "MusixmatchViewModel" var loading: MutableStateFlow = MutableStateFlow(null) - private var _data: MutableStateFlow = MutableStateFlow(null) - val data: MutableStateFlow = _data fun login( email: String, @@ -28,17 +28,31 @@ class MusixmatchViewModel( ) { loading.value = true viewModelScope.launch { - mainRepository.loginToMusixMatch(email, password).collect { - _data.value = it + mainRepository.loginToMusixMatch(email, password).collect { data -> + if (data != null) { + Log.w("MusixmatchFragment", data.toString()) + if (data.message.body.firstOrNull() + ?.credential?.error == null && + data.message.body.firstOrNull() + ?.credential?.account != null + ) { + mainRepository.getMusixmatchCookie()?.let { saveCookie(it) } + } else { + makeToast(data.message.body.firstOrNull() + ?.credential?.error + ?.description ?: getString(R.string.error)) + } + } else { + makeToast(getString(R.string.error)) + } } } } - fun saveCookie(cookie: String) { + private fun saveCookie(cookie: String) { viewModelScope.launch { dataStoreManager.setMusixmatchCookie(cookie) dataStoreManager.setMusixmatchLoggedIn(true) - YouTube.musixMatchCookie = cookie withContext(Dispatchers.Main) { loading.value = false } diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/SettingsViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/SettingsViewModel.kt index ffe38da3..66ed75c4 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/SettingsViewModel.kt @@ -15,8 +15,6 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.datasource.cache.SimpleCache import coil3.annotation.ExperimentalCoilApi import coil3.imageLoader -import com.maxrave.kotlinytmusicscraper.YouTube -import com.maxrave.kotlinytmusicscraper.models.YouTubeLocale import com.maxrave.kotlinytmusicscraper.models.simpmusic.GithubResponse import com.maxrave.simpmusic.R import com.maxrave.simpmusic.common.Config @@ -41,11 +39,13 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.koin.core.component.inject import org.koin.core.qualifier.named import java.io.FileInputStream @@ -112,6 +112,14 @@ class SettingsViewModel( val homeLimit: StateFlow = _homeLimit private var _translucentBottomBar: MutableStateFlow = MutableStateFlow(null) val translucentBottomBar: StateFlow = _translucentBottomBar + private var _usingProxy = MutableStateFlow(false) + val usingProxy: StateFlow = _usingProxy + private var _proxyType = MutableStateFlow(DataStoreManager.Settings.ProxyType.PROXY_TYPE_HTTP) + val proxyType: StateFlow = _proxyType + private var _proxyHost = MutableStateFlow("") + val proxyHost: StateFlow = _proxyHost + private var _proxyPort = MutableStateFlow(8000) + val proxyPort: StateFlow = _proxyPort private var _alertData: MutableStateFlow = MutableStateFlow(null) val alertData: StateFlow = _alertData @@ -155,9 +163,12 @@ class SettingsViewModel( getSpotifyLogIn() getSpotifyLyrics() getSpotifyCanvas() + getUsingProxy() getCanvasCache() getTranslucentBottomBar() - calculateDataFraction() + viewModelScope.launch { + calculateDataFraction() + } } private fun getCanvasCache() { @@ -172,49 +183,105 @@ class SettingsViewModel( _basicAlertData.value = alertData } - private fun calculateDataFraction() { - val mStorageStatsManager = - application.getSystemService(StorageStatsManager::class.java) - if (mStorageStatsManager != null) { - val totalByte = - mStorageStatsManager.getTotalBytes(StorageManager.UUID_DEFAULT).bytesToMB() - val freeSpace = - mStorageStatsManager.getFreeBytes(StorageManager.UUID_DEFAULT).bytesToMB() - val usedSpace = totalByte - freeSpace - val simpMusicSize = getSizeOfFile(application.filesDir).bytesToMB() - val thumbSize = (application.imageLoader.diskCache?.size ?: 0L).bytesToMB() - val otherApp = simpMusicSize.let { usedSpace.minus(it) - thumbSize } - val databaseSize = - simpMusicSize - playerCache.cacheSpace.bytesToMB() - downloadCache.cacheSpace.bytesToMB() - canvasCache.cacheSpace.bytesToMB() - if (totalByte == - freeSpace + otherApp + simpMusicSize + thumbSize - ) { - _fraction.update { - it.copy( - otherApp = otherApp.toFloat().div(totalByte.toFloat()), - downloadCache = - downloadCache.cacheSpace - .bytesToMB() - .toFloat() - .div(totalByte.toFloat()), - playerCache = - playerCache.cacheSpace - .bytesToMB() - .toFloat() - .div(totalByte.toFloat()), - canvasCache = - canvasCache.cacheSpace - .bytesToMB() - .toFloat() - .div(totalByte.toFloat()), - thumbCache = thumbSize.toFloat().div(totalByte.toFloat()), - freeSpace = freeSpace.toFloat().div(totalByte.toFloat()), - appDatabase = databaseSize.toFloat().div(totalByte.toFloat()), - ) + private fun getUsingProxy() { + viewModelScope.launch { + dataStoreManager.usingProxy.collectLatest { usingProxy -> + if (usingProxy == DataStoreManager.TRUE) { + getProxy() + } + _usingProxy.value = usingProxy == DataStoreManager.TRUE + } + } + } + + fun setUsingProxy(usingProxy: Boolean) { + viewModelScope.launch { + dataStoreManager.setUsingProxy(usingProxy) + getUsingProxy() + getProxy() + } + } + + private fun getProxy() { + viewModelScope.launch { + val host = launch { + dataStoreManager.proxyHost.collect { + _proxyHost.value = it + } + } + val port = launch { + dataStoreManager.proxyPort.collect { + _proxyPort.value = it + } + } + val type = launch { + dataStoreManager.proxyType.collect { + _proxyType.value = it + log("getProxy: $it", Log.DEBUG) + } + } + host.join() + port.join() + type.join() + } + } + + fun setProxy(proxyType: DataStoreManager.Settings.ProxyType, host: String, port: Int) { + log("setProxy: $proxyType, $host, $port", Log.DEBUG) + viewModelScope.launch { + dataStoreManager.setProxyType(proxyType) + dataStoreManager.setProxyHost(host) + dataStoreManager.setProxyPort(port) + } + } + + private suspend fun calculateDataFraction() { + withContext(Dispatchers.Default) { + val mStorageStatsManager = + application.getSystemService(StorageStatsManager::class.java) + if (mStorageStatsManager != null) { + val totalByte = + mStorageStatsManager.getTotalBytes(StorageManager.UUID_DEFAULT).bytesToMB() + val freeSpace = + mStorageStatsManager.getFreeBytes(StorageManager.UUID_DEFAULT).bytesToMB() + val usedSpace = totalByte - freeSpace + val simpMusicSize = getSizeOfFile(application.filesDir).bytesToMB() + val thumbSize = (application.imageLoader.diskCache?.size ?: 0L).bytesToMB() + val otherApp = simpMusicSize.let { usedSpace.minus(it) - thumbSize } + val databaseSize = + simpMusicSize - playerCache.cacheSpace.bytesToMB() - downloadCache.cacheSpace.bytesToMB() - canvasCache.cacheSpace.bytesToMB() + if (totalByte == + freeSpace + otherApp + simpMusicSize + thumbSize + ) { + withContext(Dispatchers.Main) { + _fraction.update { + it.copy( + otherApp = otherApp.toFloat().div(totalByte.toFloat()), + downloadCache = + downloadCache.cacheSpace + .bytesToMB() + .toFloat() + .div(totalByte.toFloat()), + playerCache = + playerCache.cacheSpace + .bytesToMB() + .toFloat() + .div(totalByte.toFloat()), + canvasCache = + canvasCache.cacheSpace + .bytesToMB() + .toFloat() + .div(totalByte.toFloat()), + thumbCache = thumbSize.toFloat().div(totalByte.toFloat()), + freeSpace = freeSpace.toFloat().div(totalByte.toFloat()), + appDatabase = databaseSize.toFloat().div(totalByte.toFloat()), + ) + } + log("calculateDataFraction: $totalByte, $freeSpace, $usedSpace, $simpMusicSize, $otherApp, $databaseSize", Log.WARN) + log("calculateDataFraction: ${_fraction.value}", Log.WARN) + log("calculateDataFraction: ${_fraction.value.combine()}", Log.WARN) + } } - log("calculateDataFraction: $totalByte, $freeSpace, $usedSpace, $simpMusicSize, $otherApp, $databaseSize", Log.WARN) - log("calculateDataFraction: ${_fraction.value}", Log.WARN) - log("calculateDataFraction: ${_fraction.value.combine()}", Log.WARN) } } } @@ -346,7 +413,6 @@ class SettingsViewModel( fun changeLocation(location: String) { viewModelScope.launch { dataStoreManager.setLocation(location) - YouTube.locale = YouTubeLocale(location, language.value!!) getLocation() } } @@ -431,11 +497,10 @@ class SettingsViewModel( } } - fun changeVideoQuality(checkedIndex: Int) { + fun changeVideoQuality(item: String) { viewModelScope.launch { - when (checkedIndex) { - 0 -> dataStoreManager.setVideoQuality(VIDEO_QUALITY.items[0].toString()) - 1 -> dataStoreManager.setVideoQuality(VIDEO_QUALITY.items[1].toString()) + if (VIDEO_QUALITY.items.contains(item)) { + dataStoreManager.setVideoQuality(item) } getVideoQuality() } @@ -582,7 +647,6 @@ class SettingsViewModel( viewModelScope.launch { dataStoreManager.putString(SELECTED_LANGUAGE, code) Log.w("SettingsViewModel", "changeLanguage: $code") - YouTube.locale = YouTubeLocale(location.value!!, code.substring(0..1)) getLanguage() val localeList = LocaleListCompat.forLanguageTags( @@ -604,7 +668,6 @@ class SettingsViewModel( fun clearCookie() { viewModelScope.launch { dataStoreManager.setCookie("") - YouTube.cookie = null dataStoreManager.setLoggedIn(false) } } @@ -681,7 +744,6 @@ class SettingsViewModel( fun clearMusixmatchCookie() { viewModelScope.launch { dataStoreManager.setMusixmatchCookie("") - YouTube.musixMatchCookie = null dataStoreManager.setMusixmatchLoggedIn(false) makeToast(getString(R.string.logged_out)) } @@ -733,7 +795,7 @@ class SettingsViewModel( email = it.email, name = it.name, thumbnailUrl = it.thumbnails.lastOrNull()?.url ?: "", - cache = YouTube.cookie, + cache = mainRepository.getYouTubeCookie(), isUsed = true, ), ) @@ -770,12 +832,12 @@ class SettingsViewModel( email = accountInfo.email, name = accountInfo.name, thumbnailUrl = accountInfo.thumbnails.lastOrNull()?.url ?: "", - cache = YouTube.cookie, + cache = mainRepository.getYouTubeCookie(), isUsed = true, ), ) dataStoreManager.setLoggedIn(true) - dataStoreManager.setCookie(YouTube.cookie ?: "") + dataStoreManager.setCookie(mainRepository.getYouTubeCookie() ?: "") delay(500) getAllGoogleAccount() getLoggedIn() @@ -793,7 +855,6 @@ class SettingsViewModel( dataStoreManager.putString("AccountName", acc.name) dataStoreManager.putString("AccountThumbUrl", acc.thumbnailUrl) mainRepository.updateGoogleAccountUsed(acc.email, true) - YouTube.cookie = acc.cache dataStoreManager.setCookie(acc.cache ?: "") dataStoreManager.setLoggedIn(true) delay(500) @@ -807,7 +868,6 @@ class SettingsViewModel( dataStoreManager.putString("AccountThumbUrl", "") dataStoreManager.setLoggedIn(false) dataStoreManager.setCookie("") - YouTube.cookie = null delay(500) getAllGoogleAccount() getLoggedIn() @@ -824,7 +884,6 @@ class SettingsViewModel( dataStoreManager.putString("AccountThumbUrl", "") dataStoreManager.setLoggedIn(false) dataStoreManager.setCookie("") - YouTube.cookie = null delay(500) getAllGoogleAccount() getLoggedIn() diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt index e8c2fc89..c090f6d8 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt @@ -16,8 +16,6 @@ import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.NetworkType import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager -import com.maxrave.kotlinytmusicscraper.YouTube -import com.maxrave.kotlinytmusicscraper.models.YouTubeLocale import com.maxrave.kotlinytmusicscraper.models.response.spotify.CanvasResponse import com.maxrave.kotlinytmusicscraper.models.simpmusic.GithubResponse import com.maxrave.simpmusic.R @@ -196,6 +194,7 @@ class SharedViewModel( val likeStatus: StateFlow = _likeStatus init { + mainRepository.initYouTube(viewModelScope) viewModelScope.launch { val timeLineJob = launch { @@ -1047,7 +1046,6 @@ class SharedViewModel( regionCode = runBlocking { dataStoreManager.location.first() } quality = runBlocking { dataStoreManager.quality.first() } language = runBlocking { dataStoreManager.getString(SELECTED_LANGUAGE).first() } - YouTube.locale = YouTubeLocale(gl = regionCode ?: "US", hl = language?.substring(0..1) ?: "en") } fun checkAllDownloadingSongs() { @@ -1071,27 +1069,6 @@ class SharedViewModel( } } - fun checkAuth() { - viewModelScope.launch { - dataStoreManager.cookie.first().let { cookie -> - if (cookie != "") { - YouTube.cookie = cookie - Log.d("Cookie", "Cookie is not empty") - } else { - Log.e("Cookie", "Cookie is empty") - } - } - dataStoreManager.musixmatchCookie.first().let { cookie -> - if (cookie != "") { - YouTube.musixMatchCookie = cookie - Log.d("Musixmatch", "Cookie is not empty") - } else { - Log.e("Musixmatch", "Cookie is empty") - } - } - } - } - private fun getFormat(mediaId: String?) { getFormatFlowJob?.cancel() getFormatFlowJob = diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b16ea28b..b515b1f6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -363,4 +363,15 @@ YouTube Music now requires you to log in to stream music, Piped Instance sometime can\'t work too. You should log in to YouTube to get better experience with SimpMusic. Spotify Canvas Cache Clear canvas cache + Proxy + Using Proxy to bypass country content blocking + Proxy type + HTTP + Socks + Proxy host + Invalid host + Proxy port + Invalid port + Please enter your Proxy host + Please enter your Proxy port \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 81776c9a..f849a42c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ android = "8.7.3" kotlin = "2.0.0" serialization = "2.0.0" ksp = "2.0.0-1.0.22" -compose-bom = "2024.11.00" +compose-bom = "2024.12.01" constraintlayout-compose = "1.1.0" activity-compose = "1.9.3" lifecycle-viewmodel-compose = "2.8.7" @@ -15,7 +15,7 @@ material = "1.12.0" startup-runtime = "1.2.0" lifecycle-livedata-ktx = "2.8.7" lifecycle-viewmodel-ktx = "2.8.7" -ui-tooling = "1.7.5" +ui-tooling = "1.7.6" media3 = "1.5.0" palette-ktx = "1.0.0" expandable-text = "2.0.0" @@ -27,7 +27,7 @@ room = "2.6.1" legacy-support-v4 = "1.0.0" coroutines-android = "1.9.0" coroutines-guava = "1.9.0" -navigation = "2.8.4" +navigation = "2.8.5" gson = "2.10.1" coil3 = "3.0.4" kmpalette = "3.1.0" @@ -39,7 +39,7 @@ swiperefreshlayout = "1.2.0-alpha01" insetter = "0.6.1" shimmer = "0.5.0" lottie = "6.4.0" -paging = "3.3.4" +paging = "3.3.5" customactivityoncrash = "2.4.0" sdp-android = "1.1.0" ssp-android = "1.1.0" @@ -64,6 +64,7 @@ compose-ui = { group = "androidx.compose.ui", name = "ui" } compose-material-ripple = { group = "androidx.compose.material", name = "material-ripple" } compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" } compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +compose-ui-viewbinding = { group = "androidx.compose.ui", name = "ui-viewbinding" } constraintlayout-compose = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "constraintlayout-compose" } ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt index da318883..719d630c 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt @@ -82,6 +82,8 @@ import com.maxrave.kotlinytmusicscraper.parser.parseUnsyncedLyrics import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlHandler import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser import io.ktor.client.call.body +import io.ktor.client.engine.ProxyBuilder +import io.ktor.client.engine.http import io.ktor.client.statement.bodyAsText import kotlinx.coroutines.delay import kotlinx.serialization.json.Json @@ -91,7 +93,6 @@ import kotlinx.serialization.json.jsonPrimitive import okhttp3.Interceptor import org.json.JSONArray import java.io.File -import java.net.Proxy import kotlin.math.abs import kotlin.random.Random @@ -132,7 +133,7 @@ private fun List.toListFormat(): List.completed() = - runCatching { - val page = getOrThrow() - val songs = page.songs.toMutableList() - var continuation = page.songsContinuation - while (continuation != null) { - val continuationPage = YouTube.playlistContinuation(continuation).getOrNull() ?: break - songs += continuationPage.songs - continuation = continuationPage.continuation - } - PlaylistPage( - playlist = page.playlist, - songs = songs, - songsContinuation = null, - continuation = page.continuation, - ) - } - fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02x".format(eachByte) } fun sha1(str: String): String = MessageDigest.getInstance("SHA-1").digest(str.toByteArray()).toHex()