From 5f495f54e5f08e18d1085eacf436ca279aa5a4b1 Mon Sep 17 00:00:00 2001 From: Antoine POPINEAU Date: Mon, 19 Aug 2019 16:50:33 +0200 Subject: [PATCH] Initial commit. --- .gitignore | 8 + LICENSE | 21 + app/.gitignore | 2 + app/build.gradle | 61 +++ app/proguard-rules.pro | 21 + app/src/main/AndroidManifest.xml | 45 ++ .../java/com/github/apognu/otter/Otter.kt | 17 + .../otter/activities/LicencesActivity.kt | 102 ++++ .../apognu/otter/activities/LoginActivity.kt | 97 ++++ .../apognu/otter/activities/MainActivity.kt | 303 ++++++++++++ .../apognu/otter/activities/SearchActivity.kt | 66 +++ .../otter/activities/SettingsActivity.kt | 121 +++++ .../apognu/otter/adapters/AlbumsAdapter.kt | 55 +++ .../otter/adapters/AlbumsGridAdapter.kt | 52 +++ .../apognu/otter/adapters/ArtistsAdapter.kt | 65 +++ .../otter/adapters/BrowseTabsAdapter.kt | 44 ++ .../apognu/otter/adapters/FavoritesAdapter.kt | 183 ++++++++ .../otter/adapters/PlaylistTracksAdapter.kt | 179 +++++++ .../apognu/otter/adapters/PlaylistsAdapter.kt | 65 +++ .../otter/adapters/SearchResultsAdapter.kt | 32 ++ .../apognu/otter/adapters/TracksAdapter.kt | 206 ++++++++ .../apognu/otter/fragments/AlbumsFragment.kt | 93 ++++ .../otter/fragments/AlbumsGridFragment.kt | 60 +++ .../apognu/otter/fragments/ArtistsFragment.kt | 58 +++ .../apognu/otter/fragments/BrowseFragment.kt | 34 ++ .../otter/fragments/FavoritesFragment.kt | 71 +++ .../otter/fragments/FunkwhaleFragment.kt | 80 ++++ .../apognu/otter/fragments/LoginDialog.kt | 23 + .../otter/fragments/PlaylistTracksFragment.kt | 120 +++++ .../otter/fragments/PlaylistsFragment.kt | 55 +++ .../apognu/otter/fragments/QueueFragment.kt | 89 ++++ .../apognu/otter/fragments/TracksFragment.kt | 122 +++++ .../otter/playback/MediaControlsManager.kt | 125 +++++ .../apognu/otter/playback/PlayerService.kt | 442 ++++++++++++++++++ .../apognu/otter/playback/QueueManager.kt | 158 +++++++ .../otter/repositories/AlbumsRepository.kt | 32 ++ .../otter/repositories/ArtistsRepository.kt | 18 + .../otter/repositories/FavoritesRepository.kt | 55 +++ .../apognu/otter/repositories/HttpUpstream.kt | 102 ++++ .../repositories/PlaylistTracksRepository.kt | 18 + .../otter/repositories/PlaylistsRepository.kt | 18 + .../apognu/otter/repositories/Repository.kt | 75 +++ .../otter/repositories/SearchRepository.kt | 35 ++ .../otter/repositories/TracksRepository.kt | 33 ++ .../github/apognu/otter/utils/AppContext.kt | 75 +++ .../com/github/apognu/otter/utils/Data.kt | 90 ++++ .../com/github/apognu/otter/utils/EventBus.kt | 117 +++++ .../github/apognu/otter/utils/Extensions.kt | 88 ++++ .../com/github/apognu/otter/utils/Models.kt | 113 +++++ .../com/github/apognu/otter/utils/Util.kt | 26 ++ .../apognu/otter/views/ExplodeReveal.kt | 79 ++++ .../apognu/otter/views/NowPlayingView.kt | 240 ++++++++++ .../apognu/otter/views/SquareImageView.kt | 17 + app/src/main/res/drawable-hdpi/ottericon.png | Bin 0 -> 696 bytes app/src/main/res/drawable-mdpi/ottericon.png | Bin 0 -> 469 bytes app/src/main/res/drawable-xhdpi/ottericon.png | Bin 0 -> 946 bytes .../main/res/drawable-xxhdpi/ottericon.png | Bin 0 -> 1475 bytes .../main/res/drawable-xxxhdpi/ottericon.png | Bin 0 -> 1995 bytes app/src/main/res/drawable/add.xml | 9 + app/src/main/res/drawable/cover.png | Bin 0 -> 12252 bytes app/src/main/res/drawable/favorite.xml | 9 + app/src/main/res/drawable/login_input.xml | 8 + app/src/main/res/drawable/more.xml | 9 + app/src/main/res/drawable/next.xml | 9 + app/src/main/res/drawable/ottershape.png | Bin 0 -> 55267 bytes app/src/main/res/drawable/pause.xml | 9 + app/src/main/res/drawable/play.xml | 9 + app/src/main/res/drawable/previous.xml | 9 + app/src/main/res/drawable/queue.xml | 9 + app/src/main/res/drawable/reorder.xml | 9 + app/src/main/res/drawable/search.xml | 9 + app/src/main/res/drawable/settings.xml | 9 + app/src/main/res/layout/activity_licences.xml | 31 ++ app/src/main/res/layout/activity_login.xml | 97 ++++ app/src/main/res/layout/activity_main.xml | 44 ++ app/src/main/res/layout/activity_search.xml | 68 +++ app/src/main/res/layout/activity_settings.xml | 21 + app/src/main/res/layout/dialog_login.xml | 15 + app/src/main/res/layout/fragment_albums.xml | 109 +++++ .../main/res/layout/fragment_albums_grid.xml | 45 ++ app/src/main/res/layout/fragment_artists.xml | 48 ++ app/src/main/res/layout/fragment_browse.xml | 26 ++ .../main/res/layout/fragment_favorites.xml | 59 +++ .../main/res/layout/fragment_playlists.xml | 47 ++ app/src/main/res/layout/fragment_queue.xml | 45 ++ app/src/main/res/layout/fragment_tracks.xml | 206 ++++++++ .../main/res/layout/partial_now_playing.xml | 218 +++++++++ .../main/res/layout/preference_category.xml | 7 + app/src/main/res/layout/row_album.xml | 46 ++ app/src/main/res/layout/row_album_grid.xml | 28 ++ app/src/main/res/layout/row_artist.xml | 47 ++ app/src/main/res/layout/row_licence.xml | 24 + app/src/main/res/layout/row_playlist.xml | 114 +++++ app/src/main/res/layout/row_track.xml | 72 +++ app/src/main/res/menu/row_queue.xml | 8 + app/src/main/res/menu/row_track.xml | 16 + app/src/main/res/menu/toolbar.xml | 31 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2175 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 2461 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4229 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1448 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1526 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2640 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3076 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 3404 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6082 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 4781 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 5597 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 9497 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 6697 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 8026 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 13553 bytes app/src/main/res/values-fr/strings.xml | 77 +++ app/src/main/res/values-night/colors.xml | 10 + app/src/main/res/values/array.xml | 24 + app/src/main/res/values/colors.xml | 16 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 77 +++ app/src/main/res/values/styles.xml | 65 +++ app/src/main/res/xml/settings.xml | 44 ++ build.gradle | 38 ++ gradle.properties | 21 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++++ gradlew.bat | 84 ++++ settings.gradle | 1 + 129 files changed, 6734 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/github/apognu/otter/Otter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/activities/LicencesActivity.kt create mode 100644 app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt create mode 100644 app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt create mode 100644 app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt create mode 100644 app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/AlbumsAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/ArtistsAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/PlaylistTracksAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/PlaylistsAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/SearchResultsAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/adapters/TracksAdapter.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/AlbumsGridFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/BrowseFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/LoginDialog.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/PlaylistsFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/QueueFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/fragments/TracksFragment.kt create mode 100644 app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt create mode 100644 app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt create mode 100644 app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/Repository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt create mode 100644 app/src/main/java/com/github/apognu/otter/utils/AppContext.kt create mode 100644 app/src/main/java/com/github/apognu/otter/utils/Data.kt create mode 100644 app/src/main/java/com/github/apognu/otter/utils/EventBus.kt create mode 100644 app/src/main/java/com/github/apognu/otter/utils/Extensions.kt create mode 100644 app/src/main/java/com/github/apognu/otter/utils/Models.kt create mode 100644 app/src/main/java/com/github/apognu/otter/utils/Util.kt create mode 100644 app/src/main/java/com/github/apognu/otter/views/ExplodeReveal.kt create mode 100644 app/src/main/java/com/github/apognu/otter/views/NowPlayingView.kt create mode 100644 app/src/main/java/com/github/apognu/otter/views/SquareImageView.kt create mode 100644 app/src/main/res/drawable-hdpi/ottericon.png create mode 100644 app/src/main/res/drawable-mdpi/ottericon.png create mode 100644 app/src/main/res/drawable-xhdpi/ottericon.png create mode 100644 app/src/main/res/drawable-xxhdpi/ottericon.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ottericon.png create mode 100644 app/src/main/res/drawable/add.xml create mode 100644 app/src/main/res/drawable/cover.png create mode 100644 app/src/main/res/drawable/favorite.xml create mode 100644 app/src/main/res/drawable/login_input.xml create mode 100644 app/src/main/res/drawable/more.xml create mode 100644 app/src/main/res/drawable/next.xml create mode 100644 app/src/main/res/drawable/ottershape.png create mode 100644 app/src/main/res/drawable/pause.xml create mode 100644 app/src/main/res/drawable/play.xml create mode 100644 app/src/main/res/drawable/previous.xml create mode 100644 app/src/main/res/drawable/queue.xml create mode 100644 app/src/main/res/drawable/reorder.xml create mode 100644 app/src/main/res/drawable/search.xml create mode 100644 app/src/main/res/drawable/settings.xml create mode 100644 app/src/main/res/layout/activity_licences.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_search.xml create mode 100644 app/src/main/res/layout/activity_settings.xml create mode 100644 app/src/main/res/layout/dialog_login.xml create mode 100644 app/src/main/res/layout/fragment_albums.xml create mode 100644 app/src/main/res/layout/fragment_albums_grid.xml create mode 100644 app/src/main/res/layout/fragment_artists.xml create mode 100644 app/src/main/res/layout/fragment_browse.xml create mode 100644 app/src/main/res/layout/fragment_favorites.xml create mode 100644 app/src/main/res/layout/fragment_playlists.xml create mode 100644 app/src/main/res/layout/fragment_queue.xml create mode 100644 app/src/main/res/layout/fragment_tracks.xml create mode 100644 app/src/main/res/layout/partial_now_playing.xml create mode 100644 app/src/main/res/layout/preference_category.xml create mode 100644 app/src/main/res/layout/row_album.xml create mode 100644 app/src/main/res/layout/row_album_grid.xml create mode 100644 app/src/main/res/layout/row_artist.xml create mode 100644 app/src/main/res/layout/row_licence.xml create mode 100644 app/src/main/res/layout/row_playlist.xml create mode 100644 app/src/main/res/layout/row_track.xml create mode 100644 app/src/main/res/menu/row_queue.xml create mode 100644 app/src/main/res/menu/row_track.xml create mode 100644 app/src/main/res/menu/toolbar.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-fr/strings.xml create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values/array.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/settings.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09b993d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8b0beb2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Antoine POPINEAU + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..67e07b8 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +/release diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..99a72d7 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + compileSdkVersion 29 + + defaultConfig { + applicationId "com.github.apognu.otter" + minSdkVersion 23 + targetSdkVersion 29 + versionCode 4 + versionName "1.0.3" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2' + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0-beta01' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.0.0' + implementation 'androidx.preference:preference:1.1.0' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' + implementation 'com.google.android.material:material:1.1.0-beta01' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + + implementation 'com.google.android.exoplayer:exoplayer:2.10.3' + implementation 'com.google.android.exoplayer:extension-mediasession:2.10.6' + implementation 'com.google.android.exoplayer:extension-cast:2.10.6' + implementation 'com.aliassadi:power-preference-lib:1.4.1' + implementation 'com.github.kittinunf.fuel:fuel:2.1.0' + implementation 'com.github.kittinunf.fuel:fuel-coroutines:2.1.0' + implementation 'com.github.kittinunf.fuel:fuel-android:2.1.0' + implementation 'com.github.kittinunf.fuel:fuel-gson:2.1.0' + implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'jp.wasabeef:picasso-transformations:2.2.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a289d53 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/Otter.kt b/app/src/main/java/com/github/apognu/otter/Otter.kt new file mode 100644 index 0000000..4632159 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/Otter.kt @@ -0,0 +1,17 @@ +package com.github.apognu.otter + +import android.app.Application +import androidx.appcompat.app.AppCompatDelegate +import com.preference.PowerPreference + +class Otter : Application() { + override fun onCreate() { + super.onCreate() + + when (PowerPreference.getDefaultFile().getString("night_mode")) { + "on" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + "off" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + else -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/activities/LicencesActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/LicencesActivity.kt new file mode 100644 index 0000000..1433ec0 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/activities/LicencesActivity.kt @@ -0,0 +1,102 @@ +package com.github.apognu.otter.activities + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import kotlinx.android.synthetic.main.activity_licences.* +import kotlinx.android.synthetic.main.row_licence.view.* + +class LicencesActivity : AppCompatActivity() { + data class Licence(val name: String, val licence: String, val url: String) + + interface OnLicenceClickListener { + fun onClick(url: String) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_licences) + + LicencesAdapter(OnLicenceClick()).also { + licences.layoutManager = LinearLayoutManager(this) + licences.adapter = it + } + } + + private inner class LicencesAdapter(val listener: OnLicenceClickListener) : RecyclerView.Adapter() { + val licences = listOf( + Licence( + "ExoPlayer", + "Apache License 2.0", + "https://github.com/google/ExoPlayer/blob/release-v2/LICENSE" + ), + Licence( + "Fuel", + "MIT License", + "https://github.com/kittinunf/fuel/blob/master/LICENSE.md" + ), + Licence( + "Gson", + "Apache License 2.0", + "https://github.com/google/gson/blob/master/LICENSE" + ), + Licence( + "Picasso", + "Apache License 2.0", + "https://github.com/square/picasso/blob/master/LICENSE.txt" + ), + Licence( + "Picasso Transformations", + "Apache License 2.0", + "https://github.com/wasabeef/picasso-transformations/blob/master/LICENSE" + ), + Licence( + "PowerPreference", + "Apache License 2.0", + "https://github.com/AliAsadi/PowerPreference/blob/master/LICENSE" + ) + ) + + override fun getItemCount() = licences.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(this@LicencesActivity).inflate(R.layout.row_licence, parent, false) + + return ViewHolder(view).also { + view.setOnClickListener(it) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = licences[position] + + holder.name.text = item.name + holder.licence.text = item.licence + } + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener { + val name = view.name + val licence = view.licence + + override fun onClick(view: View?) { + listener.onClick(licences[layoutPosition].url) + } + } + } + + inner class OnLicenceClick : OnLicenceClickListener { + override fun onClick(url: String) { + Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { + startActivity(this) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt new file mode 100644 index 0000000..6d5a540 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.github.apognu.otter.activities + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.appcompat.app.AppCompatActivity +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.LoginDialog +import com.github.apognu.otter.utils.AppContext +import com.github.apognu.otter.utils.log +import com.github.kittinunf.fuel.Fuel +import com.github.kittinunf.fuel.coroutines.awaitObjectResult +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.preference.PowerPreference +import kotlinx.android.synthetic.main.activity_login.* +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +data class FwCredentials(val token: String) + +class LoginActivity : AppCompatActivity() { + override fun onResume() { + super.onResume() + + getSharedPreferences(AppContext.PREFS_CREDENTIALS, Context.MODE_PRIVATE).apply { + when (contains("access_token")) { + true -> Intent(this@LoginActivity, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NO_ANIMATION + + startActivity(this) + } + + false -> setContentView(R.layout.activity_login) + } + } + + login?.setOnClickListener { + val hostname = hostname.text.toString().trim() + val username = username.text.toString() + val password = password.text.toString() + + try { + if (hostname.isEmpty()) throw Exception(getString(R.string.login_error_hostname)) + + val url = Uri.parse(hostname) + + if (url.scheme != "https") { + throw Exception(getString(R.string.login_error_hostname_https)) + } + } catch (e: Exception) { + val message = + if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname) + else e.message + + hostname_field.error = message + + return@setOnClickListener + } + + hostname_field.error = "" + + val body = mapOf( + "username" to username, + "password" to password + ).toList() + + val dialog = LoginDialog().apply { + show(supportFragmentManager, "LoginDialog") + } + + GlobalScope.launch(Main) { + val result = Fuel.post("$hostname/api/v1/token", body) + .awaitObjectResult(gsonDeserializerOf(FwCredentials::class.java)) + + result.fold( + { data -> + PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).apply { + setString("hostname", hostname) + setString("username", username) + setString("password", password) + setString("access_token", data.token) + } + + dialog.dismiss() + startActivity(Intent(this@LoginActivity, MainActivity::class.java)) + }, + { error -> + dialog.dismiss() + + hostname_field.error = error.localizedMessage + } + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt new file mode 100644 index 0000000..427373b --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt @@ -0,0 +1,303 @@ +package com.github.apognu.otter.activities + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.SeekBar +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.BrowseFragment +import com.github.apognu.otter.fragments.QueueFragment +import com.github.apognu.otter.playback.MediaControlsManager +import com.github.apognu.otter.playback.PlayerService +import com.github.apognu.otter.repositories.FavoritesRepository +import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.utils.* +import com.preference.PowerPreference +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.partial_now_playing.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class MainActivity : AppCompatActivity() { + private val favoriteRepository = FavoritesRepository(this) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + AppContext.init(this) + + setContentView(R.layout.activity_main) + setSupportActionBar(appbar) + + when (intent.action) { + MediaControlsManager.NOTIFICATION_ACTION_OPEN_QUEUE.toString() -> launchDialog(QueueFragment()) + } + + supportFragmentManager + .beginTransaction() + .replace(R.id.container, BrowseFragment()) + .commit() + + startService(Intent(this, PlayerService::class.java)) + + watchEventBus() + + CommandBus.send(Command.RefreshService) + } + + override fun onResume() { + super.onResume() + + now_playing_toggle.setOnClickListener { + CommandBus.send(Command.ToggleState) + } + + now_playing_next.setOnClickListener { + CommandBus.send(Command.NextTrack) + } + + now_playing_details_previous.setOnClickListener { + CommandBus.send(Command.PreviousTrack) + } + + now_playing_details_next.setOnClickListener { + CommandBus.send(Command.NextTrack) + } + + now_playing_details_toggle.setOnClickListener { + CommandBus.send(Command.ToggleState) + } + + now_playing_details_progress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onStopTrackingTouch(view: SeekBar?) {} + + override fun onStartTrackingTouch(view: SeekBar?) {} + + override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) { + CommandBus.send(Command.Seek(progress)) + } + } + }) + } + + override fun onBackPressed() { + if (now_playing.isOpened()) { + now_playing.close() + return + } + + super.onBackPressed() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.toolbar, menu) + + // CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.cast) + + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + now_playing.close() + + (supportFragmentManager.fragments.last() as? BrowseFragment)?.let { + it.selectTabAt(0) + + return true + } + + launchFragment(BrowseFragment()) + } + + R.id.nav_queue -> launchDialog(QueueFragment()) + R.id.nav_search -> startActivity(Intent(this, SearchActivity::class.java)) + R.id.settings -> startActivity(Intent(this, SettingsActivity::class.java)) + } + + return true + } + + private fun launchFragment(fragment: Fragment) { + supportFragmentManager.fragments.lastOrNull()?.also { oldFragment -> + oldFragment.enterTransition = null + oldFragment.exitTransition = null + + supportFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + } + + supportFragmentManager + .beginTransaction() + .setCustomAnimations(0, 0, 0, 0) + .replace(R.id.container, fragment) + .commit() + } + + private fun launchDialog(fragment: DialogFragment) { + supportFragmentManager.beginTransaction().let { + fragment.show(it, "") + } + } + + @SuppressLint("NewApi") + private fun watchEventBus() { + GlobalScope.launch(Main) { + for (message in EventBus.asChannel()) { + when (message) { + is Event.LogOut -> { + PowerPreference.clearAllData() + + startActivity(Intent(this@MainActivity, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NO_HISTORY + }) + + finish() + } + + is Event.PlaybackError -> toast(message.message) + + is Event.Buffering -> { + when (message.value) { + true -> now_playing_buffering.visibility = View.VISIBLE + false -> now_playing_buffering.visibility = View.GONE + } + } + + is Event.PlaybackStopped -> { + if (now_playing.visibility == View.VISIBLE) { + (container.layoutParams as? ViewGroup.MarginLayoutParams)?.let { + it.bottomMargin = it.bottomMargin / 2 + } + + now_playing.animate() + .alpha(0.0f) + .setDuration(400) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animator: Animator?) { + now_playing.visibility = View.GONE + } + }) + .start() + } + } + + is Event.TrackPlayed -> { + message.track?.let { track -> + if (now_playing.visibility == View.GONE) { + now_playing.visibility = View.VISIBLE + now_playing.alpha = 0f + + now_playing.animate() + .alpha(1.0f) + .setDuration(400) + .setListener(null) + .start() + + (container.layoutParams as? ViewGroup.MarginLayoutParams)?.let { + it.bottomMargin = it.bottomMargin * 2 + } + } + + now_playing_title.text = track.title + now_playing_album.text = track.artist.name + now_playing_toggle.icon = getDrawable(R.drawable.pause) + now_playing_progress.progress = 0 + + now_playing_details_title.text = track.title + now_playing_details_artist.text = track.artist.name + now_playing_details_toggle.icon = getDrawable(R.drawable.pause) + now_playing_details_progress.progress = 0 + + Picasso.get() + .load(normalizeUrl(track.album.cover.original)) + .fit() + .centerCrop() + .into(now_playing_cover) + + Picasso.get() + .load(normalizeUrl(track.album.cover.original)) + .fit() + .centerCrop() + .into(now_playing_details_cover) + + favoriteRepository.fetch().untilNetwork(IO) { favorites -> + GlobalScope.launch(Main) { + val favorites = favorites.map { it.track.id } + + track.favorite = favorites.contains(track.id) + when (track.favorite) { + true -> now_playing_details_favorite.setColorFilter(resources.getColor(R.color.colorFavorite)) + false -> now_playing_details_favorite.setColorFilter(resources.getColor(R.color.controlForeground)) + } + } + } + + now_playing_details_favorite.setOnClickListener { + when (track.favorite) { + true -> { + favoriteRepository.deleteFavorite(track.id) + now_playing_details_favorite.setColorFilter(resources.getColor(R.color.controlForeground)) + } + + false -> { + favoriteRepository.addFavorite(track.id) + now_playing_details_favorite.setColorFilter(resources.getColor(R.color.colorFavorite)) + } + } + + track.favorite = !track.favorite + + favoriteRepository.fetch(Repository.Origin.Network.origin) + } + } + } + + is Event.StateChanged -> { + when (message.playing) { + true -> { + now_playing_toggle.icon = getDrawable(R.drawable.pause) + now_playing_details_toggle.icon = getDrawable(R.drawable.pause) + } + + false -> { + now_playing_toggle.icon = getDrawable(R.drawable.play) + now_playing_details_toggle.icon = getDrawable(R.drawable.play) + } + } + } + } + } + } + + GlobalScope.launch(Main) { + for ((current, duration, percent) in ProgressBus.asChannel()) { + now_playing_progress.progress = percent + now_playing_details_progress.progress = percent + + val currentMins = (current / 1000) / 60 + val currentSecs = (current / 1000) % 60 + + val durationMins = duration / 60 + val durationSecs = duration % 60 + + now_playing_details_progress_current.text = "%02d:%02d".format(currentMins, currentSecs) + now_playing_details_progress_duration.text = "%02d:%02d".format(durationMins, durationSecs) + } + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt new file mode 100644 index 0000000..c7fd556 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt @@ -0,0 +1,66 @@ +package com.github.apognu.otter.activities + +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.github.apognu.otter.R +import com.github.apognu.otter.adapters.TracksAdapter +import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.repositories.SearchRepository +import com.github.apognu.otter.utils.untilNetwork +import kotlinx.android.synthetic.main.activity_search.* + +class SearchActivity : AppCompatActivity() { + private lateinit var adapter: TracksAdapter + + lateinit var repository: SearchRepository + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_search) + + adapter = TracksAdapter(this).also { + results.layoutManager = LinearLayoutManager(this) + results.adapter = it + } + } + + override fun onResume() { + super.onResume() + + search.requestFocus() + + search.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + query?.let { + repository = SearchRepository(this@SearchActivity, it.toLowerCase()) + + search_spinner.visibility = View.VISIBLE + search_no_results.visibility = View.GONE + + adapter.data.clear() + adapter.notifyDataSetChanged() + + repository.fetch(Repository.Origin.Network.origin).untilNetwork { tracks -> + search_spinner.visibility = View.GONE + search_empty.visibility = View.GONE + + when (tracks.isEmpty()) { + true -> search_no_results.visibility = View.VISIBLE + false -> adapter.data = tracks.toMutableList() + } + + adapter.notifyDataSetChanged() + } + } + + return true + } + + override fun onQueryTextChange(newText: String?) = true + + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt new file mode 100644 index 0000000..8138344 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt @@ -0,0 +1,121 @@ +package com.github.apognu.otter.activities + +import android.content.Intent +import android.content.SharedPreferences +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SeekBarPreference +import com.github.apognu.otter.R +import com.github.apognu.otter.utils.AppContext +import com.preference.PowerPreference + +class SettingsActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_settings) + + supportFragmentManager + .beginTransaction() + .replace( + R.id.container, + SettingsFragment() + ) + .commit() + } + + fun getThemeResId(): Int = R.style.AppTheme +} + +class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { + override fun onResume() { + super.onResume() + + preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.settings, rootKey) + + updateValues() + } + + override fun onPreferenceTreeClick(preference: Preference?): Boolean { + when (preference?.key) { + "oss_licences" -> startActivity(Intent(activity, LicencesActivity::class.java)) + "logout" -> { + context?.let { context -> + AlertDialog.Builder(context) + .setTitle(context.getString(R.string.logout_title)) + .setMessage(context.getString(R.string.logout_content)) + .setPositiveButton(android.R.string.yes) { _, _ -> + PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).clear() + + Intent(context, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + + startActivity(this) + activity?.finish() + } + } + .setNegativeButton(android.R.string.no, null) + .show() + } + } + } + + updateValues() + + return super.onPreferenceTreeClick(preference) + } + + override fun onSharedPreferenceChanged(preferences: SharedPreferences?, key: String?) { + updateValues() + } + + private fun updateValues() { + (activity as? AppCompatActivity)?.let { activity -> + preferenceManager.findPreference("media_quality")?.let { + it.summary = when (it.value) { + "quality" -> activity.getString(R.string.settings_media_quality_summary_quality) + "size" -> activity.getString(R.string.settings_media_quality_summary_size) + else -> activity.getString(R.string.settings_media_quality_summary_size) + } + } + + preferenceManager.findPreference("night_mode")?.let { + when (it.value) { + "on" -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + activity.delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES + + it.summary = getString(R.string.settings_night_mode_on_summary) + } + + "off" -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + activity.delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_NO + + it.summary = getString(R.string.settings_night_mode_off_summary) + } + + else -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + activity.delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + + it.summary = getString(R.string.settings_night_mode_system_summary) + } + } + } + + preferenceManager.findPreference("media_cache_size")?.let { + it.summary = getString(R.string.settings_media_cache_size_summary, it.value) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/AlbumsAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/AlbumsAdapter.kt new file mode 100644 index 0000000..f82d1b9 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/AlbumsAdapter.kt @@ -0,0 +1,55 @@ +package com.github.apognu.otter.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.FunkwhaleAdapter +import com.github.apognu.otter.utils.Album +import com.github.apognu.otter.utils.normalizeUrl +import com.squareup.picasso.Picasso +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.android.synthetic.main.row_album.view.* +import kotlinx.android.synthetic.main.row_artist.view.art + +class AlbumsAdapter(val context: Context?, val listener: OnAlbumClickListener) : FunkwhaleAdapter() { + interface OnAlbumClickListener { + fun onClick(view: View?, album: Album) + } + + override fun getItemCount() = data.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_album, parent, false) + + return ViewHolder(view, listener).also { + view.setOnClickListener(it) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val album = data[position] + + Picasso.get() + .load(normalizeUrl(album.cover.original)) + .fit() + .placeholder(R.drawable.cover) + .transform(RoundedCornersTransformation(16, 0)) + .into(holder.art) + + holder.title.text = album.title + holder.artist.text = album.artist.name + } + + inner class ViewHolder(view: View, val listener: OnAlbumClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener { + val art = view.art + val title = view.title + val artist = view.artist + + override fun onClick(view: View?) { + listener.onClick(view, data[layoutPosition]) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt new file mode 100644 index 0000000..afbc227 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt @@ -0,0 +1,52 @@ +package com.github.apognu.otter.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.FunkwhaleAdapter +import com.github.apognu.otter.utils.Album +import com.github.apognu.otter.utils.normalizeUrl +import com.squareup.picasso.Picasso +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.android.synthetic.main.row_album_grid.view.* + +class AlbumsGridAdapter(val context: Context?, val listener: OnAlbumClickListener) : FunkwhaleAdapter() { + interface OnAlbumClickListener { + fun onClick(view: View?, album: Album) + } + + override fun getItemCount() = data.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_album_grid, parent, false) + + return ViewHolder(view, listener).also { + view.setOnClickListener(it) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val album = data[position] + + Picasso.get() + .load(normalizeUrl(album.cover.original)) + .fit() + .placeholder(R.drawable.cover) + .transform(RoundedCornersTransformation(24, 0)) + .into(holder.cover) + + holder.title.text = album.title + } + + inner class ViewHolder(view: View, val listener: OnAlbumClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener { + val cover = view.cover + val title = view.title + + override fun onClick(view: View?) { + listener.onClick(view, data[layoutPosition]) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/ArtistsAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/ArtistsAdapter.kt new file mode 100644 index 0000000..ecb2859 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/ArtistsAdapter.kt @@ -0,0 +1,65 @@ +package com.github.apognu.otter.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.FunkwhaleAdapter +import com.github.apognu.otter.utils.Artist +import com.github.apognu.otter.utils.normalizeUrl +import com.squareup.picasso.Picasso +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.android.synthetic.main.row_artist.view.* + +class ArtistsAdapter(val context: Context?, val listener: OnArtistClickListener) : FunkwhaleAdapter() { + interface OnArtistClickListener { + fun onClick(holder: View?, artist: Artist) + } + + override fun getItemCount() = data.size + + override fun getItemId(position: Int) = data[position].id.toLong() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_artist, parent, false) + + return ViewHolder(view, listener).also { + view.setOnClickListener(it) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val artist = data[position] + + artist.albums?.let { albums -> + if (albums.isNotEmpty()) { + Picasso.get() + .load(normalizeUrl(albums[0].cover.original)) + .fit() + .placeholder(R.drawable.cover) + .transform(RoundedCornersTransformation(16, 0)) + .into(holder.art) + } + } + + holder.name.text = artist.name + + artist.albums?.let { + context?.let { + holder.albums.text = context.resources.getQuantityString(R.plurals.album_count, artist.albums.size, artist.albums.size) + } + } + } + + inner class ViewHolder(view: View, val listener: OnArtistClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener { + val art = view.art + val name = view.name + val albums = view.albums + + override fun onClick(view: View?) { + listener.onClick(view, data[layoutPosition]) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt new file mode 100644 index 0000000..e554667 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt @@ -0,0 +1,44 @@ +package com.github.apognu.otter.adapters + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.AlbumsGridFragment +import com.github.apognu.otter.fragments.ArtistsFragment +import com.github.apognu.otter.fragments.FavoritesFragment +import com.github.apognu.otter.fragments.PlaylistsFragment + +class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + var tabs = mutableListOf() + + override fun getCount() = 4 + + override fun getItem(position: Int): Fragment { + tabs.getOrNull(position)?.let { + return it + } + + val fragment = when (position) { + 0 -> ArtistsFragment() + 1 -> AlbumsGridFragment() + 2 -> PlaylistsFragment() + 3 -> FavoritesFragment() + else -> ArtistsFragment() + } + + tabs.add(position, fragment) + + return fragment + } + + override fun getPageTitle(position: Int): String { + return when (position) { + 0 -> context.getString(R.string.artists) + 1 -> context.getString(R.string.albums) + 2 -> context.getString(R.string.playlists) + 3 -> context.getString(R.string.favorites) + else -> "" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt new file mode 100644 index 0000000..8fceb78 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt @@ -0,0 +1,183 @@ +package com.github.apognu.otter.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.PopupMenu +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.FunkwhaleAdapter +import com.github.apognu.otter.utils.* +import com.squareup.picasso.Picasso +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.android.synthetic.main.row_track.view.* +import java.util.* + +class FavoritesAdapter(private val context: Context?, val favoriteListener: OnFavoriteListener, val fromQueue: Boolean = false) : FunkwhaleAdapter() { + interface OnFavoriteListener { + fun onToggleFavorite(id: Int, state: Boolean) + } + + var currentTrack: Track? = null + + override fun getItemCount() = data.size + + override fun getItemId(position: Int): Long { + return data[position].track.id.toLong() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_track, parent, false) + + return ViewHolder(view, context).also { + view.setOnClickListener(it) + } + } + + @SuppressLint("NewApi") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val favorite = data[position] + + Picasso.get() + .load(normalizeUrl(favorite.track.album.cover.original)) + .fit() + .placeholder(R.drawable.cover) + .transform(RoundedCornersTransformation(16, 0)) + .into(holder.cover) + + holder.title.text = favorite.track.title + holder.artist.text = favorite.track.artist.name + + Build.VERSION_CODES.P.onApi( + { + holder.title.setTypeface(holder.title.typeface, Typeface.DEFAULT.weight) + holder.artist.setTypeface(holder.artist.typeface, Typeface.DEFAULT.weight) + }, + { + holder.title.setTypeface(holder.title.typeface, Typeface.NORMAL) + holder.artist.setTypeface(holder.artist.typeface, Typeface.NORMAL) + }) + + + if (favorite.track == currentTrack || favorite.track.current) { + holder.title.setTypeface(holder.title.typeface, Typeface.BOLD) + holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD) + } + + context?.let { + when (favorite.track.favorite) { + true -> holder.favorite.setColorFilter(context.resources.getColor(R.color.colorFavorite)) + false -> holder.favorite.setColorFilter(context.resources.getColor(R.color.colorSelected)) + } + + holder.favorite.setOnClickListener { + favoriteListener.onToggleFavorite(favorite.track.id, !favorite.track.favorite) + + data.remove(favorite) + notifyItemRemoved(holder.adapterPosition) + } + } + + holder.actions.setOnClickListener { + context?.let { context -> + PopupMenu(context, holder.actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply { + inflate(if (fromQueue) R.menu.row_queue else R.menu.row_track) + + setOnMenuItemClickListener { + when (it.itemId) { + R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(favorite.track))) + R.id.track_play_next -> CommandBus.send(Command.PlayNext(favorite.track)) + R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(favorite.track)) + } + + true + } + + show() + } + } + } + } + + fun onItemMove(oldPosition: Int, newPosition: Int) { + if (oldPosition < newPosition) { + for (i in oldPosition.rangeTo(newPosition - 1)) { + Collections.swap(data, i, i + 1) + } + } else { + for (i in newPosition.downTo(oldPosition)) { + Collections.swap(data, i, i - 1) + } + } + + notifyItemMoved(oldPosition, newPosition) + CommandBus.send(Command.MoveFromQueue(oldPosition, newPosition)) + } + + inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener { + val handle = view.handle + val cover = view.cover + val title = view.title + val artist = view.artist + + val favorite = view.favorite + val actions = view.actions + + override fun onClick(view: View?) { + when (fromQueue) { + true -> CommandBus.send(Command.PlayTrack(layoutPosition)) + false -> { + data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply { + CommandBus.send(Command.ReplaceQueue(this.map { it.track })) + + context.toast("All tracks were added to your queue") + } + } + } + } + } + + inner class TouchHelperCallback : ItemTouchHelper.Callback() { + override fun isLongPressDragEnabled() = false + + override fun isItemViewSwipeEnabled() = false + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) = + makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + onItemMove(viewHolder.adapterPosition, target.adapterPosition) + + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + @SuppressLint("NewApi") + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + context?.let { + Build.VERSION_CODES.M.onApi( + { viewHolder?.itemView?.background = ColorDrawable(context.resources.getColor(R.color.colorSelected, null)) }, + { viewHolder?.itemView?.background = ColorDrawable(context.resources.getColor(R.color.colorSelected)) }) + } + } + + super.onSelectedChanged(viewHolder, actionState) + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + viewHolder.itemView.background = ColorDrawable(Color.TRANSPARENT) + + super.clearView(recyclerView, viewHolder) + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/adapters/PlaylistTracksAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/PlaylistTracksAdapter.kt new file mode 100644 index 0000000..0229ee0 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/PlaylistTracksAdapter.kt @@ -0,0 +1,179 @@ +package com.github.apognu.otter.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.view.* +import androidx.appcompat.widget.PopupMenu +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.FunkwhaleAdapter +import com.github.apognu.otter.utils.* +import com.squareup.picasso.Picasso +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.android.synthetic.main.row_track.view.* +import java.util.* + +class PlaylistTracksAdapter(private val context: Context?, val fromQueue: Boolean = false) : FunkwhaleAdapter() { + private lateinit var touchHelper: ItemTouchHelper + + var currentTrack: Track? = null + + override fun getItemCount() = data.size + + override fun getItemId(position: Int): Long { + return data[position].track.id.toLong() + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + + if (fromQueue) { + touchHelper = ItemTouchHelper(TouchHelperCallback()).also { + it.attachToRecyclerView(recyclerView) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_track, parent, false) + + return ViewHolder(view, context).also { + view.setOnClickListener(it) + } + } + + @SuppressLint("NewApi") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val track = data[position] + + Picasso.get() + .load(normalizeUrl(track.track.album.cover.original)) + .fit() + .placeholder(R.drawable.cover) + .transform(RoundedCornersTransformation(16, 0)) + .into(holder.cover) + + holder.title.text = track.track.title + holder.artist.text = track.track.artist.name + + Build.VERSION_CODES.P.onApi( + { + holder.title.setTypeface(holder.title.typeface, Typeface.DEFAULT.weight) + holder.artist.setTypeface(holder.artist.typeface, Typeface.DEFAULT.weight) + }, + { + holder.title.setTypeface(holder.title.typeface, Typeface.NORMAL) + holder.artist.setTypeface(holder.artist.typeface, Typeface.NORMAL) + }) + + + if (track.track == currentTrack || track.track.current) { + holder.title.setTypeface(holder.title.typeface, Typeface.BOLD) + holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD) + } + + holder.actions.setOnClickListener { + context?.let { context -> + PopupMenu(context, holder.actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply { + inflate(if (fromQueue) R.menu.row_queue else R.menu.row_track) + + setOnMenuItemClickListener { + when (it.itemId) { + R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track.track))) + R.id.track_play_next -> CommandBus.send(Command.PlayNext(track.track)) + R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track.track)) + } + + true + } + + show() + } + } + } + + if (fromQueue) { + holder.handle.visibility = View.VISIBLE + + holder.handle.setOnTouchListener { _, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + touchHelper.startDrag(holder) + } + + true + } + } + } + + fun onItemMove(oldPosition: Int, newPosition: Int) { + if (oldPosition < newPosition) { + for (i in oldPosition.rangeTo(newPosition - 1)) { + Collections.swap(data, i, i + 1) + } + } else { + for (i in newPosition.downTo(oldPosition)) { + Collections.swap(data, i, i - 1) + } + } + + notifyItemMoved(oldPosition, newPosition) + CommandBus.send(Command.MoveFromQueue(oldPosition, newPosition)) + } + + inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener { + val handle = view.handle + val cover = view.cover + val title = view.title + val artist = view.artist + val actions = view.actions + + override fun onClick(view: View?) { + when (fromQueue) { + true -> CommandBus.send(Command.PlayTrack(layoutPosition)) + false -> { + data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply { + CommandBus.send(Command.ReplaceQueue(this.map { it.track })) + + context.toast("All tracks were added to your queue") + } + } + } + } + } + + inner class TouchHelperCallback : ItemTouchHelper.Callback() { + override fun isLongPressDragEnabled() = false + + override fun isItemViewSwipeEnabled() = false + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) = + makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + onItemMove(viewHolder.adapterPosition, target.adapterPosition) + + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + viewHolder?.itemView?.background = ColorDrawable(Color.argb(255, 100, 100, 100)) + } + + super.onSelectedChanged(viewHolder, actionState) + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + viewHolder.itemView.background = ColorDrawable(Color.TRANSPARENT) + + super.clearView(recyclerView, viewHolder) + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/adapters/PlaylistsAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/PlaylistsAdapter.kt new file mode 100644 index 0000000..ed410dd --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/PlaylistsAdapter.kt @@ -0,0 +1,65 @@ +package com.github.apognu.otter.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.FunkwhaleAdapter +import com.github.apognu.otter.utils.Playlist +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.row_playlist.view.* + +class PlaylistsAdapter(val context: Context?, val listener: OnPlaylistClickListener) : FunkwhaleAdapter() { + interface OnPlaylistClickListener { + fun onClick(holder: View?, playlist: Playlist) + } + + override fun getItemCount() = data.size + + override fun getItemId(position: Int) = data[position].id.toLong() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_playlist, parent, false) + + return ViewHolder(view, listener).also { + view.setOnClickListener(it) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val playlist = data[position] + + holder.name.text = playlist.name + holder.summary.text = "${playlist.tracks_count} tracks • ${playlist.duration} seconds" + + playlist.album_covers.shuffled().take(4).forEachIndexed { index, url -> + val imageView = when (index) { + 0 -> holder.cover_top_left + 1 -> holder.cover_top_right + 2 -> holder.cover_bottom_left + 3 -> holder.cover_bottom_right + else -> holder.cover_top_left + } + + Picasso.get() + .load(url) + .into(imageView) + } + } + + inner class ViewHolder(view: View, val listener: OnPlaylistClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener { + val name = view.name + val summary = view.summary + + val cover_top_left = view.cover_top_left + val cover_top_right = view.cover_top_right + val cover_bottom_left = view.cover_bottom_left + val cover_bottom_right = view.cover_bottom_right + + override fun onClick(view: View?) { + listener.onClick(view, data[layoutPosition]) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/SearchResultsAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/SearchResultsAdapter.kt new file mode 100644 index 0000000..aa25302 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/SearchResultsAdapter.kt @@ -0,0 +1,32 @@ +package com.github.apognu.otter.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.utils.Track +import kotlinx.android.synthetic.main.row_track.view.* + +class SearchResultsAdapter(val context: Context?) : RecyclerView.Adapter() { + var tracks: List = listOf() + + override fun getItemCount() = tracks.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_track, parent, false) + + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val artist = tracks[position] + + holder.title.text = artist.title + } + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val title = view.title + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/adapters/TracksAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/TracksAdapter.kt new file mode 100644 index 0000000..7079e92 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/TracksAdapter.kt @@ -0,0 +1,206 @@ +package com.github.apognu.otter.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.view.* +import androidx.appcompat.widget.PopupMenu +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.fragments.FunkwhaleAdapter +import com.github.apognu.otter.utils.* +import com.squareup.picasso.Picasso +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.android.synthetic.main.row_track.view.* +import java.util.* + +class TracksAdapter(private val context: Context?, val favoriteListener: OnFavoriteListener? = null, val fromQueue: Boolean = false) : FunkwhaleAdapter() { + interface OnFavoriteListener { + fun onToggleFavorite(id: Int, state: Boolean) + } + + private lateinit var touchHelper: ItemTouchHelper + + var currentTrack: Track? = null + + override fun getItemCount() = data.size + + override fun getItemId(position: Int): Long { + return data[position].id.toLong() + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + + if (fromQueue) { + touchHelper = ItemTouchHelper(TouchHelperCallback()).also { + it.attachToRecyclerView(recyclerView) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.row_track, parent, false) + + return ViewHolder(view, context).also { + view.setOnClickListener(it) + } + } + + @SuppressLint("NewApi") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val track = data[position] + + Picasso.get() + .load(normalizeUrl(track.album.cover.original)) + .fit() + .placeholder(R.drawable.cover) + .transform(RoundedCornersTransformation(16, 0)) + .into(holder.cover) + + holder.title.text = track.title + holder.artist.text = track.artist.name + + Build.VERSION_CODES.P.onApi( + { + holder.title.setTypeface(holder.title.typeface, Typeface.DEFAULT.weight) + holder.artist.setTypeface(holder.artist.typeface, Typeface.DEFAULT.weight) + }, + { + holder.title.setTypeface(holder.title.typeface, Typeface.NORMAL) + holder.artist.setTypeface(holder.artist.typeface, Typeface.NORMAL) + }) + + + if (track == currentTrack || track.current) { + holder.title.setTypeface(holder.title.typeface, Typeface.BOLD) + holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD) + } + + context?.let { + when (track.favorite) { + true -> holder.favorite.setColorFilter(context.resources.getColor(R.color.colorFavorite)) + false -> holder.favorite.setColorFilter(context.resources.getColor(R.color.colorSelected)) + } + + holder.favorite.setOnClickListener { + favoriteListener?.let { + favoriteListener.onToggleFavorite(track.id, !track.favorite) + + track.favorite = !track.favorite + notifyItemChanged(position) + } + } + } + + holder.actions.setOnClickListener { + context?.let { context -> + PopupMenu(context, holder.actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply { + inflate(if (fromQueue) R.menu.row_queue else R.menu.row_track) + + setOnMenuItemClickListener { + when (it.itemId) { + R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track))) + R.id.track_play_next -> CommandBus.send(Command.PlayNext(track)) + R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track)) + } + + true + } + + show() + } + } + } + + if (fromQueue) { + holder.handle.visibility = View.VISIBLE + + holder.handle.setOnTouchListener { _, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + touchHelper.startDrag(holder) + } + + true + } + } + } + + fun onItemMove(oldPosition: Int, newPosition: Int) { + if (oldPosition < newPosition) { + for (i in oldPosition.rangeTo(newPosition - 1)) { + Collections.swap(data, i, i + 1) + } + } else { + for (i in newPosition.downTo(oldPosition)) { + Collections.swap(data, i, i - 1) + } + } + + notifyItemMoved(oldPosition, newPosition) + CommandBus.send(Command.MoveFromQueue(oldPosition, newPosition)) + } + + inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener { + val handle = view.handle + val cover = view.cover + val title = view.title + val artist = view.artist + + val favorite = view.favorite + val actions = view.actions + + override fun onClick(view: View?) { + when (fromQueue) { + true -> CommandBus.send(Command.PlayTrack(layoutPosition)) + false -> { + data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply { + CommandBus.send(Command.ReplaceQueue(this)) + + context.toast("All tracks were added to your queue") + } + } + } + } + } + + inner class TouchHelperCallback : ItemTouchHelper.Callback() { + override fun isLongPressDragEnabled() = false + + override fun isItemViewSwipeEnabled() = false + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) = + makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + onItemMove(viewHolder.adapterPosition, target.adapterPosition) + + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + @SuppressLint("NewApi") + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + context?.let { + Build.VERSION_CODES.M.onApi( + { viewHolder?.itemView?.background = ColorDrawable(context.resources.getColor(R.color.colorSelected, null)) }, + { viewHolder?.itemView?.background = ColorDrawable(context.resources.getColor(R.color.colorSelected)) }) + } + } + + super.onSelectedChanged(viewHolder, actionState) + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + viewHolder.itemView.background = ColorDrawable(Color.TRANSPARENT) + + super.clearView(recyclerView, viewHolder) + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt new file mode 100644 index 0000000..de746b8 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt @@ -0,0 +1,93 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.core.os.bundleOf +import androidx.recyclerview.widget.RecyclerView +import androidx.transition.Fade +import androidx.transition.Slide +import com.github.apognu.otter.R +import com.github.apognu.otter.activities.MainActivity +import com.github.apognu.otter.adapters.AlbumsAdapter +import com.github.apognu.otter.repositories.AlbumsRepository +import com.github.apognu.otter.utils.Album +import com.github.apognu.otter.utils.AppContext +import com.github.apognu.otter.utils.Artist +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.fragment_albums.* + +class AlbumsFragment : FunkwhaleFragment() { + override val viewRes = R.layout.fragment_albums + override val recycler: RecyclerView get() = albums + + var artistId = 0 + var artistName = "" + var artistArt = "" + + companion object { + fun new(artist: Artist): AlbumsFragment { + return AlbumsFragment().apply { + arguments = bundleOf( + "artistId" to artist.id, + "artistName" to artist.name, + "artistArt" to artist.albums!![0].cover.original + ) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + arguments?.apply { + artistId = getInt("artistId") + artistName = getString("artistName") ?: "" + artistArt = getString("artistArt") ?: "" + } + + adapter = AlbumsAdapter(context, OnAlbumClickListener()) + repository = AlbumsRepository(context, artistId) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + Picasso.get() + .load(artistArt) + .noFade() + .fit() + .centerCrop() + .into(cover) + + artist.text = artistName + } + + inner class OnAlbumClickListener : AlbumsAdapter.OnAlbumClickListener { + override fun onClick(holder: View?, album: Album) { + (context as? MainActivity)?.let { activity -> + exitTransition = Fade().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + + view?.let { + addTarget(it) + } + } + + val fragment = TracksFragment.new(album).apply { + enterTransition = Slide().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + } + } + + activity.supportFragmentManager + .beginTransaction() + .replace(R.id.container, fragment) + .addToBackStack(null) + .commit() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/AlbumsGridFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsGridFragment.kt new file mode 100644 index 0000000..c463d97 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsGridFragment.kt @@ -0,0 +1,60 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.transition.Fade +import androidx.transition.Slide +import com.github.apognu.otter.R +import com.github.apognu.otter.activities.MainActivity +import com.github.apognu.otter.adapters.AlbumsGridAdapter +import com.github.apognu.otter.repositories.AlbumsRepository +import com.github.apognu.otter.utils.Album +import com.github.apognu.otter.utils.AppContext +import com.github.apognu.otter.utils.onViewPager +import kotlinx.android.synthetic.main.fragment_albums_grid.* + +class AlbumsGridFragment : FunkwhaleFragment() { + override val viewRes = R.layout.fragment_albums_grid + override val recycler: RecyclerView get() = albums + override val layoutManager get() = GridLayoutManager(context, 3) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + adapter = AlbumsGridAdapter(context, OnAlbumClickListener()) + repository = AlbumsRepository(context) + } + + inner class OnAlbumClickListener : AlbumsGridAdapter.OnAlbumClickListener { + override fun onClick(holder: View?, album: Album) { + (context as? MainActivity)?.let { activity -> + onViewPager { + exitTransition = Fade().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + + view?.let { + addTarget(it) + } + } + } + + val fragment = TracksFragment.new(album).apply { + enterTransition = Slide().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + } + } + + activity.supportFragmentManager + .beginTransaction() + .replace(R.id.container, fragment) + .addToBackStack(null) + .commit() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt new file mode 100644 index 0000000..46884b0 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt @@ -0,0 +1,58 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.recyclerview.widget.RecyclerView +import androidx.transition.Fade +import androidx.transition.Slide +import com.github.apognu.otter.R +import com.github.apognu.otter.activities.MainActivity +import com.github.apognu.otter.adapters.ArtistsAdapter +import com.github.apognu.otter.repositories.ArtistsRepository +import com.github.apognu.otter.utils.AppContext +import com.github.apognu.otter.utils.Artist +import com.github.apognu.otter.utils.onViewPager +import kotlinx.android.synthetic.main.fragment_artists.* + +class ArtistsFragment : FunkwhaleFragment() { + override val viewRes = R.layout.fragment_artists + override val recycler: RecyclerView get() = artists + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + adapter = ArtistsAdapter(context, OnArtistClickListener()) + repository = ArtistsRepository(context) + } + + inner class OnArtistClickListener : ArtistsAdapter.OnArtistClickListener { + override fun onClick(holder: View?, artist: Artist) { + (context as? MainActivity)?.let { activity -> + onViewPager { + exitTransition = Fade().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + + view?.let { + addTarget(it) + } + } + } + + val fragment = AlbumsFragment.new(artist).apply { + enterTransition = Slide().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + } + } + + activity.supportFragmentManager + .beginTransaction() + .replace(R.id.container, fragment) + .addToBackStack(null) + .commit() + } + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/fragments/BrowseFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/BrowseFragment.kt new file mode 100644 index 0000000..33eec35 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/BrowseFragment.kt @@ -0,0 +1,34 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.github.apognu.otter.R +import com.github.apognu.otter.adapters.BrowseTabsAdapter +import kotlinx.android.synthetic.main.fragment_browse.view.* + +class BrowseFragment : Fragment() { + var adapter: BrowseTabsAdapter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + adapter = BrowseTabsAdapter(this, childFragmentManager) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_browse, container, false).apply { + tabs.setupWithViewPager(pager) + tabs.getTabAt(0)?.select() + + pager.adapter = adapter + pager.offscreenPageLimit = 4 + } + } + + fun selectTabAt(position: Int) { + view?.tabs?.getTabAt(position)?.select() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt new file mode 100644 index 0000000..dc72bfe --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt @@ -0,0 +1,71 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.adapters.FavoritesAdapter +import com.github.apognu.otter.repositories.FavoritesRepository +import com.github.apognu.otter.utils.* +import kotlinx.android.synthetic.main.fragment_favorites.* +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class FavoritesFragment : FunkwhaleFragment() { + override val viewRes = R.layout.fragment_favorites + override val recycler: RecyclerView get() = favorites + + lateinit var favoritesRepository: FavoritesRepository + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + adapter = FavoritesAdapter(context, FavoriteListener()) + repository = FavoritesRepository(context) + favoritesRepository = FavoritesRepository(context) + + watchEventBus() + } + + override fun onResume() { + super.onResume() + + GlobalScope.launch(Main) { + RequestBus.send(Request.GetCurrentTrack).wait()?.let { response -> + adapter.currentTrack = response.track + adapter.notifyDataSetChanged() + } + } + + play.setOnClickListener { + CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled().map { it.track })) + } + } + + private fun watchEventBus() { + GlobalScope.launch(Main) { + for (message in EventBus.asChannel()) { + when (message) { + is Event.TrackPlayed -> { + GlobalScope.launch(Main) { + RequestBus.send(Request.GetCurrentTrack).wait()?.let { response -> + adapter.currentTrack = response.track + adapter.notifyDataSetChanged() + } + } + } + } + } + } + } + + inner class FavoriteListener : FavoritesAdapter.OnFavoriteListener { + override fun onToggleFavorite(id: Int, state: Boolean) { + when (state) { + true -> favoritesRepository.addFavorite(id) + false -> favoritesRepository.deleteFavorite(id) + } + } + + } +} diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt new file mode 100644 index 0000000..3f725c5 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt @@ -0,0 +1,80 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.NestedScrollView +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.utils.untilNetwork +import kotlinx.android.synthetic.main.fragment_artists.* + +abstract class FunkwhaleAdapter : RecyclerView.Adapter() { + var data: MutableList = mutableListOf() +} + +abstract class FunkwhaleFragment> : Fragment() { + abstract val viewRes: Int + abstract val recycler: RecyclerView + open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context) + + lateinit var repository: Repository + lateinit var adapter: A + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(viewRes, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + recycler.layoutManager = layoutManager + recycler.adapter = adapter + + scroller?.setOnScrollChangeListener { _: NestedScrollView?, _: Int, _: Int, _: Int, _: Int -> + if (!scroller.canScrollVertically(1)) { + repository.fetch(Repository.Origin.Network.origin, adapter.data).untilNetwork { + swiper?.isRefreshing = false + + onDataFetched(it) + + adapter.data = it.toMutableList() + adapter.notifyDataSetChanged() + } + } + } + + swiper?.isRefreshing = true + + repository.fetch().untilNetwork { + swiper?.isRefreshing = false + + onDataFetched(it) + + adapter.data = it.toMutableList() + adapter.notifyDataSetChanged() + } + } + + override fun onResume() { + super.onResume() + + recycler.adapter = adapter + + swiper?.setOnRefreshListener { + repository.fetch(Repository.Origin.Network.origin, listOf()).untilNetwork { + swiper?.isRefreshing = false + + onDataFetched(it) + + adapter.data = it.toMutableList() + adapter.notifyDataSetChanged() + } + } + } + + open fun onDataFetched(data: List) {} +} diff --git a/app/src/main/java/com/github/apognu/otter/fragments/LoginDialog.kt b/app/src/main/java/com/github/apognu/otter/fragments/LoginDialog.kt new file mode 100644 index 0000000..4fd4682 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/LoginDialog.kt @@ -0,0 +1,23 @@ +package com.github.apognu.otter.fragments + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.github.apognu.otter.R + +class LoginDialog : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(getString(R.string.login_logging_in)) + setView(R.layout.dialog_login) + }.create() + } + + override fun onResume() { + super.onResume() + + dialog?.setCanceledOnTouchOutside(false) + dialog?.setCancelable(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt new file mode 100644 index 0000000..505b5b7 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt @@ -0,0 +1,120 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.adapters.PlaylistTracksAdapter +import com.github.apognu.otter.repositories.PlaylistTracksRepository +import com.github.apognu.otter.utils.* +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.fragment_tracks.* +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class PlaylistTracksFragment : FunkwhaleFragment() { + override val viewRes = R.layout.fragment_tracks + override val recycler: RecyclerView get() = tracks + + var albumId = 0 + var albumArtist = "" + var albumTitle = "" + var albumCover = "" + + companion object { + fun new(playlist: Playlist): PlaylistTracksFragment { + return PlaylistTracksFragment().apply { + arguments = bundleOf( + "albumId" to playlist.id, + "albumArtist" to "N/A", + "albumTitle" to playlist.name, + "albumCover" to "" + ) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + arguments?.apply { + albumId = getInt("albumId") + albumArtist = getString("albumArtist") ?: "" + albumTitle = getString("albumTitle") ?: "" + albumCover = getString("albumCover") ?: "" + } + + adapter = PlaylistTracksAdapter(context) + repository = PlaylistTracksRepository(context, albumId) + + watchEventBus() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + cover.visibility = View.INVISIBLE + covers.visibility = View.VISIBLE + + artist.text = "Playlist" + title.text = albumTitle + } + + override fun onResume() { + super.onResume() + + GlobalScope.launch(Main) { + RequestBus.send(Request.GetCurrentTrack).wait()?.let { response -> + adapter.currentTrack = response.track + adapter.notifyDataSetChanged() + } + } + + play.setOnClickListener { + CommandBus.send(Command.ReplaceQueue(adapter.data.map { it.track }.shuffled())) + + context.toast("All tracks were added to your queue") + } + + queue.setOnClickListener { + CommandBus.send(Command.AddToQueue(adapter.data.map { it.track })) + + context.toast("All tracks were added to your queue") + } + } + + override fun onDataFetched(data: List) { + data.map { it.track.album }.toSet().map { it.cover.original }.take(4).forEachIndexed { index, url -> + val imageView = when (index) { + 0 -> cover_top_left + 1 -> cover_top_right + 2 -> cover_bottom_left + 3 -> cover_bottom_right + else -> cover_top_left + } + + Picasso.get() + .load(normalizeUrl(url)) + .into(imageView) + } + } + + private fun watchEventBus() { + GlobalScope.launch(Main) { + for (message in EventBus.asChannel()) { + when (message) { + is Event.TrackPlayed -> { + GlobalScope.launch(Main) { + RequestBus.send(Request.GetCurrentTrack).wait()?.let { response -> + adapter.currentTrack = response.track + adapter.notifyDataSetChanged() + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/PlaylistsFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/PlaylistsFragment.kt new file mode 100644 index 0000000..b24e925 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/PlaylistsFragment.kt @@ -0,0 +1,55 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.recyclerview.widget.RecyclerView +import androidx.transition.Fade +import androidx.transition.Slide +import com.github.apognu.otter.R +import com.github.apognu.otter.activities.MainActivity +import com.github.apognu.otter.adapters.PlaylistsAdapter +import com.github.apognu.otter.repositories.PlaylistsRepository +import com.github.apognu.otter.utils.AppContext +import com.github.apognu.otter.utils.Playlist +import kotlinx.android.synthetic.main.fragment_playlists.* + +class PlaylistsFragment : FunkwhaleFragment() { + override val viewRes = R.layout.fragment_playlists + override val recycler: RecyclerView get() = playlists + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + adapter = PlaylistsAdapter(context, OnPlaylistClickListener()) + repository = PlaylistsRepository(context) + } + + inner class OnPlaylistClickListener : PlaylistsAdapter.OnPlaylistClickListener { + override fun onClick(holder: View?, playlist: Playlist) { + (context as? MainActivity)?.let { activity -> + exitTransition = Fade().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + + view?.let { + addTarget(it) + } + } + + val fragment = PlaylistTracksFragment.new(playlist).apply { + enterTransition = Slide().apply { + duration = AppContext.TRANSITION_DURATION + interpolator = AccelerateDecelerateInterpolator() + } + } + + activity.supportFragmentManager + .beginTransaction() + .replace(R.id.container, fragment) + .addToBackStack(null) + .commit() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/QueueFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/QueueFragment.kt new file mode 100644 index 0000000..8f2a7e5 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/QueueFragment.kt @@ -0,0 +1,89 @@ +package com.github.apognu.otter.fragments + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.github.apognu.otter.R +import com.github.apognu.otter.adapters.TracksAdapter +import com.github.apognu.otter.utils.* +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.fragment_queue.* +import kotlinx.android.synthetic.main.fragment_queue.view.* +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class QueueFragment : BottomSheetDialogFragment() { + private var adapter: TracksAdapter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_FloatingBottomSheet) + + watchEventBus() + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return super.onCreateDialog(savedInstanceState).apply { + setOnShowListener { + findViewById(com.google.android.material.R.id.design_bottom_sheet)?.let { + BottomSheetBehavior.from(it).skipCollapsed = true + } + } + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_queue, container, false).apply { + adapter = TracksAdapter(context, fromQueue = true).also { + queue.layoutManager = LinearLayoutManager(context) + queue.adapter = it + } + } + } + + override fun onResume() { + super.onResume() + + queue?.visibility = View.GONE + placeholder?.visibility = View.VISIBLE + + refresh() + } + + private fun refresh() { + GlobalScope.launch(Main) { + RequestBus.send(Request.GetQueue).wait()?.let { response -> + adapter?.let { + it.data = response.queue.toMutableList() + it.notifyDataSetChanged() + + if (it.data.isEmpty()) { + queue?.visibility = View.GONE + placeholder?.visibility = View.VISIBLE + } else { + queue?.visibility = View.VISIBLE + placeholder?.visibility = View.GONE + } + } + } + } + } + + private fun watchEventBus() { + GlobalScope.launch(Main) { + for (message in EventBus.asChannel()) { + when (message) { + is Event.TrackPlayed -> refresh() + is Event.QueueChanged -> refresh() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/TracksFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/TracksFragment.kt new file mode 100644 index 0000000..3b19c08 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/TracksFragment.kt @@ -0,0 +1,122 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.R +import com.github.apognu.otter.adapters.TracksAdapter +import com.github.apognu.otter.repositories.FavoritesRepository +import com.github.apognu.otter.repositories.TracksRepository +import com.github.apognu.otter.utils.* +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.fragment_tracks.* +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class TracksFragment : FunkwhaleFragment() { + override val viewRes = R.layout.fragment_tracks + override val recycler: RecyclerView get() = tracks + + lateinit var favoritesRepository: FavoritesRepository + + var albumId = 0 + var albumArtist = "" + var albumTitle = "" + var albumCover = "" + + companion object { + fun new(album: Album): TracksFragment { + return TracksFragment().apply { + arguments = bundleOf( + "albumId" to album.id, + "albumArtist" to album.artist.name, + "albumTitle" to album.title, + "albumCover" to album.cover.original + ) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + arguments?.apply { + albumId = getInt("albumId") + albumArtist = getString("albumArtist") ?: "" + albumTitle = getString("albumTitle") ?: "" + albumCover = getString("albumCover") ?: "" + } + + adapter = TracksAdapter(context, FavoriteListener()) + repository = TracksRepository(context, albumId) + favoritesRepository = FavoritesRepository(context) + + watchEventBus() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + Picasso.get() + .load(albumCover) + .noFade() + .fit() + .centerCrop() + .into(cover) + + artist.text = albumArtist + title.text = albumTitle + } + + override fun onResume() { + super.onResume() + + GlobalScope.launch(Main) { + RequestBus.send(Request.GetCurrentTrack).wait()?.let { response -> + adapter.currentTrack = response.track + adapter.notifyDataSetChanged() + } + } + + play.setOnClickListener { + CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled())) + + context.toast("All tracks were added to your queue") + } + + queue.setOnClickListener { + CommandBus.send(Command.AddToQueue(adapter.data)) + + context.toast("All tracks were added to your queue") + } + } + + private fun watchEventBus() { + GlobalScope.launch(Main) { + for (message in EventBus.asChannel()) { + when (message) { + is Event.TrackPlayed -> { + GlobalScope.launch(Main) { + RequestBus.send(Request.GetCurrentTrack).wait()?.let { response -> + adapter.currentTrack = response.track + adapter.notifyDataSetChanged() + } + } + } + } + } + } + } + + inner class FavoriteListener : TracksAdapter.OnFavoriteListener { + override fun onToggleFavorite(id: Int, state: Boolean) { + when (state) { + true -> favoritesRepository.addFavorite(id) + false -> favoritesRepository.deleteFavorite(id) + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt b/app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt new file mode 100644 index 0000000..1855ebc --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt @@ -0,0 +1,125 @@ +package com.github.apognu.otter.playback + +import android.app.Notification +import android.app.PendingIntent +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.media.MediaMetadata +import android.support.v4.media.MediaMetadataCompat +import android.support.v4.media.session.MediaSessionCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.media.app.NotificationCompat.MediaStyle +import com.github.apognu.otter.R +import com.github.apognu.otter.activities.MainActivity +import com.github.apognu.otter.utils.* +import com.squareup.picasso.Picasso +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class MediaControlsManager(val context: Service, val mediaSession: MediaSessionCompat) { + companion object { + const val NOTIFICATION_ACTION_OPEN_QUEUE = 0 + const val NOTIFICATION_ACTION_PREVIOUS = 1 + const val NOTIFICATION_ACTION_TOGGLE = 2 + const val NOTIFICATION_ACTION_NEXT = 3 + const val NOTIFICATION_ACTION_FAVORITE = 4 + } + + var notification: Notification? = null + + fun updateNotification(track: Track?, playing: Boolean) { + if (notification == null && !playing) return + + track?.let { + val stateIcon = when (playing) { + true -> R.drawable.pause + false -> R.drawable.play + } + + GlobalScope.launch(IO) { + val openIntent = Intent(context, MainActivity::class.java).apply { action = NOTIFICATION_ACTION_OPEN_QUEUE.toString() } + val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, 0) + + mediaSession.setMetadata(MediaMetadataCompat.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, track.artist.name) + putString(MediaMetadata.METADATA_KEY_TITLE, track.title) + }.build()) + + notification = NotificationCompat.Builder( + context, + AppContext.NOTIFICATION_CHANNEL_MEDIA_CONTROL + ) + .setShowWhen(false) + .setStyle( + MediaStyle() + .setMediaSession(mediaSession.sessionToken) + .setShowActionsInCompactView(0, 1, 2) + ) + .setSmallIcon(R.drawable.ottericon) + .setLargeIcon(Picasso.get().load(normalizeUrl(track.album.cover.original)).get()) + .setContentTitle(track.title) + .setContentText(track.artist.name) + .setContentIntent(openPendingIntent) + .setChannelId(AppContext.NOTIFICATION_CHANNEL_MEDIA_CONTROL) + .addAction( + action( + R.drawable.previous, context.getString(R.string.control_previous), + NOTIFICATION_ACTION_PREVIOUS + ) + ) + .addAction( + action( + stateIcon, context.getString(R.string.control_toggle), + NOTIFICATION_ACTION_TOGGLE + ) + ) + .addAction( + action( + R.drawable.next, context.getString(R.string.control_next), + NOTIFICATION_ACTION_NEXT + ) + ) + .build() + + notification?.let { + NotificationManagerCompat.from(context).notify(AppContext.NOTIFICATION_MEDIA_CONTROL, it) + } + + if (playing) tick() + } + } + } + + fun tick() { + notification?.let { + context.startForeground(AppContext.NOTIFICATION_MEDIA_CONTROL, it) + } + } + + private fun action(icon: Int, title: String, id: Int): NotificationCompat.Action { + val intent = Intent(context, MediaControlActionReceiver::class.java).apply { action = id.toString() } + val pendingIntent = PendingIntent.getBroadcast(context, id, intent, 0) + + return NotificationCompat.Action.Builder(icon, title, pendingIntent).build() + } +} + +class MediaControlActionReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + MediaControlsManager.NOTIFICATION_ACTION_PREVIOUS.toString() -> CommandBus.send( + Command.PreviousTrack + ) + MediaControlsManager.NOTIFICATION_ACTION_TOGGLE.toString() -> CommandBus.send( + Command.ToggleState + ) + MediaControlsManager.NOTIFICATION_ACTION_NEXT.toString() -> CommandBus.send( + Command.NextTrack + ) + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt new file mode 100644 index 0000000..31ff816 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt @@ -0,0 +1,442 @@ +package com.github.apognu.otter.playback + +import android.annotation.SuppressLint +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.media.AudioAttributes +import android.media.AudioFocusRequest +import android.media.AudioManager +import android.os.Build +import android.support.v4.media.session.MediaSessionCompat +import android.view.KeyEvent +import com.github.apognu.otter.R +import com.github.apognu.otter.utils.* +import com.google.android.exoplayer2.* +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector +import com.google.android.exoplayer2.source.TrackGroupArray +import com.google.android.exoplayer2.trackselection.TrackSelectionArray +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class PlayerService : Service() { + private lateinit var queue: QueueManager + private val jobs = mutableListOf() + + private lateinit var audioManager: AudioManager + private var audioFocusRequest: AudioFocusRequest? = null + private val audioFocusChangeListener = AudioFocusChange() + private var stateWhenLostFocus = false + + private lateinit var mediaControlsManager: MediaControlsManager + private lateinit var mediaSession: MediaSessionCompat + private lateinit var player: SimpleExoPlayer + + private lateinit var playerEventListener: PlayerEventListener + private val headphonesUnpluggedReceiver = HeadphonesUnpluggedReceiver() + + private var progressCache = Triple(0, 0, 0) + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + watchEventBus() + + return START_STICKY + } + + override fun onCreate() { + super.onCreate() + + queue = QueueManager(this) + + audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run { + setAudioAttributes(AudioAttributes.Builder().run { + setUsage(AudioAttributes.USAGE_MEDIA) + setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + + setAcceptsDelayedFocusGain(true) + setOnAudioFocusChangeListener(audioFocusChangeListener) + + build() + }) + + build() + } + } + + mediaSession = MediaSessionCompat(this, applicationContext.packageName).apply { + isActive = true + } + + mediaControlsManager = MediaControlsManager(this, mediaSession) + + player = ExoPlayerFactory.newSimpleInstance(this).apply { + playWhenReady = false + + playerEventListener = PlayerEventListener().also { + addListener(it) + } + + MediaSessionConnector(mediaSession).also { + it.setPlayer(this) + it.setMediaButtonEventHandler { player, _, mediaButtonEvent -> + mediaButtonEvent?.extras?.getParcelable(Intent.EXTRA_KEY_EVENT)?.let { key -> + if (key.action == KeyEvent.ACTION_UP) { + when (key.keyCode) { + KeyEvent.KEYCODE_MEDIA_PLAY -> state(true) + KeyEvent.KEYCODE_MEDIA_PAUSE -> state(false) + KeyEvent.KEYCODE_MEDIA_NEXT -> player?.next() + KeyEvent.KEYCODE_MEDIA_PREVIOUS -> previousTrack() + } + } + } + + true + } + } + } + + if (queue.current > -1) { + player.prepare(queue.datasources, true, true) + player.seekTo(queue.current, 0) + } + + registerReceiver(headphonesUnpluggedReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) + } + + private fun watchEventBus() { + jobs.add(GlobalScope.launch(Main) { + for (message in CommandBus.asChannel()) { + when (message) { + is Command.RefreshService -> { + EventBus.send(Event.QueueChanged) + + if (queue.metadata.isNotEmpty()) { + EventBus.send( + Event.TrackPlayed( + queue.current(), + player.playWhenReady + ) + ) + EventBus.send( + Event.StateChanged( + player.playWhenReady + ) + ) + } + } + + is Command.ReplaceQueue -> { + queue.replace(message.queue) + player.prepare(queue.datasources, true, true) + + state(true) + + EventBus.send( + Event.TrackPlayed( + queue.current(), + true + ) + ) + } + + is Command.AddToQueue -> queue.append(message.tracks) + is Command.PlayNext -> queue.insertNext(message.track) + is Command.RemoveFromQueue -> queue.remove(message.track) + is Command.MoveFromQueue -> queue.move(message.oldPosition, message.newPosition) + + is Command.PlayTrack -> { + queue.current = message.index + player.seekTo(message.index, C.TIME_UNSET) + + state(true) + + EventBus.send( + Event.TrackPlayed( + queue.current(), + true + ) + ) + } + + is Command.ToggleState -> toggle() + is Command.SetState -> state(message.state) + + is Command.NextTrack -> player.next() + is Command.PreviousTrack -> previousTrack() + is Command.Seek -> progress(message.progress) + } + + if (player.playWhenReady) { + mediaControlsManager.tick() + } + } + }) + + jobs.add(GlobalScope.launch(Main) { + for (request in RequestBus.asChannel()) { + when (request) { + is Request.GetCurrentTrack -> request.channel?.offer( + Response.CurrentTrack( + queue.current() + ) + ) + is Request.GetState -> request.channel?.offer( + Response.State( + player.playWhenReady + ) + ) + is Request.GetQueue -> request.channel?.offer( + Response.Queue( + queue.get() + ) + ) + } + } + }) + + jobs.add(GlobalScope.launch(Main) { + while (true) { + delay(1000) + + val (current, duration, percent) = progress() + + if (player.playWhenReady) { + ProgressBus.send(current, duration, percent) + } + } + }) + } + + override fun onBind(intent: Intent?) = null + + @SuppressLint("NewApi") + override fun onDestroy() { + jobs.forEach { it.cancel() } + + try { + unregisterReceiver(headphonesUnpluggedReceiver) + } catch (_: Exception) { + } + + Build.VERSION_CODES.O.onApi( + { + audioFocusRequest?.let { + audioManager.abandonAudioFocusRequest(it) + } + }, + { + @Suppress("DEPRECATION") + audioManager.abandonAudioFocus(audioFocusChangeListener) + }) + + mediaSession.isActive = false + mediaSession.release() + + player.removeListener(playerEventListener) + state(false) + player.release() + + queue.cache.release() + + stopForeground(true) + stopSelf() + + super.onDestroy() + } + + @SuppressLint("NewApi") + private fun state(state: Boolean) { + if (state && player.playbackState == Player.STATE_IDLE) { + player.prepare(queue.datasources) + } + + var allowed = !state + + if (!allowed) { + Build.VERSION_CODES.O.onApi( + { + audioFocusRequest?.let { + allowed = when (audioManager.requestAudioFocus(it)) { + AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> true + else -> false + } + } + }, + { + + @Suppress("DEPRECATION") + audioManager.requestAudioFocus(audioFocusChangeListener, AudioAttributes.CONTENT_TYPE_MUSIC, AudioManager.AUDIOFOCUS_GAIN).let { + allowed = when (it) { + AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> true + else -> false + } + } + } + ) + } + + if (allowed) { + player.playWhenReady = state + + EventBus.send(Event.StateChanged(state)) + } + } + + private fun toggle() { + state(!player.playWhenReady) + } + + private fun previousTrack() { + if (player.currentPosition > 5000) { + return player.seekTo(0) + } + + player.previous() + } + + private fun progress(): Triple { + if (!player.playWhenReady) return progressCache + + return queue.current()?.bestUpload()?.let { upload -> + val current = player.currentPosition + val duration = upload.duration.toFloat() + val percent = ((current / (duration * 1000)) * 100).toInt() + + progressCache = Triple(current.toInt(), duration.toInt(), percent) + progressCache + } ?: Triple(0, 0, 0) + } + + private fun progress(value: Int) { + val duration = ((queue.current()?.bestUpload()?.duration ?: 0) * (value.toFloat() / 100)) * 1000 + + progressCache = Triple(duration.toInt(), queue.current()?.bestUpload()?.duration ?: 0, value) + + player.seekTo(duration.toLong()) + } + + inner class PlayerEventListener : Player.EventListener { + override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { + super.onPlayerStateChanged(playWhenReady, playbackState) + + EventBus.send( + Event.StateChanged( + playWhenReady + ) + ) + + if (queue.current == -1) { + EventBus.send( + Event.TrackPlayed( + queue.current(), + playWhenReady + ) + ) + } + + when (playWhenReady) { + true -> { + when (playbackState) { + Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), true) + Player.STATE_BUFFERING -> EventBus.send( + Event.Buffering( + true + ) + ) + Player.STATE_IDLE -> state(false) + Player.STATE_ENDED -> EventBus.send(Event.PlaybackStopped) + } + + if (playbackState != Player.STATE_BUFFERING) EventBus.send( + Event.Buffering( + false + ) + ) + } + + false -> { + EventBus.send( + Event.StateChanged( + false + ) + ) + EventBus.send( + Event.Buffering( + false + ) + ) + + if (playbackState == Player.STATE_READY) { + mediaControlsManager.updateNotification(queue.current(), false) + stopForeground(false) + } + } + } + } + + override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) { + super.onTracksChanged(trackGroups, trackSelections) + + queue.current = player.currentWindowIndex + mediaControlsManager.updateNotification(queue.current(), player.playWhenReady) + + Cache.set( + this@PlayerService, + "current", + queue.current.toString().toByteArray() + ) + + EventBus.send( + Event.TrackPlayed( + queue.current(), + true + ) + ) + } + + override fun onPlayerError(error: ExoPlaybackException?) { + EventBus.send( + Event.PlaybackError( + getString(R.string.error_playback) + ) + ) + + player.next() + } + } + + inner class AudioFocusChange : AudioManager.OnAudioFocusChangeListener { + override fun onAudioFocusChange(focus: Int) { + when (focus) { + AudioManager.AUDIOFOCUS_GAIN -> { + player.volume = 1f + + state(stateWhenLostFocus) + stateWhenLostFocus = false + } + + AudioManager.AUDIOFOCUS_LOSS -> { + stateWhenLostFocus = false + state(false) + } + + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { + stateWhenLostFocus = player.playWhenReady + state(false) + } + + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { + stateWhenLostFocus = player.playWhenReady + player.volume = 0.3f + } + } + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt b/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt new file mode 100644 index 0000000..0d9d59e --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt @@ -0,0 +1,158 @@ +package com.github.apognu.otter.playback + +import android.content.Context +import android.net.Uri +import com.github.apognu.otter.R +import com.github.apognu.otter.repositories.FavoritesRepository +import com.github.apognu.otter.utils.* +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.android.exoplayer2.source.ConcatenatingMediaSource +import com.google.android.exoplayer2.source.ProgressiveMediaSource +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory +import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory +import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor +import com.google.android.exoplayer2.upstream.cache.SimpleCache +import com.google.android.exoplayer2.util.Util +import com.google.gson.Gson +import com.preference.PowerPreference + +class QueueManager(val context: Context) { + var cache: SimpleCache + var metadata: MutableList = mutableListOf() + val datasources = ConcatenatingMediaSource() + var current = -1 + + init { + PowerPreference.getDefaultFile().getInt("media_cache_size", 1).toLong().also { + cache = SimpleCache( + context.cacheDir.resolve("media"), + LeastRecentlyUsedCacheEvictor(it * 1024 * 1024 * 1024) + ) + } + + Cache.get(context, "queue")?.let { json -> + gsonDeserializerOf(QueueCache::class.java).deserialize(json)?.let { cache -> + metadata = cache.data.toMutableList() + + val factory = factory() + + datasources.addMediaSources(metadata.map { track -> + val url = normalizeUrl(track.bestUpload()?.listen_url ?: "") + + ProgressiveMediaSource.Factory(factory).setTag(track.title).createMediaSource(Uri.parse(url)) + }) + } + } + + Cache.get(context, "current")?.let { string -> + current = string.readLine().toInt() + } + } + + private fun persist() { + Cache.set( + context, + "queue", + Gson().toJson(QueueCache(metadata)).toByteArray() + ) + } + + private fun factory(): CacheDataSourceFactory { + val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") + + val http = DefaultHttpDataSourceFactory(Util.getUserAgent(context, context.getString(R.string.app_name))).apply { + defaultRequestProperties.apply { + set("Authorization", "Bearer $token") + } + } + + return CacheDataSourceFactory(cache, http) + } + + fun replace(tracks: List) { + val factory = factory() + + val sources = tracks.map { track -> + val url = normalizeUrl(track.bestUpload()?.listen_url ?: "") + + ProgressiveMediaSource.Factory(factory).setTag(track.title).createMediaSource(Uri.parse(url)) + } + + metadata = tracks.toMutableList() + datasources.clear() + datasources.addMediaSources(sources) + + persist() + + EventBus.send(Event.QueueChanged) + } + + fun append(tracks: List) { + val factory = factory() + val tracks = tracks.filter { metadata.indexOf(it) == -1 } + + val sources = tracks.map { track -> + val url = normalizeUrl(track.bestUpload()?.listen_url ?: "") + + ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)) + } + + metadata.addAll(tracks) + datasources.addMediaSources(sources) + + persist() + + EventBus.send(Event.QueueChanged) + } + + fun insertNext(track: Track) { + val factory = factory() + val url = normalizeUrl(track.bestUpload()?.listen_url ?: "") + + if (metadata.indexOf(track) == -1) { + ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)).let { + datasources.addMediaSource(current + 1, it) + metadata.add(current + 1, track) + } + } else { + move(metadata.indexOf(track), current + 1) + } + + persist() + + EventBus.send(Event.QueueChanged) + } + + fun remove(track: Track) { + metadata.indexOf(track).let { + datasources.removeMediaSource(it) + metadata.removeAt(it) + } + + persist() + + EventBus.send(Event.QueueChanged) + } + + fun move(oldPosition: Int, newPosition: Int) { + datasources.moveMediaSource(oldPosition, newPosition) + metadata.add(newPosition, metadata.removeAt(oldPosition)) + + persist() + } + + fun get() = metadata.mapIndexed { index, track -> + track.current = index == current + track + } + + fun get(index: Int): Track = metadata[index] + + fun current(): Track? { + if (current == -1) { + return metadata.getOrNull(0) + } + + return metadata.getOrNull(current) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt new file mode 100644 index 0000000..fbbff60 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt @@ -0,0 +1,32 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.Album +import com.github.apognu.otter.utils.AlbumsCache +import com.github.apognu.otter.utils.AlbumsResponse +import com.github.apognu.otter.utils.FunkwhaleResponse +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.gson.reflect.TypeToken +import java.io.BufferedReader + +class AlbumsRepository(override val context: Context?, artistId: Int? = null) : Repository() { + override val cacheId: String by lazy { + if (artistId == null) "albums" + else "albums-artist-$artistId" + } + + override val upstream: Upstream by lazy { + val url = + if (artistId == null) "/api/v1/albums?playable=true" + else "/api/v1/albums?playable=true&artist=$artistId" + + HttpUpstream>( + HttpUpstream.Behavior.Progressive, + url, + object : TypeToken() {}.type + ) + } + + override fun cache(data: List) = AlbumsCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(AlbumsCache::class.java).deserialize(reader) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt new file mode 100644 index 0000000..3676a67 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt @@ -0,0 +1,18 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.Artist +import com.github.apognu.otter.utils.ArtistsCache +import com.github.apognu.otter.utils.ArtistsResponse +import com.github.apognu.otter.utils.FunkwhaleResponse +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.gson.reflect.TypeToken +import java.io.BufferedReader + +class ArtistsRepository(override val context: Context?) : Repository() { + override val cacheId = "artists" + override val upstream = HttpUpstream>(HttpUpstream.Behavior.Progressive, "/api/v1/artists?playable=true", object : TypeToken() {}.type) + + override fun cache(data: List) = ArtistsCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(ArtistsCache::class.java).deserialize(reader) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt new file mode 100644 index 0000000..8cf7d29 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt @@ -0,0 +1,55 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.* +import com.github.kittinunf.fuel.Fuel +import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.preference.PowerPreference +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.runBlocking +import java.io.BufferedReader + +class FavoritesRepository(override val context: Context?) : Repository() { + override val cacheId = "favorites" + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/favorites/tracks?playable=true", object : TypeToken() {}.type) + + override fun cache(data: List) = FavoritesCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(FavoritesCache::class.java).deserialize(reader) + + override fun onDataFetched(data: List) = data.map { + it.apply { + it.track.favorite = true + } + } + + fun addFavorite(id: Int) { + val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") + val body = mapOf("track" to id) + + runBlocking(IO) { + Fuel + .post(normalizeUrl("/api/v1/favorites/tracks")) + .header("Authorization", "Bearer $token") + .header("Content-Type", "application/json") + .body(Gson().toJson(body)) + .awaitByteArrayResponseResult() + } + } + + fun deleteFavorite(id: Int) { + val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") + val body = mapOf("track" to id) + + runBlocking(IO) { + Fuel + .post(normalizeUrl("/api/v1/favorites/tracks/remove/")) + .header("Authorization", "Bearer $token") + .header("Content-Type", "application/json") + .body(Gson().toJson(body)) + .awaitByteArrayResponseResult() + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt new file mode 100644 index 0000000..66879f3 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt @@ -0,0 +1,102 @@ +package com.github.apognu.otter.repositories + +import android.net.Uri +import com.github.apognu.otter.utils.* +import com.github.kittinunf.fuel.Fuel +import com.github.kittinunf.fuel.core.FuelError +import com.github.kittinunf.fuel.core.ResponseDeserializable +import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult +import com.github.kittinunf.fuel.coroutines.awaitObjectResult +import com.github.kittinunf.result.Result +import com.google.gson.Gson +import com.preference.PowerPreference +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import java.io.Reader +import java.lang.reflect.Type +import kotlin.math.ceil + +class HttpUpstream>(private val behavior: Behavior, private val url: String, private val type: Type) : Upstream { + enum class Behavior { + AtOnce, Progressive + } + + private var _channel: Channel>? = null + private val channel: Channel> + get() { + if (_channel?.isClosedForSend ?: true) { + _channel = Channel() + } + + return _channel!! + } + + override fun fetch(data: List): Channel>? { + val page = ceil(data.size / AppContext.PAGE_SIZE.toDouble()).toInt() + 1 + + GlobalScope.launch(Dispatchers.IO) { + val offsetUrl = + Uri.parse(url) + .buildUpon() + .appendQueryParameter("page_size", AppContext.PAGE_SIZE.toString()) + .appendQueryParameter("page", page.toString()) + .build() + .toString() + + get(offsetUrl).fold( + { response -> + val data = data.plus(response.getData()) + + if (behavior == Behavior.Progressive || response.next == null) { + channel.offer(Repository.Response(Repository.Origin.Network, data)) + } else { + fetch(data) + } + }, + { error -> + when (error.exception) { + is RefreshError -> EventBus.send(Event.LogOut) + } + } + ) + } + + return channel + } + + class GenericDeserializer>(val type: Type) : ResponseDeserializable { + override fun deserialize(reader: Reader): T? { + return Gson().fromJson(reader, type) + } + } + + suspend fun get(url: String): Result { + val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") + + val (_, response, result) = Fuel + .get(normalizeUrl(url)) + .header("Authorization", "Bearer $token") + .awaitObjectResponseResult(GenericDeserializer(type)) + + if (response.statusCode == 401) { + return retryGet(url) + } + + return result + } + + private suspend fun retryGet(url: String): Result { + return if (HTTP.refresh()) { + val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") + + Fuel + .get(normalizeUrl(url)) + .header("Authorization", "Bearer $token") + .awaitObjectResult(GenericDeserializer(type)) + } else { + Result.Failure(FuelError.wrap(RefreshError)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt new file mode 100644 index 0000000..6456b57 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt @@ -0,0 +1,18 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.FunkwhaleResponse +import com.github.apognu.otter.utils.PlaylistTrack +import com.github.apognu.otter.utils.PlaylistTracksCache +import com.github.apognu.otter.utils.PlaylistTracksResponse +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.gson.reflect.TypeToken +import java.io.BufferedReader + +class PlaylistTracksRepository(override val context: Context?, playlistId: Int) : Repository() { + override val cacheId = "tracks-playlist-$playlistId" + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/playlists/$playlistId/tracks?playable=true", object : TypeToken() {}.type) + + override fun cache(data: List) = PlaylistTracksCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistTracksCache::class.java).deserialize(reader) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt new file mode 100644 index 0000000..30c597a --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt @@ -0,0 +1,18 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.FunkwhaleResponse +import com.github.apognu.otter.utils.Playlist +import com.github.apognu.otter.utils.PlaylistsCache +import com.github.apognu.otter.utils.PlaylistsResponse +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.gson.reflect.TypeToken +import java.io.BufferedReader + +class PlaylistsRepository(override val context: Context?) : Repository() { + override val cacheId = "tracks-playlists" + override val upstream = HttpUpstream>(HttpUpstream.Behavior.Progressive, "/api/v1/playlists?playable=true", object : TypeToken() {}.type) + + override fun cache(data: List) = PlaylistsCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistsCache::class.java).deserialize(reader) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt b/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt new file mode 100644 index 0000000..8b225e0 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt @@ -0,0 +1,75 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.Cache +import com.github.apognu.otter.utils.CacheItem +import com.github.apognu.otter.utils.untilNetwork +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.channels.Channel +import java.io.BufferedReader + +interface Upstream { + fun fetch(data: List = listOf()): Channel>? +} + +abstract class Repository> { + enum class Origin(val origin: Int) { + Cache(0b01), + Network(0b10) + } + + data class Response(val origin: Origin, val data: List) + + abstract val context: Context? + abstract val cacheId: String? + abstract val upstream: Upstream + + private var _channel: Channel>? = null + private val channel: Channel> + get() { + if (_channel?.isClosedForSend ?: true) { + _channel = Channel(10) + } + + return _channel!! + } + + protected open fun cache(data: List): C? = null + protected open fun uncache(reader: BufferedReader): C? = null + + fun fetch(upstreams: Int = Origin.Cache.origin and Origin.Network.origin, from: List = listOf()): Channel> { + if (Origin.Cache.origin and upstreams == upstreams) fromCache() + if (Origin.Network.origin and upstreams == upstreams) fromNetwork(from) + + return channel + } + + private fun fromCache() { + cacheId?.let { cacheId -> + Cache.get(context, cacheId)?.let { reader -> + uncache(reader)?.let { cache -> + channel.offer(Response(Origin.Cache, cache.data)) + } + } + } + } + + private fun fromNetwork(from: List) { + upstream.fetch(data = from)?.untilNetwork(IO) { + val data = onDataFetched(it) + + cacheId?.let { cacheId -> + Cache.set( + context, + cacheId, + Gson().toJson(cache(data)).toByteArray() + ) + } + + channel.offer(Response(Origin.Network, data)) + } + } + + protected open fun onDataFetched(data: List) = data +} diff --git a/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt new file mode 100644 index 0000000..0713d79 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt @@ -0,0 +1,35 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.FunkwhaleResponse +import com.github.apognu.otter.utils.Track +import com.github.apognu.otter.utils.TracksCache +import com.github.apognu.otter.utils.TracksResponse +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.gson.reflect.TypeToken +import kotlinx.coroutines.runBlocking +import java.io.BufferedReader + +class SearchRepository(override val context: Context?, query: String) : Repository() { + override val cacheId: String? = null + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks?playable=true&q=$query", object : TypeToken() {}.type) + + override fun cache(data: List) = TracksCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) + + var query: String? = null + + override fun onDataFetched(data: List): List = runBlocking { + val favorites = FavoritesRepository(context).fetch(Origin.Network.origin).receive().data + + data.map { track -> + val favorite = favorites.find { it.track.id == track.id } + + if (favorite != null) { + track.favorite = true + } + + track + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt new file mode 100644 index 0000000..a6f16e8 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt @@ -0,0 +1,33 @@ +package com.github.apognu.otter.repositories + +import android.content.Context +import com.github.apognu.otter.utils.FunkwhaleResponse +import com.github.apognu.otter.utils.Track +import com.github.apognu.otter.utils.TracksCache +import com.github.apognu.otter.utils.TracksResponse +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.gson.reflect.TypeToken +import kotlinx.coroutines.runBlocking +import java.io.BufferedReader + +class TracksRepository(override val context: Context?, albumId: Int) : Repository() { + override val cacheId = "tracks-album-$albumId" + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks?playable=true&album=$albumId", object : TypeToken() {}.type) + + override fun cache(data: List) = TracksCache(data) + override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) + + override fun onDataFetched(data: List): List = runBlocking { + val favorites = FavoritesRepository(context).fetch(Origin.Network.origin).receive().data + + data.map { track -> + val favorite = favorites.find { it.track.id == track.id } + + if (favorite != null) { + track.favorite = true + } + + track + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/utils/AppContext.kt b/app/src/main/java/com/github/apognu/otter/utils/AppContext.kt new file mode 100644 index 0000000..2c41537 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/utils/AppContext.kt @@ -0,0 +1,75 @@ +package com.github.apognu.otter.utils + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.pm.ActivityInfo +import android.os.Build +import com.github.apognu.otter.R +import com.github.kittinunf.fuel.core.FuelManager +import com.github.kittinunf.fuel.core.Method + +object AppContext { + const val PREFS_CREDENTIALS = "credentials" + + const val NOTIFICATION_MEDIA_CONTROL = 1 + const val NOTIFICATION_CHANNEL_MEDIA_CONTROL = "mediacontrols" + + const val PAGE_SIZE = 7 + const val TRANSITION_DURATION = 300L + + fun init(context: Activity) { + setupNotificationChannels(context) + + context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + + // CastContext.getSharedInstance(context) + + FuelManager.instance.addResponseInterceptor { next -> + { request, response -> + if (request.method == Method.GET && response.statusCode == 200) { + var cacheId = request.url.path.toString() + + request.url.query?.let { + cacheId = "$cacheId?$it" + } + + Cache.set(context, cacheId, response.body().toByteArray()) + } + + next(request, response) + } + } + } + + @SuppressLint("NewApi") + private fun setupNotificationChannels(context: Context) { + Build.VERSION_CODES.O.onApi { + (context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).let { manager -> + NotificationChannel( + NOTIFICATION_CHANNEL_MEDIA_CONTROL, + context.getString(R.string.playback_media_controls), + NotificationManager.IMPORTANCE_LOW + ).run { + description = context.getString(R.string.playback_media_controls_description) + + enableLights(false) + enableVibration(false) + setSound(null, null) + + manager.createNotificationChannel(this) + } + } + } + } +} + +class HeadphonesUnpluggedReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + CommandBus.send(Command.SetState(false)) + } +} diff --git a/app/src/main/java/com/github/apognu/otter/utils/Data.kt b/app/src/main/java/com/github/apognu/otter/utils/Data.kt new file mode 100644 index 0000000..0082d82 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/utils/Data.kt @@ -0,0 +1,90 @@ +package com.github.apognu.otter.utils + +import android.content.Context +import com.github.apognu.otter.activities.FwCredentials +import com.github.kittinunf.fuel.Fuel +import com.github.kittinunf.fuel.core.FuelError +import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult +import com.github.kittinunf.fuel.coroutines.awaitObjectResult +import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.github.kittinunf.result.Result +import com.preference.PowerPreference +import java.io.BufferedReader +import java.io.File +import java.nio.charset.Charset +import java.security.MessageDigest + +object RefreshError : Throwable() + +object HTTP { + suspend fun refresh(): Boolean { + val body = mapOf( + "username" to PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("username"), + "password" to PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("password") + ).toList() + + val result = Fuel.post(normalizeUrl("/api/v1/token"), body).awaitObjectResult(gsonDeserializerOf(FwCredentials::class.java)) + + return result.fold( + { data -> + PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).setString("access_token", data.token) + + true + }, + { false } + ) + } + + suspend inline fun get(url: String): Result { + val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") + + val (_, response, result) = Fuel + .get(normalizeUrl(url)) + .header("Authorization", "Bearer $token") + .awaitObjectResponseResult(gsonDeserializerOf(T::class.java)) + + if (response.statusCode == 401) { + return retryGet(url) + } + + return result + } + + suspend inline fun retryGet(url: String): Result { + return if (refresh()) { + val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") + + Fuel + .get(normalizeUrl(url)) + .header("Authorization", "Bearer $token") + .awaitObjectResult(gsonDeserializerOf(T::class.java)) + } else { + Result.Failure(FuelError.wrap(RefreshError)) + } + } +} + +object Cache { + private fun key(key: String): String { + val md = MessageDigest.getInstance("SHA-1") + val digest = md.digest(key.toByteArray(Charset.defaultCharset())) + + return digest.fold("", { acc, it -> acc + "%02x".format(it) }) + } + + fun set(context: Context?, key: String, value: ByteArray) = context?.let { + with(File(it.cacheDir, key(key))) { + writeBytes(value) + } + } + + fun get(context: Context?, key: String): BufferedReader? = context?.let { + try { + with(File(it.cacheDir, key(key))) { + bufferedReader() + } + } catch (e: Exception) { + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt b/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt new file mode 100644 index 0000000..19201cc --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt @@ -0,0 +1,117 @@ +package com.github.apognu.otter.utils + +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.launch + +sealed class Command { + object RefreshService : Command() + + object ToggleState : Command() + class SetState(val state: Boolean) : Command() + + object NextTrack : Command() + object PreviousTrack : Command() + class Seek(val progress: Int) : Command() + + class AddToQueue(val tracks: List) : Command() + class PlayNext(val track: Track) : Command() + class ReplaceQueue(val queue: List) : Command() + class RemoveFromQueue(val track: Track) : Command() + class MoveFromQueue(val oldPosition: Int, val newPosition: Int) : Command() + + class PlayTrack(val index: Int) : Command() +} + +sealed class Event { + object LogOut : Event() + + class PlaybackError(val message: String) : Event() + object PlaybackStopped : Event() + class Buffering(val value: Boolean) : Event() + class TrackPlayed(val track: Track?, val play: Boolean) : Event() + class StateChanged(val playing: Boolean) : Event() + object QueueChanged : Event() +} + +sealed class Request(var channel: Channel? = null) { + object GetState : Request() + object GetQueue : Request() + object GetCurrentTrack : Request() +} + +sealed class Response { + class State(val playing: Boolean) : Response() + class Queue(val queue: List) : Response() + class CurrentTrack(val track: Track?) : Response() +} + +object EventBus { + private var bus: BroadcastChannel = BroadcastChannel(10) + + fun send(event: Event) { + GlobalScope.launch { + bus.offer(event) + } + } + + fun get() = bus + + inline fun asChannel(): ReceiveChannel { + return get().openSubscription().filter { it is T }.map { it as T } + } +} + +object CommandBus { + private var bus: Channel = Channel(10) + + fun send(command: Command) { + GlobalScope.launch { + bus.offer(command) + } + } + + fun asChannel() = bus +} + +object RequestBus { + private var bus: BroadcastChannel = BroadcastChannel(10) + + fun send(request: Request): Channel { + return Channel().also { + GlobalScope.launch(Main) { + request.channel = it + + bus.offer(request) + } + } + } + + fun get() = bus + + inline fun asChannel(): ReceiveChannel { + return get().openSubscription().filter { it is T }.map { it as T } + } +} + +object ProgressBus { + private val bus: BroadcastChannel> = ConflatedBroadcastChannel() + + fun send(current: Int, duration: Int, percent: Int) { + GlobalScope.launch { + bus.send(Triple(current, duration, percent)) + } + } + + fun asChannel(): ReceiveChannel> { + return bus.openSubscription() + } +} + +suspend inline fun Channel.wait(): T? { + return when (val response = this.receive()) { + is T -> response + else -> null + } +} diff --git a/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt b/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt new file mode 100644 index 0000000..283c664 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt @@ -0,0 +1,88 @@ +package com.github.apognu.otter.utils + +import android.os.Build +import android.view.ViewGroup +import android.view.animation.Interpolator +import androidx.core.view.doOnPreDraw +import androidx.fragment.app.Fragment +import androidx.transition.TransitionSet +import com.github.apognu.otter.fragments.BrowseFragment +import com.github.apognu.otter.repositories.Repository +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext + +inline fun Channel>.await(context: CoroutineContext = Main, crossinline callback: (data: List) -> Unit) { + GlobalScope.launch(context) { + this@await.receive().also { + callback(it.data) + close() + } + } +} + +inline fun Channel>.untilNetwork(context: CoroutineContext = Main, crossinline callback: (data: List) -> Unit) { + GlobalScope.launch(context) { + for (data in this@untilNetwork) { + callback(data.data) + + if (data.origin == Repository.Origin.Network) { + close() + } + } + } +} + +fun TransitionSet.setCommonInterpolator(interpolator: Interpolator): TransitionSet { + (0 until transitionCount) + .map { index -> getTransitionAt(index) } + .forEach { transition -> transition.interpolator = interpolator } + + return this +} + +fun Fragment.onViewPager(block: Fragment.() -> Unit) { + for (f in activity?.supportFragmentManager?.fragments ?: listOf()) { + if (f is BrowseFragment) { + f.block() + } + } +} + +fun Fragment.startTransitions() { + (view?.parent as? ViewGroup)?.doOnPreDraw { + startPostponedEnterTransition() + } +} + +fun Int.onApi(block: () -> T) { + if (Build.VERSION.SDK_INT >= this) { + block() + } +} + +fun Int.onApi(block: () -> T, elseBlock: (() -> U)) { + if (Build.VERSION.SDK_INT >= this) { + block() + } else { + elseBlock() + } +} + +fun Int.onApiForResult(block: () -> T, elseBlock: (() -> T)): T { + if (Build.VERSION.SDK_INT >= this) { + return block() + } else { + return elseBlock() + } +} + +fun T.applyOnApi(api: Int, block: T.() -> T): T { + if (Build.VERSION.SDK_INT >= api) { + return block() + } else { + return this + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/utils/Models.kt b/app/src/main/java/com/github/apognu/otter/utils/Models.kt new file mode 100644 index 0000000..d34ff52 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/utils/Models.kt @@ -0,0 +1,113 @@ +package com.github.apognu.otter.utils + +import com.preference.PowerPreference + +sealed class CacheItem(val data: List) +class ArtistsCache(data: List) : CacheItem(data) +class AlbumsCache(data: List) : CacheItem(data) +class TracksCache(data: List) : CacheItem(data) +class PlaylistsCache(data: List) : CacheItem(data) +class PlaylistTracksCache(data: List) : CacheItem(data) +class FavoritesCache(data: List) : CacheItem(data) +class QueueCache(data: List) : CacheItem(data) + +abstract class FunkwhaleResponse { + abstract val count: Int + abstract val next: String? + + abstract fun getData(): List +} + +data class ArtistsResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { + override fun getData() = results +} + +data class AlbumsResponse(override val count: Int, override val next: String?, val results: AlbumList) : FunkwhaleResponse() { + override fun getData() = results +} + +data class TracksResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { + override fun getData() = results +} + +data class FavoritesResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { + override fun getData() = results +} + +data class PlaylistsResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { + override fun getData() = results +} + +data class PlaylistTracksResponse(override val count: Int, override val next: String?, val results: List) : FunkwhaleResponse() { + override fun getData() = results +} + +data class Covers(val original: String) + +typealias AlbumList = List + +data class Album( + val id: Int, + val artist: Artist, + val title: String, + val cover: Covers +) { + data class Artist(val name: String) +} + +data class Artist( + val id: Int, + val name: String, + val albums: List? +) { + data class Album( + val title: String, + val cover: Covers + ) +} + +data class Track( + val id: Int, + val title: String, + val artist: Artist, + val album: Album, + val uploads: List +) { + var current: Boolean = false + var favorite: Boolean = false + + data class Upload( + val listen_url: String, + val duration: Int, + val bitrate: Int + ) + + override fun equals(other: Any?): Boolean { + return when (other) { + is Track -> other.id == id + else -> false + } + } + + fun bestUpload(): Upload? { + if (uploads.isEmpty()) return null + + return when (PowerPreference.getDefaultFile().getString("media_cache_quality")) { + "quality" -> uploads.maxBy { it.bitrate } ?: uploads[0] + "size" -> uploads.minBy { it.bitrate } ?: uploads[0] + else -> uploads.maxBy { it.bitrate } ?: uploads[0] + } + } +} + +data class Favorite(val id: Int, val track: Track) + +data class Playlist( + val id: Int, + val name: String, + val album_covers: List, + val tracks_count: Int, + val duration: Int +) + +data class PlaylistTrack(val track: Track) \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/utils/Util.kt b/app/src/main/java/com/github/apognu/otter/utils/Util.kt new file mode 100644 index 0000000..ba40f9e --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/utils/Util.kt @@ -0,0 +1,26 @@ +package com.github.apognu.otter.utils + +import android.content.Context +import android.widget.Toast +import com.google.android.exoplayer2.util.Log +import com.preference.PowerPreference +import java.net.URI + +fun Context?.toast(message: String, length: Int = Toast.LENGTH_SHORT) { + if (this != null) { + Toast.makeText(this, message, length).show() + } +} + +fun Any.log(message: String) { + Log.d("FUNKWHALE", "${this.javaClass.simpleName}: $message") +} + +fun normalizeUrl(url: String): String { + val fallbackHost = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("hostname") + val uri = URI(url).takeIf { it.host != null } ?: URI("$fallbackHost$url") + + return uri.run { + URI("https", host, path, query, null) + }.toString() +} diff --git a/app/src/main/java/com/github/apognu/otter/views/ExplodeReveal.kt b/app/src/main/java/com/github/apognu/otter/views/ExplodeReveal.kt new file mode 100644 index 0000000..8692d89 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/views/ExplodeReveal.kt @@ -0,0 +1,79 @@ +package com.github.apognu.otter.views + +import android.animation.Animator +import android.animation.ObjectAnimator +import android.graphics.Rect +import android.view.View +import android.view.ViewGroup +import androidx.transition.TransitionValues +import androidx.transition.Visibility + +class ExplodeReveal : Visibility() { + val SCREEN_BOUNDS = "screenBounds" + + private val locations = IntArray(2) + + override fun captureStartValues(transitionValues: TransitionValues) { + super.captureStartValues(transitionValues) + + capture(transitionValues) + } + + override fun captureEndValues(transitionValues: TransitionValues) { + super.captureEndValues(transitionValues) + + capture(transitionValues) + } + + override fun onAppear(sceneRoot: ViewGroup, view: View, startValues: TransitionValues?, endValues: TransitionValues?): Animator? { + if (endValues == null) return null + + val bounds = endValues.values[SCREEN_BOUNDS] as Rect + + val endY = view.translationY + val distance = calculateDistance(sceneRoot, bounds) + val startY = endY + distance + + return ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY) + } + + override fun onDisappear(sceneRoot: ViewGroup, view: View, startValues: TransitionValues?, endValues: TransitionValues?): Animator? { + if (startValues == null) return null + + val bounds = startValues.values[SCREEN_BOUNDS] as Rect + + val startY = view.translationY + val distance = calculateDistance(sceneRoot, bounds) + val endY = startY + distance + + return ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY) + } + + private fun capture(transitionValues: TransitionValues) { + transitionValues.view.also { + it.getLocationOnScreen(locations) + + val left = locations[0] + val top = locations[1] + val right = left + it.width + val bottom = top + it.height + + transitionValues.values[SCREEN_BOUNDS] = Rect(left, top, right, bottom) + } + } + + private fun calculateDistance(sceneRoot: View, viewBounds: Rect): Int { + sceneRoot.getLocationOnScreen(locations) + + val sceneRootY = locations[1] + + return when (epicenter) { + is Rect -> return when { + viewBounds.top <= (epicenter as Rect).top -> sceneRootY - (epicenter as Rect).top + else -> sceneRootY + sceneRoot.height - (epicenter as Rect).bottom + } + + else -> -sceneRoot.height + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/views/NowPlayingView.kt b/app/src/main/java/com/github/apognu/otter/views/NowPlayingView.kt new file mode 100644 index 0000000..a64256f --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/views/NowPlayingView.kt @@ -0,0 +1,240 @@ +package com.github.apognu.otter.views + +import android.animation.ValueAnimator +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import android.view.ViewTreeObserver +import android.view.animation.DecelerateInterpolator +import com.github.apognu.otter.R +import com.google.android.material.card.MaterialCardView +import kotlinx.android.synthetic.main.partial_now_playing.view.* +import kotlin.math.abs +import kotlin.math.min + +class NowPlayingView : MaterialCardView { + val activity: Context + var gestureDetector: GestureDetector? = null + var gestureDetectorCallback: OnGestureDetection? = null + + constructor(context: Context) : super(context) { + activity = context + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + activity = context + } + + constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) { + activity = context + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + now_playing_root.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED)) + } + + override fun onVisibilityChanged(changedView: View, visibility: Int) { + super.onVisibilityChanged(changedView, visibility) + + if (visibility == View.VISIBLE && gestureDetector == null) { + viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + gestureDetectorCallback = OnGestureDetection() + gestureDetector = GestureDetector(context, gestureDetectorCallback) + + setOnTouchListener { _, motionEvent -> + val ret = gestureDetector?.onTouchEvent(motionEvent) ?: false + + if (motionEvent.actionMasked == MotionEvent.ACTION_UP) { + if (gestureDetectorCallback?.isScrolling == true) { + gestureDetectorCallback?.onUp(motionEvent) + } + } + + ret + } + + viewTreeObserver.removeOnGlobalLayoutListener(this) + } + }) + } + } + + fun isOpened(): Boolean = gestureDetectorCallback?.isOpened() ?: false + + fun close() { + gestureDetectorCallback?.close() + } + + inner class OnGestureDetection : GestureDetector.SimpleOnGestureListener() { + var maxHeight = 0 + private var minHeight = 0 + private var maxMargin = 0 + + private var initialTouchY = 0f + private var lastTouchY = 0f + + var isScrolling = false + private var flingAnimator: ValueAnimator? = null + + init { + (layoutParams as? MarginLayoutParams)?.let { + maxMargin = it.marginStart + } + + minHeight = TypedValue().let { + activity.theme.resolveAttribute(R.attr.actionBarSize, it, true) + + TypedValue.complexToDimensionPixelSize(it.data, resources.displayMetrics) + } + + maxHeight = now_playing_details.measuredHeight + (2 * maxMargin) + } + + override fun onDown(e: MotionEvent): Boolean { + initialTouchY = e.rawY + lastTouchY = e.rawY + + flingAnimator?.cancel() + + return true + } + + fun onUp(event: MotionEvent): Boolean { + isScrolling = false + + layoutParams.let { + val offsetToMax = maxHeight - height + val offsetToMin = height - minHeight + + flingAnimator = + if (offsetToMin < offsetToMax) ValueAnimator.ofInt(it.height, minHeight) + else ValueAnimator.ofInt(it.height, maxHeight) + + animateFling(500) + + return true + } + } + + override fun onFling(firstMotionEvent: MotionEvent?, secondMotionEvent: MotionEvent?, velocityX: Float, velocityY: Float): Boolean { + isScrolling = false + + layoutParams.let { + val diff = + if (velocityY < 0) maxHeight - it.height + else it.height - minHeight + + flingAnimator = + if (velocityY < 0) ValueAnimator.ofInt(it.height, maxHeight) + else ValueAnimator.ofInt(it.height, minHeight) + + animateFling(min(abs((diff.toFloat() / velocityY * 1000).toLong()), 600)) + } + + return true + } + + override fun onScroll(firstMotionEvent: MotionEvent, secondMotionEvent: MotionEvent, distanceX: Float, distanceY: Float): Boolean { + isScrolling = true + + layoutParams.let { + val newHeight = it.height + lastTouchY - secondMotionEvent.rawY + val progress = (newHeight - minHeight) / (maxHeight - minHeight) + val newMargin = maxMargin - (maxMargin * progress) + + (layoutParams as? MarginLayoutParams)?.let { + it.marginStart = newMargin.toInt() + it.marginEnd = newMargin.toInt() + it.bottomMargin = newMargin.toInt() + } + + layoutParams = layoutParams.apply { + when { + newHeight <= minHeight -> { + height = minHeight + return true + } + newHeight >= maxHeight -> { + height = maxHeight + return true + } + else -> height = newHeight.toInt() + } + } + + summary.alpha = 1f - progress + + summary.layoutParams = summary.layoutParams.apply { + height = (minHeight * (1f - progress)).toInt() + } + } + + lastTouchY = secondMotionEvent.rawY + + return true + } + + override fun onSingleTapUp(e: MotionEvent?): Boolean { + layoutParams.let { + if (height != minHeight) return true + + flingAnimator = ValueAnimator.ofInt(it.height, maxHeight) + + animateFling(300) + } + + return true + } + + fun isOpened(): Boolean = layoutParams.height == maxHeight + + fun close(): Boolean { + layoutParams.let { + if (it.height == minHeight) return true + + flingAnimator = ValueAnimator.ofInt(it.height, minHeight) + + animateFling(300) + } + + return true + } + + private fun animateFling(dur: Long) { + flingAnimator?.apply { + duration = dur + interpolator = DecelerateInterpolator() + + addUpdateListener { valueAnimator -> + layoutParams = layoutParams.apply { + val newHeight = valueAnimator.animatedValue as Int + val progress = (newHeight.toFloat() - minHeight) / (maxHeight - minHeight) + val newMargin = maxMargin - (maxMargin * progress) + + (layoutParams as? MarginLayoutParams)?.let { + it.marginStart = newMargin.toInt() + it.marginEnd = newMargin.toInt() + it.bottomMargin = newMargin.toInt() + } + + height = newHeight + + summary.alpha = 1f - progress + + summary.layoutParams = summary.layoutParams.apply { + height = (minHeight * (1f - progress)).toInt() + } + } + } + + start() + } + } + } +} diff --git a/app/src/main/java/com/github/apognu/otter/views/SquareImageView.kt b/app/src/main/java/com/github/apognu/otter/views/SquareImageView.kt new file mode 100644 index 0000000..79a247e --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/views/SquareImageView.kt @@ -0,0 +1,17 @@ +package com.github.apognu.otter.views + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView + +class SquareImageView : AppCompatImageView { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + setMeasuredDimension(measuredWidth, measuredWidth) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ottericon.png b/app/src/main/res/drawable-hdpi/ottericon.png new file mode 100644 index 0000000000000000000000000000000000000000..09f5018b76cafe46867f4e42daf16d8bef48822c GIT binary patch literal 696 zcmV;p0!RIcP)F>M^MIUy1Sm~aOb0klL5Lf&)gDyLz$FTC(_!G$R)Iy3F9l740+0pPG)DHpJV%phLYuzR zJpXG@Ed_Ovvw=Fz;BO?F`#&~_*#!~q+hka!auNpaNI^MZ1bo*FPKf?7!Fb;EzvRvnoM} z0LkqKmzY*g8{>wEGDf8obU#3*UFQs6L5Yo)KH#aK(QEG)EDccox$R+t9J|?vt*=zF zyadsg{KuWE$sZKOzvf%h`;U;chni8t-7fZQ@{z&B#3I*4G>p~aaI3+24kP- zSnKg&^A`U1R*WH0#m+-mmVU1IaZtl&W>ZBU1kbocBrL|zsj-EH=eT^YFyvHa@kdHq23VSIy;YwR^e4avQi((~3O+8z~4`1+~seLDxj^*r0LQb4KgT_##?YS elxdlkrgi}%kcd)>R?w#a0000?b57y8)^+qjAs-Qs6v-VJ|1-1SGu1S3wrs6=B?P1*SmGIH;o!&?^WZgB2xQ zPheiy6x~rfn6Xbm9xnwWX;1+Ugtc+j9TwKkz@Y6Qt?ZIcl>yhn+7>7SyTaNFZOnEs z;FBG1-~&v8HqZfSHUjZ&PIK6kL~6?Qls4;6*1;6MxODYu)HZM>Z4wO~h3 zqVO$o+V+8UC9U^Cz11O{n8{Z@ftlyHQp2`~M=-*=NeZ8t+efb9-vm3OlT9c^X1D;O z+~DG-oBr)wh-p$=#DA?|h~EclEB&BRkaJ8leBXm29x12vR7W1a=5mxcy|VlM00000 LNkvXXu0mjfpIg!{ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ottericon.png b/app/src/main/res/drawable-xhdpi/ottericon.png new file mode 100644 index 0000000000000000000000000000000000000000..70efa597d94167242e3bd871dc51160f63ef4046 GIT binary patch literal 946 zcmV;j15NyiP)t?sD7xVHxy+ACJ1Zx zCVEo#Sgj;;|EKLc@Ld>gbMMZ~-rX&@10V1%%$YN1&YZ^>3I!t=!3f5GJ94>P3Uq;f za1*4#doTrlfjN)`zrlC#5j+8Rz!}g3TFR|-E!YpnB?XJ+88`&$N~w4Z^wGN65-|;q zinaV!ei^NlEfd1c+Wb;}9W9eBGhe|fQ)LsN6Lf$mSOb1kKzVr+OQgFOEo5mCL9T*( zJjPEIR6fs3Fah3DpMxsptEv2Q>9>dffbw}RQ>TSsRD^El23CXn()UJ9!6J-iER~uu zaL-xEm4u65cW$5+JO>dF=8@?fZJXsqzX;->3LG;$g{@#nH86vMrg&8; z^M~m={W`+y`0QaK_?7D7daO}nP_oD`7p;W^*Yl`rESmT(gAE+9Xf4|rDVx14AD6zH z7)^8+nbditVxMhd{cmz}FABlG>LFTufnqUHq&+rROuW2p7DbvumRNYVVKfqL?s}JC zla%{_dY2c9TDsIVi#u_i-PUo@O<6n;4D+%_Rgs5APlQzEDJP|*?=Um$R2drhX+ASX z98oK#(6hnKPn%pGa#OVCTnQ8xRq6$`#kqDQ07Zy_DODm0OIWCb_?uCNx$@PuYqo{X^L z>()`kWdjYi54?60i+fa788U1mNLY7K1vU^@-9_8~$V5UF7?n8rU<4x=!7v+t00NO? U_J{bYPXGV_07*qoM6N<$f^zP!J^%m! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ottericon.png b/app/src/main/res/drawable-xxhdpi/ottericon.png new file mode 100644 index 0000000000000000000000000000000000000000..743d3122485e36f42605f823b7a5088948eb4a90 GIT binary patch literal 1475 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/add.xml b/app/src/main/res/drawable/add.xml new file mode 100644 index 0000000..fb601c0 --- /dev/null +++ b/app/src/main/res/drawable/add.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cover.png b/app/src/main/res/drawable/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..e1b7511b6b4e444488b1d4e4264aae3bac72d470 GIT binary patch literal 12252 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 + + diff --git a/app/src/main/res/drawable/login_input.xml b/app/src/main/res/drawable/login_input.xml new file mode 100644 index 0000000..df9aa73 --- /dev/null +++ b/app/src/main/res/drawable/login_input.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/more.xml b/app/src/main/res/drawable/more.xml new file mode 100644 index 0000000..063c9a7 --- /dev/null +++ b/app/src/main/res/drawable/more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/next.xml b/app/src/main/res/drawable/next.xml new file mode 100644 index 0000000..9ce21d3 --- /dev/null +++ b/app/src/main/res/drawable/next.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ottershape.png b/app/src/main/res/drawable/ottershape.png new file mode 100644 index 0000000000000000000000000000000000000000..8e7aed9f7d8f9d558f3128879386b68a3fdb7c65 GIT binary patch literal 55267 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 + + diff --git a/app/src/main/res/drawable/play.xml b/app/src/main/res/drawable/play.xml new file mode 100644 index 0000000..0345329 --- /dev/null +++ b/app/src/main/res/drawable/play.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/previous.xml b/app/src/main/res/drawable/previous.xml new file mode 100644 index 0000000..414fb2d --- /dev/null +++ b/app/src/main/res/drawable/previous.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/queue.xml b/app/src/main/res/drawable/queue.xml new file mode 100644 index 0000000..06f0e68 --- /dev/null +++ b/app/src/main/res/drawable/queue.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/reorder.xml b/app/src/main/res/drawable/reorder.xml new file mode 100644 index 0000000..35b4da3 --- /dev/null +++ b/app/src/main/res/drawable/reorder.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/search.xml b/app/src/main/res/drawable/search.xml new file mode 100644 index 0000000..3ad6951 --- /dev/null +++ b/app/src/main/res/drawable/search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/settings.xml b/app/src/main/res/drawable/settings.xml new file mode 100644 index 0000000..59c2f3c --- /dev/null +++ b/app/src/main/res/drawable/settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_licences.xml b/app/src/main/res/layout/activity_licences.xml new file mode 100644 index 0000000..52bfd76 --- /dev/null +++ b/app/src/main/res/layout/activity_licences.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..525293b --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..10d4afa --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml new file mode 100644 index 0000000..454acda --- /dev/null +++ b/app/src/main/res/layout/activity_search.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..8cdb7b6 --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_login.xml b/app/src/main/res/layout/dialog_login.xml new file mode 100644 index 0000000..4f9bade --- /dev/null +++ b/app/src/main/res/layout/dialog_login.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_albums.xml b/app/src/main/res/layout/fragment_albums.xml new file mode 100644 index 0000000..0e7a5ff --- /dev/null +++ b/app/src/main/res/layout/fragment_albums.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_albums_grid.xml b/app/src/main/res/layout/fragment_albums_grid.xml new file mode 100644 index 0000000..019b2b4 --- /dev/null +++ b/app/src/main/res/layout/fragment_albums_grid.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_artists.xml b/app/src/main/res/layout/fragment_artists.xml new file mode 100644 index 0000000..8654848 --- /dev/null +++ b/app/src/main/res/layout/fragment_artists.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_browse.xml b/app/src/main/res/layout/fragment_browse.xml new file mode 100644 index 0000000..78b3cd1 --- /dev/null +++ b/app/src/main/res/layout/fragment_browse.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_favorites.xml b/app/src/main/res/layout/fragment_favorites.xml new file mode 100644 index 0000000..0b6e54e --- /dev/null +++ b/app/src/main/res/layout/fragment_favorites.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playlists.xml b/app/src/main/res/layout/fragment_playlists.xml new file mode 100644 index 0000000..b85c10c --- /dev/null +++ b/app/src/main/res/layout/fragment_playlists.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_queue.xml b/app/src/main/res/layout/fragment_queue.xml new file mode 100644 index 0000000..39be80a --- /dev/null +++ b/app/src/main/res/layout/fragment_queue.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_tracks.xml b/app/src/main/res/layout/fragment_tracks.xml new file mode 100644 index 0000000..f2a1191 --- /dev/null +++ b/app/src/main/res/layout/fragment_tracks.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/partial_now_playing.xml b/app/src/main/res/layout/partial_now_playing.xml new file mode 100644 index 0000000..8a34c4d --- /dev/null +++ b/app/src/main/res/layout/partial_now_playing.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/preference_category.xml b/app/src/main/res/layout/preference_category.xml new file mode 100644 index 0000000..62b423a --- /dev/null +++ b/app/src/main/res/layout/preference_category.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_album.xml b/app/src/main/res/layout/row_album.xml new file mode 100644 index 0000000..01b5b96 --- /dev/null +++ b/app/src/main/res/layout/row_album.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_album_grid.xml b/app/src/main/res/layout/row_album_grid.xml new file mode 100644 index 0000000..e7e78fc --- /dev/null +++ b/app/src/main/res/layout/row_album_grid.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_artist.xml b/app/src/main/res/layout/row_artist.xml new file mode 100644 index 0000000..028a9e6 --- /dev/null +++ b/app/src/main/res/layout/row_artist.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_licence.xml b/app/src/main/res/layout/row_licence.xml new file mode 100644 index 0000000..2859c3b --- /dev/null +++ b/app/src/main/res/layout/row_licence.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_playlist.xml b/app/src/main/res/layout/row_playlist.xml new file mode 100644 index 0000000..f59f6f9 --- /dev/null +++ b/app/src/main/res/layout/row_playlist.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_track.xml b/app/src/main/res/layout/row_track.xml new file mode 100644 index 0000000..712e0d3 --- /dev/null +++ b/app/src/main/res/layout/row_track.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/row_queue.xml b/app/src/main/res/menu/row_queue.xml new file mode 100644 index 0000000..2d21bfa --- /dev/null +++ b/app/src/main/res/menu/row_queue.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/row_track.xml b/app/src/main/res/menu/row_track.xml new file mode 100644 index 0000000..b555645 --- /dev/null +++ b/app/src/main/res/menu/row_track.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/toolbar.xml b/app/src/main/res/menu/toolbar.xml new file mode 100644 index 0000000..c9237a9 --- /dev/null +++ b/app/src/main/res/menu/toolbar.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2b7f809 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..2b7f809 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7e83ca2f9a202ca4fa1edfdfefe26cace7f71c4a GIT binary patch literal 2175 zcmV-_2!QvAP)W2`9&VR|;?czBF4S;Y?-o5lp9aKHKX;gW5laLHbg)Xz&KmFzJt z!8POBpJ4EtKXj5*Zxl(z6YZpuG*P%1Oi36fvDZZXNYe06yKr&(lSzG)rX^yq4LF#A z5XWd^<{d1E5jnv=Upytpv@8fcLunUu7_2?FE!4z37=q000uFhaEn0zm>n0zn$$AeF$J6XCEp=@hIK;OEiyWHU+n`O^30+-X z(B0kLOO$xNt+NxF@VXkg3@$#n2mcmafY0M18ObI=kTVjqGBp9RYpOxk(XNw-!OBBL z3q;%~gZXEUgT1~)83I8c#)LxHwaXxXM#N}KRdW;kfo|x0)Zh3B`s9=p((hG4*Dw&H zDMY>}x}DQeKf@)6;=zLWDAdEUbs@V`>Tj=yE5VAhk2K0z}hjzDU4 zCDR%`Ck+l)%;A%q2i4$+mh)C*5ad3%5AQ?;k3K;z#{*f0nt|DBn$!>SYHRuhLE0rt z^SxZa(mWsNb}R#;Zllydhrl7B>HtpQN&x4eub3; zKcn?fp~sz9pljADVD_WuqzH9E+NyxJvE48zf~Ww@ z$2NkrNed<*91#RYE|)_GW+FP$xkri?kYHo^U2-hjPSxzbDJxPRwFF-a!NQQNi33k_ z6DdSCeFkT#^cnOTeSTnBiD7l~oGeQ|4K?+T831)Jg%-n+($v(XCXHg&Y(6a$SqRi4 zH=~E!l526U#*xHXQ^mttm=hCbcpGL8VZJau$Quq7Us4xyL=I_{a}7-}w1=|{)A&I4 zvMD}k#~_-Ud(62TRr0@Qq=0kqVX(*b7*3(c$T_S|`4JdTT2)hz|iOARn>Z|EY#G1Lc~`XXs02%-fz zp!8@vLcG~|F2F8MJOPFG?mqL~LyB`zWu-7j!$pY<2Q6hy3-Rf7Zq%}vE-jM#QMRIQ zO!@YU1|notS=je2KNklx&`LEegvA~$cQ>dsubn;YH3%bXay=Oi`!UOv)z`6&t-%me zhs9rFRRyb%XB`jdp@5!i@aE58qehU{VjXb-Wa8S_eGn50E7KC-C}z7-`WhMovF1Kc zCt3+@&)#UPXS;(QxtVM$gubQiRiNVh^f^zJBJMvpkG6~Th(|oyq##f3N1I_(HLp%j zgf;0&tUmfGKAL?q>&R0PQ@nH3ih~ZJzQ*y@^MEut)c+Jg1vu%jQllV9uZ544B7}dS z5(p9q5(p9q5(s+U33?9h)z~?tdo}i&t_7)XhZ>p%`FQO&^&9G}xBMm1n;`40?n}AJ zL<8xNjYSBHRIHpDa1bW_vSKx1brG)|H`tqcUqE@?=D?0ZGDVt@5oHo0X6`k4`^FN) zXvMG&`iBMhmrV8F4|XBmKoc9c_`p>E12B2p#?tXKoaPcnGgsHK zW_k@9n-}q~CCW^TrHeOMxvvek+T<2(wPAgv)n>P-p0ZBs@j4x^4dj~Pm=lki+#=c9 z5d&c%ObnZq)k{P9?FDl)OUsw>IEn5OM{z=NHga)NVX-i?uy}=G9P)5+88>03&4gD) zmJ`R@TD)e=LKp}OVH%E~12H!d5r!~?Aq=A!{{v^UH4khBcUS-b002ovPDHLkV1gS} B0Vn_f literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..40419d1732507da121ba96fe1b5b9a733c49ac73 GIT binary patch literal 2461 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 G$NvJBjg8p= literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..c0faf57cad585fbff9fdc87f53e208155552ab32 GIT binary patch literal 4229 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 b67K&1+MzUu;|R6~00000NkvXXu0mjfgU17> literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..69f67ed24f7c48ab0dd009740fb93cee8d345806 GIT binary patch literal 1448 zcmV;Z1y}lsP)7#tB?Oa1q$jBm+8r|!a0iQ6 z`Qw8)LC+>?ly)RDB~mK_BdD7&5EfBJ((vltpFPaS>d{!lV+-H^tw^L+j^RTd!W+nOn+>wNJ6J$ZC+|Y} zs=fw3I(-7R;BE*)V4XG{zH2%Ihtc}e!I>R)7R;0?HKvL1kYL3|q|1g|t`;j`v;#hs))4jl|XlVF|olQQ|WotxgAg(^v-*UgdqE zL<>E}F`siQvG~c+RRtsPX>FxX5adoDV@Bc6dm3GC?`Qtd!IPM-1tUPUfP&bC zBuF^{=fDsVs;;_m6O{CHvx4^=#tc!v`+VpA&4o|2phyw zjRK-HN=R;NLTfs2T9E@nJ3yJp+tK;u(p=~tH~v+)a4eCaxs#KVU_|2EhNE$y4Tua$8_5? zpkoD)t&Ilab~#d=DqIZktt3?|;EnPEsO<0Y`&dm`OgCYIpIRFsIydvy@jNTVLx*4l zWIRPOx6hKRY{o-a0sa#sYf~|Ex_8CJtnFk3o3T1mTHWYA??`yorYE-xf@UEpPYIt4 zKh=_3(CPAr^Yskyhj_}2Ezb6+&U_!9hUJ@H4|m@rEuHO(>Fza& zd-Ft8T)3P~&p?PV01pBDQ7z+>wQZ%GSzP?b}@vp*S!aap<5aLUqu{o(n0Q zPDSGA$~|8k4V8wI`-J#=Q@IRfP1xhI7w?aWe)ZmHzC0_i+|ot5!=fTzA}qeGKb?eJ z9*Vs%l=}iKVGPUPx_HN6S+CrFS0H8PfL(0C(f$M*=*WUM$neMj0000~E)0o^f)Z_UFp6A8y`+NC)_bmwx_SM!j(o|4T(DwK9I=uO(|DC$( zCa+^6`V|yZI{dv5M>B=9q~xTSV+I6=dw)<|YfzSb?AlyQ}&Hi8} zzpd0&WxiD*(Uxy~C8w=Fyu0p#=EosLj!SjZLWT%CN^@0d+b;^=F77(eJE5F6^N8Pu zD)c&)p3XHiFkr7d+I{bBPT#}95i*+@@?IeLdue4w)yns2Z+Lep<9x%z(B9q}GMVgO zR#vv*B9f*68}v79vM+KUB7l=%FnFM+r{}04?pgEEH>kmO=Oa0Gtq=A@ZftBMyTO%6 ze&+TnXZ+fqCREz;vpM4Ncr4{-Kon#j+n(ivL8K2mq%*e%d*&xfMnJ$Tuiq6H7hkVLlz;p*cLaaM zIq;nP%)ftvzOrAry-{>o-k3SUNT(#fiaW0;QcNt~)s_G0(;CTRMyK z>(2N#<~8MG_~V6}Xzh7*#BC`E1RA^{k4hz4R*Lp!@K_zUa)}AZ6+p<=Bw{sN4jwA& z^Z8WuoIEdX>g>$h@g-Goa)?MI@}{Py7;etWvsrysTZUA4YR=Z7>P!QJ%&GLtQovJ?rGjwEEWoe>pxq(s)bV2r`zYr_-V~-jh4yKD)+E! zh;9VVtfb7WwkyI2z!+&sN=%$AXR%mi(8^;<^pWo!mY9-cvhhlxVpc?+jv!w4%1o}7 z?1<~=F|cE|q&XU)#lyplr(N(cG&=YG`1oA_3E80KJ)aq~4L5Knh4&yzBosnP%Y}BQ zFqOq2j}Cmlp%L@=ms)WoVO0}y{Dx)!+vE1{+~mx+{mT6;_PtB2>QK0Sd#ppc+|Q$N z@tC{8FrkfK9sl6|{or@uCebB+S#v{D&AY{k>~G=7U$bOx0G|2uT@&~!r@3{SxC`+E zV+1zk8>e)AzyjxhPtjkHXrhnGeys7SZIoFC(U1k1`?(NrQYSH{5bV1IWWZk7BZ6Up=)7QuMIu# Ry4iCDfA3%~n&*jve*peN>y!Wh literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2710baea73f17b8568c43d348c077bb8caf017 GIT binary patch literal 2640 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 I?xW}b1y4Srng9R* literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..77137bc06db125d4b4543a43da3d832bae06d2da GIT binary patch literal 6082 zcmV;z7d_~SP)Nkl zSaQj&&0Vw9th7=~bIYAYMFm$F-|zXqbIx!WW`;R4gMd1}-}{5v&UxSe^F04&d(Rn# z;_l<_qt*vwO%yRPPJAq0+|=R_KZoz!ec1RQ0DEnUX@;YH5>;*x%;r7;RIGU zjmFs$;J&LhEkhTjwu{JJ5E!155w6Tji^RbHcn$C2GnNEmILBQJsnSqmFd`Q(Zmudw zk5J{Nzo5!bn-rFpzA!8=ZG$RLvxokkpmCl?IgKmADA&KHIWBx=1HOar@?+4ka7<&N z+ihWu^J91mJEOWi(h=uEzeskEPkgkBxKCxlAg;ouKhj;KMUA z+*EmKFA_j`Gzv`tuFBO^hUKa&#DV90Xf?WOLwMczvt|T>bLHXOn1i|b-L}Ao%UU z0k=vZIA^ukCfwW1LNZ!{!p$x-JJ4K-^JLJ*xTPpEP= zYXH8O7~MYLSNR5UU8+T?YeQzvY%Rs0-ZVcbE2|B0&*LzrbOTt#Y5K?~dpEJj^ zUMxX8VR*(YHwrVi>%qUBD91WVQe`La2#Oiq=GF>9i~#7bDmU$$I}iNoLQrNVtyK&i z>U?XWBS{QQnxB1#(2jXJ%F|SYY1K@bK2Iw~M|GPg2ohE;Qcn^c?i~ug#3zt*&Pin< zsk3A1)Cvp{PmcO2Lp|pM{EEG@el_d&^)mL)m3i#`#aXvb5P9kfRSx-sl=!8Dm6HTF z$7p|8cJhE~HT*hQMAy$U+2gD7*$@IU@t2)!&9TGm_j4ueuZtJhnesALa^*5By?TXR zympP1UBAZ4uV3e*jK(E;e&O0xcAnlZx?IjqmR)3rFPvxF&zxk7{`ifJ-?ouG^W_ru z(9&!cUT033{9@L878)Nr$T3zRIFu00%+?fg9+aA!BS#kk{)6u~vakL+#ExDpWtF;0 zrqk&hKF*Q=zdwC~#qZw1dVa30Gb#$zBpmh~JJid5r>y^S6Y}y?Azep;4=n^uB>(mk zE4*A*CxBZTu$pCu{$S4&U3GL+^Pl$>(kfNiDJ(cKHjam;HVZ)bVpwJZ`nTu#!E@9O ziF)o|oXN%!B^)?^mffhVyyf7F1D1BG>=K*3Ydd?GsL3G$07fLx(6ofoz)9mGgxsK+ zt=tdoPjsaAgM(KbKESTrxKVGw8;={66>Rg#W31PTJO@#c-U@;eW7WV=wl6Rg+ENpO z=zwP(rTu-sTFQPZI?XEUQR~fHgN|DyA}xnl0R*9$bIy0{h>rT)V5qZv?$1u1EXjDS z=Xd%J!bd5$B8=Qwc&fnxZ#?##E#hIU12%){kemh0{&cCHB`eN{)|HT!!C7OW=dNC{GrX*1e-em0hzda^*D2hT^L>0CeoPD|2CZMst^8Sv z=H${)z{lfW1GbSdG0kg>ew>t9Ny&0U&jfP3;Ce(qcn&Qoxl%4qv?YZdZ@aJu{w>n>0#ihd9zD9LJvtX1n&i&9wN ztSOniNM|-05?xRe1?yoy#E%aK&4GzBXalyHALBl}4 z0nn@0oHE#!Q^#Z*_#`` zV$XlIjPtl$&`{rz&P`8gU$Y6@H_MNRh|UWB+-GpevO8w*0~zy}-^9_cnh!@s{$G&r zp#*%J>i(aTnaW)xYWPn|v@5z`0jII?+cvRtSL8cm0KdXa7<-kRHXYW$VFS)y+@8f!jUlc+JhPSFxd$A4Rv!^7Pt zRu!;OTi3B~{yJ16Y`snq6xx*#m_0cKzK^!1J)N?Ud7tcckM#HC#kp#ckWl?>_trpf z^4dpi6+jfx$BM)I>!d9310`puQd*(`I9e3W&}6BLIUusKg@!nmh=h{jj(PNbJfLQE zR1iRi4~B-SwCX}z>;I^FqHt9gz1-ebnm?>?-1i&nq~WvWN#cJufFt!LD=Y#7mzr7{3Y4HGHaDZhTrs^m)U9AH(C*eihl40xEArcDXJ z=e>6#?V%tzLG10Zs?tbN&gILOSxHF=yKup}QUmJo%gMX(qri1$=2~DLZ%W9IKGz=ei9#Cxe zLm2=7W5K349`6Bg5qvBf&!wM1PZse%Dx-3%vgl9D-nE1GD5`bI$jYD$BK1K4#^;q< z1Lg;-LPOr3jan#01kH*;DZ-OuU9A)W0rctxlW+}9jnA@I0LT_+?);9GoGmd)DaM*F zQo;?RnN_}WBJfb)vy7N46Ef;i7_4%q6?94EGegxk3Ldg{)tW{(TXGX@;N+6b_$EiO18ZrvmlXq|99{ZU>q6Kzqz@NSaYw}UhYO_bIz7f6q+9`fG@uG z>iX4&8oU_pNs}N5#5fu;j=gQ!D)<6v?ZH|)X~wjNd4cyK9MqB!ZArt<&Ljj3+d|N| zQje#f>B(XEZH3Ofnp;TrCKWlohmz-`A-^yZkUIFvBHc1M&eCZzlra?W^^sA zsIm$^>C#P zHgm}>28zC*QmA&0*}8#OUZX5%Iuxpl5!G7Sm=&6qzv@!&K4pBz7Pp-qR)%2)_LN7YimGtJ&KBp+18WC_!bzkhYaoSnwezQ z3dCT;8vYMTh&|OZIvQo@7DBjh;h|K*3fhItotal%EV13NGJQJ5;df~RXnl3A*;B?%MjKY=r+9B6*8YXsj7Q7ioj58uAsuyvwc zz|@JK@KjpnCanmb515~H*UXv`2FeC~ND$Z?g!uiM13OxmDa!fddy!)TGM{&c%>$MKrdB@mH018yvqyJT#$4I`0XW^rY|9aZr3@C^ zOt1w64VDWO7D5=6GIvHJktz}zIPX%FRNEDhVzg%3VpHVD{z$&&OfPo5FX~ZXspxGD z{Zu{p9w8V*Q0xRX?ty@9)^c(JOEI(@ZNip9Y|=`jtaQVPqkMZf8ZfBOR|?kvU>_06 z#RySRLB)j>=oes@o~({umTA7ne50)`L+HP+!!-eZ?~lj^h60u{?;D|)n1|b=J^mJ& zkys?h3OeSfEel6$LxGPp3P~7xvbbOlyOiNTv0Lq5WHE5)*s)+s$4ctA%q5eS)?`Zi zgX5=_xOVIE7%(L6cd_MK`6i0+aNOG6zr*Bl@wo6n9Xdh=z2jx{l>k!8dwRKIpnTcO%&aD z4C~_@(&542c{7jb(`@zKTb3D04r+e)#K0Mo{%RKJ*8^A)eY-kB`^AS->!<+ypg};} zmtGjG)Wlaf^7b{2`lfwi%s+QVg~xMG4Fg63Rt~>=qO)tLcRQ5p{NH;!4xZi~e{g%; zF;TcTQ0k|w{jkA_z(&BRPVOh&MB(q>LeZ)f_O*2Mn>e-r$p*jvNqgtrJ5($;pw{1h z^xMmT3CBi%DQcbl(uR}5)6-pOtwnX1I({v>{e0WS9V7&-l{;Nr`8fXFjf6!s$3%}! zo&Ul{Q$>K@A1Iuiy9UHg*mfrg!2mw)f--5$4}?KCjzvJ-@V;p(qVUR9>C+bX^}qx= zO#8p>5<1e|=A5sC<`eikY3<#Cfq+G`x}+XcLJ*yDFpc{^Q&b zW_*bCK|9!H9xQRXxBM5DaKh%Tg4^L2hVUzNUHrz6S{$m5*Z=yE5F6h%aX!y-DcTt| z3=S8!90(EC-n>4*uOv(rLX_|DH$DxSH}eK=R3!L?#%T?_5vA7gtRL6-Cd|0u z{pKKqno)eM`Zd=p_|@o0;pTR)!o>wbi1d7+&s&rg9syT2N^6j4J=PE~eZo@ zisWm=TI-2+EDOQIqqU1q8{8QxqJ^*T-+kX7xgsPvj;ZwZ6&>#_H)KKJ#~AeeZ*H|A z^Jg*N_lB=>Y2O}Qtq8uhHf`E8H1MTDKp57lg|BA^{LTa7W3K`I#stPr+z;nv*eOO1 zts#Z1Jhar8RSb@{!}Jdic>TNY`$CMb64um8SYw0!Nkb$eE{b;Kpjx-aFOzn1c6Wc+ z_w6_42gOY(oPn0{UNiR*nhKacw~D$HhJJ^~P5m`n z8qV=}4RgVqd`Apj=-w%;x8P`1!ra{&D&<&9NiGUk*S0R+t>K`iB)u^faY22t|%ygwJzo(dsErXs7D*0@vT?A7Lft;4@^PYHAr z&gV*V7PP);V+6lO=FIuCRV!~7Z|@L65K+#QNw$6QnNhxDhc6GD`Oz@|f~H25WuzH| z!qa~(hQ=_skaE;I>1&+U^bI3jwPV1+lAw{N1kU>CFPszSra3$$XnZ)I8_iMR`euzC zcvHT?nJX@UlF8NkqZ&@DQ$ujli9M)?dG9Pc~!ozMIyjolO&J7G6*^`XE|r=AK)m{Alw z?~}6__&+Fq+A02C?8M#vlgDoI9Xq0+{jkCD_x9`aE{=s`3ZQr9=fXL0ZhcJO@=gHW zoR8qwB!F|_^spxtjSXI3*Mk*Jz;&h34Rqnsw(X;>RKZWUMRx4l=7Ff^Fz`QKqxX8^ zGs0)P3Y^|qIF=H}=8jd*@26 zui-s>2A|b~-9k9VUBK-Wh$s{%fijv3ph}1Mx|0O#1`qiE1FLy8L=Mcsv;Y7A07*qo IM6N<$g8i<<#Q*>R literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b32cc41565fcb3769d1d2eb1cb9a89479dcaff3f GIT binary patch literal 4781 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 LZAgu>W!V1#;v!$- literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4b1b704b8d41461030db2af814177b84ac91a99b GIT binary patch literal 9497 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b6de01b7a94ffd1a33367d73d706803c886a6340 GIT binary patch literal 6697 zcmb7pWmwct^zU~U7GY`WM!FjWsiiS!q(g}X0qM>qrIAhn38kcC$=x3uQlg8KD3GkUrK{H@u_h|5XSH?rOso zw|fAFZ5Rv^u0rv)fI>=}&Vf10OH)%TpOR)|`0BDYxm=(Pb2>=bLufbWF1W0OQ%N49fPl`s*R@_j2k!SJ^S(JXsq{hx9Gzl*NF8?RZQ$k z^SoThVaucv2qeXrqFFxe{QpEIqKOQI*&khYDeb*kz|yj^YM+auL7pmJWi^>Mzb3vp ze|^S8U7JJiHW<)2h4s>jf@{RIr$JGfI9miBCW=$JCXOPGyrI6-e6%j%tfP=c{KINp z{HF(!oEcjS3JDwus{Nvo0VNGyF9ngHROE(UW#L!12$Et3e-B(;{NH1qSfg7w!{ggzgA&mkG4MDcMi+N-R5r2Lplr$k zbks4^yM8ZrQ85}hX$lBBS55Gahqb2A^IL?Umb^g=J27bNCErwG?5X10i6QQRJd{+_GVLbNIavV^ONj0Cgu0Z>FKzz0NuO94VC;*O8H zYea$B|4#-X5cf8pawGLSmBt7gPm=qzk;Y`u%#g>=Ns2R}c&%7LmhN-*eeY0vmbNUK!^KP|?sR{s zeb%9gS6G8tTaQXhVrFQ%j>Jnr-l*tpJnC&_-$uxl;tfm5a$!zQIGOUTeZRQ)-_h`+ z?X~@llnK`%#B<`IL%K=YfT!<$^Ar>AtO<=iJ?8e!=KO<~@!R9IIq!$3hVG9Fxi+|i zL}{qwGXzZsY1m3DD%2geclev?be~YzpB_?*4hxy84!Wt`>1@MuO?`yg%Sn8df%~_O zwbHlxO;yIE0kgPlo%jIUnq}91TSs}*X;rLLr1ADo|nR?MEzfhg`V1Rl$b${QVYCFslw@Z6D zaAgJ%-cL7C2zpTa?c#h}!#}*3z9pgqs}#Tp!K8uFb~9WI7#jg0oZ#KOjYzXP-AC2m zx~LIsM3{gic*67Jp#c`0>9MV~(c5^%2wjCfZx94S4#swG-J`+goXZ`tP?o&oF1F~6 z2Um@OVU0|M#hU%yu#F+&jV2mmKExOwf6t3gM{DvqXVExX5V(Rm>o@mO9W<(+fN^z__X2~AkI zLAq>FkPXRlrvXtkxQZR5&=%Zeu`%*Rm7AyeQMp|}HTNwWRcg|o&fGbawa{pX+fl(B z-8k&(zOYfuPP~&#NKP#Gke$7EMgb)sz(!np*3b7GnoJkjP?d(7ahHnv68-(<)3I05 zRBxvo=|HDESGPHS8#hBOvp%<9jxX%K{U{lP@Dqj-{G-w5&;^}q12|>W^{Y3*=T)yS zHZ$aOmEUxoJATxCJGwoXI2hO_xxWC3rh1+ntD#OJHW1)$*u8$YEFq@8b7t0^Um*}F zU$1nwG(1TDJ8)l`K60-7jFOCp`%M^ zk;^sa;o9AR@DD~oner#Cx~l5&Y>#^LrPU7IQoOnXz#4w%eQo=B7)e|JjR5eF6|h?F0k_6;c@rIpTUYKby?H8wiI3g?$mWE6cA z=$tIcu^}IS7EdGplBeRYX<@s_>%$P;M3nY&X+%lp_dKxJZe(baQ(-WFAdY@zuIt$0x`;h!d4mqF9GZr*&8NSu zm6C$IXa>Z<2MdWy_eJ5gpOSG}_dFzc|4{k-%o6CtWffn$9QqqU#1sz@_10D`sVoxP zXSPCo`sGRm0m2GF&pIvRM~bc5;`e|)8=`REi%_p88Em2ik$jrv13c$9=#g%jy)K^F zvAoU^pz!Zst9u0C788^>4%0a>F?fh>5f5?YhG6UlhkVmmbVd<~KyK3>J*6IJryx%! zq{okE+d28%h%MHEUTqAgV}IjY7lr1>P+wf}pa{oi|YQRdp> z`f%k9GNDn$F1tP`{sadr4%h75u@HHhfFw{LM+(9f)38)p#;@>eRfL<_fSb;IVq4t4 z_4UC`#(BO3mbo{ggPuL?rR2=}=!c)Yf>^XiHYC+LhO63B;HOd+Le~*FYL?mnxp0cf zTq3;JbC9mSnVCMwAU07tf#}{@0VDooTAER8c>T8lNr$(|=|P|QeT#W;25Ffqu*KcPZ`1H22AUe0Wg7uoFRWuG~V!z8Oc9MMTMFDq_>Cko%YI7}_C z=la1u%h3&KdCw30$B0Z_Bjy--Bmb~ER>IaAFGd?=1_SWgwgYWXr&Vs4s!gefcl~-B zqoL)J{mlLkV;qt!RQTvG z_D?IDyxcn(d4X8oY#t5DceOoozxF7d?Xzi)hA$-(a@NL(exRCPazpgf!LUmzYxcS) zZ!eCtp8s_*H+f>Pd+}@dIfshnemlo7+u~j(86`EM_Gi2I;IlcIVawXp7YQyicIy@u zb+xODx1M8V9E)#@wlc|Ps1d{~sKEBgFk3}54~@quX5>jL&A&W5y>?NHa(%!({^bz7 zWqst}_*a80 zn)Y^zNZfWacCd?XjPOW$4+uN~MpFYNwaA)_lp|~^Dk|bDD!9BZyf0i`8$+Pi(gxkp zw3`RVKh21S>nOuLOMWSCrLed&Ffee4iyIUv1Q|Y%lQS=p574C!-c^5QTw(~&Anzxy zKHlaIgTp__hF!LDX#4xOkAD7q#HjL{X`;Bhi1W0fGCqHVtXfCr@C!YB@vDwq1Bsp`fT}&Beu4 z)xaJ%lPh$)mOLbbKPH<332~jU*y_hd>}Kp`d#^CTdyGEnR;!?~VRm$^jN5n0@gYQf zK2)X8jmDGPE!qS(6-G%Wr+PBD4f2UNc`=^umc0wvnvS;9ABWNmU}Vc4Sk5Q3eWaQl za|yC90AI+vEhH!pWaE8a{i^HDrT6ry(d6K}7e&J7O|+w6Cc6*J33J;rnC&IaP`z@N z(TJbp`Lz!HJ3l1o8Mu?$>R-vmQSYi&Xj;GKR(C^KJF*=varjph3%3ZygbeyE*HhUV z85s?IZ*fwD(zkGg)0UlWf%~sj-rc{uHQ0fkc(;f9+PIA)JCGs+Q60c9Bc2$vMfIOzRtuM6Hk*Df)TIq#rhVMY2C+h!8BE z4^}AbNkPE$NSZ8*xQ~y|+?QGTHhE5Ebt*QtL+_Ij?X<5HU-X7*S?t#8(jo)Jd|nK6 z+t3j$Bp}wjs7EBxEmNG}D>l!Bx8mMC9&T~d%Yq4D+;`BT&tT_@?Fbm>D*kJC?Ktn< zr%?C^9T^?C-!uCsbGvr;9! zQ*^A9ABaZRa7hRRcCt&@6d$A#e{)2~?`VgQv!A0@u79Hxe3QjEuWLV&^IIu7N!o^o z_Z1MGd?p#SaL{gl{cZ;r2XFC#_CKa<_xL!X`QP1ZlL(W_usMY8Oy}U29MGS~ z-wOCiH?SZ1Z*%uH{>u}(vvcQY2l*%&V>vTq_FOU#QUMBlO+CdeSaq}LYH5dKsdbf)PQO1}eP}odkhiTc zgm2~=q)V{5&pDu;7JnEf0EWVNFP61pt@mkS>Jqw>i9YzgUC$;)7*JKKUGsJno&J*1 zHpMb3YOq*1Rh1Vdd+4ukiJW)X%6rZ;kUKKTEbyu;?Xdpf)Tl~Rhk=k!sm5}rIIlwf z8vedzl@@T-76`wOh})_`cHz zyqJ^@i8$&BiA%|H-@%8Lmf<7btQ)1OKX4zb3@DKgA?*}D?zY?lQHlvLTE`AO2rf<) z6Cn3wRW+}*^+M5auO{HXGRk_hNQcv4QRH)y*;y4{a^*lOdV?VtR}kj?x-eU~cGkbi z_bCsS&cEA87#5_~R-~$<%S_Gp!ad_xt$BQP4<6i3zTTF_Q<)6=v5sS?kbk`c1bXk` zScAtHki7#W&p>&6cX6+BYvF8aTAoV7nf|-tC(_bn;MMf8Z!8SO+^Ta152mZUw=J9* z_hsjm4xYQAl~s|z_xa`H3MqnrYpup&5RB~p8084IHDtZ+vE2}8Qp0B?jqU8oh2$kd zsmWWVQ#;Z>LH2;p#%aumTwww{DiVfrO~O)&k1Y!Pooy;x9-O8|4_3)53+b~YYtK;gxLkvm|1L-20%dK3?1vizaxpU-vgJknQLdp5S@!}%{ya^X zH(`18P4v{=i0Q)MG?{Au#}X`yaGuRv#`GOHVfAIV112@+5WY|1lS3Jm6tBCCs(3W; z9N{|HF(Mf!xV3-62SKd#afL5@E+_p;|58BXS6+=;>^4*3e9fxaX{V=RZQnKxp7$>- zeWa$T>?ZvJBK+1lsn~R@a8224vytZP!9;@5ffq7Pa-CH&a(W9NZs|cV6fO#6$yTv{ z!Km#^1SMmo9P6ImGS)vH&sR6#OK=*-dOTkotIQO&)*pH5=Qq;^28;dj_RmxL+UR)~ zKg*HRr5yuOVWJ@S(FF;tJat6B=%|O_su^RqsY2KJy|ktT#_54CGUvW0h6HDg}q5f1c#KLz=H4lPcYE|<1GTM=_ zGD*#oGy^m1XUo#(#M4+pzmxGd^C-0?-TDwVZ9Z&RzrhbhtAh3G^&IlvPj*|>saj7K zZNAY+&Gk9z<4|hX-Efl#IB*PjM{Lu+@eqpc2V)f+liwRam1;i17*sec7C?>%)VH!R8Vnhbd_Lgh`+LNE}PbwZ1A%!IvhukKH*ogcM)lPBLji)^!9 zhbPScbsdJ*oI5PgwxQt;1CIz&QjJD-b+uP$#1p}dWdICbuH=#?VJsf!975`;&PV(9 zE>o_d5NEfH4d!&aQi)z_rd@w9Fh2(yk71bkee47+B@w%{%euAvI>cDVw@Gh&0=7t} zZ*ciL@vI!avy*g}T#gN?JRVZ?Zao_&@Z;?3HTX{-Py)AO84AURgQROC-Q5>MaCYO~ z;y*ApO~TUVL}jfXMgpJEI8w?wFoiHo$z3WhKa9VTC>`pzj$fD=F8AB{EpA^n8wqaA z1G*TPC&Ux;J3rFUyo+bJ>2gUA^!Z~kr%gNeZZDH68^xNNFOurylqkD9TpGal>pHzH zG7xr(`ea!HHn;sPsVOi!`T~(Zlj{nmMm*mAmUy{ZrPj4d)_2gdS%)Qc1#6CdlfyH% z1HI@vl<~@HY=(sij8@s2416MlqQ9VhJ$zfVo@?nr(GmEF-L-|)iMrGS35rv{;LN)u zILpyP<@uXwSRS})5Zu^|vNRx+rYAH4vdw_t+?#76ZIZmE_wT^$0(vs>Dq;|rK0pnq z+`%9C5EzK!4wm@;@33W60a4tE-$|<&2Z6?+q8|W4)P2|=P_N5onR{4>=b^tO=kXoh zRU0p`ZCLA}#P57QYfm2Ss`JmU)}8@`f3oY-du7paguY=}($JM9Zo31z9zxhJxGcgi zZI*7YeiU~frrKhFntL&E^DzJyBSeR#JNFK{(+&^qy)WgL?0od!vKH4!0e?LN zGH1Bb5(9w2E~CU~ip8CsPQwrOADmw4Zni(*VSUZ@OlpiZUc> zI$S}Zu8O5wjB1J$)Y|;{(2h-i zRxCQU2{2$?;Fa}`az2_y4Nd6XY?ok6R_8mU2o|Kinm+*+?##Q8l34qVcUk z2<(%^K6q~-Hse8*$bD>@cQe<7Ta4wi|W!6a$t8xg$6e z1u@B6sT=zIx$d(_$IsKZ3$EbGnOPlE4GIbhu54xjj~bsB*OwG3PKES@s++3(4}UG} zg6sLdUa8&`O6kruN?g1KR~@i}@kK^S+6=4f5WqGqWH#Hb%Jie_i{9W>t}d$Z1a}N# zOSQTi&ppe#B6~PLj%=&J-47(2SejF_LQDudIHmpcwz^W?Rp$rtyMK%=r`e{eYYNkZ zRK3&`(x_G~sww^IzGg+T2Gnclf??gcpI z#kB~X6e?+E&8sJR;ijrp?!1IijXUbuoPDeY6gQ;*w=Wipi+z;dARNDRaQ^)63kZ0u Mp{ri6`XcIo0Kl()*#H0l literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..df294f6c500e916504ea1bc8a42c4b72fbc04039 GIT binary patch literal 8026 zcmds6_dgq2*jH`2+Nv9BuU@hDD2k%@5~*E^*wk(jMTr5t#87*DV?v8b;kG+NLx#*XsVa|3P;( zGGHVtPD8{0m#+5TW0Z&Ryo>?#gC)hog~JcakgceEbojAaVBD;#(% zYO_!dTf`C$NkQJ^9*%@bPE&Ok}fq^-u)u7E?JMD^+kr)PW zNc=2MjBz??QzR|`;(+#C^);n83c^z?5{-E|eS>jZfXZ8aehzbtDr!z5#!O$2%PB*E0B0A<>#g^9tr5L8;4%(QT0K1xBXb+_r-eqPgzv^&E^zMl@? zL5LtRn{=nY`O!O?T%j69U}B7=aK1sg{?Gx`GerDapRsNBhsiIL4{9fa0VHkCm^+Tz z#4IbLgNt0-aYBtW?2g>`>lueJ5-{AuDqBkfg(`9ONXmBxx)RUbc_8t0##_Xs`lOI=HKay8*z7LHA1dQ+{KiVMKk%MZKVJ4o5*}%+Q?)8aPbcL|8W#kkJmWcR z*>$|gG4ymoaCIS~XJ1=M!ivu32A)1EuRZTs)8T^iiQ(@7g&7_k!vlHvdCX1x@2MIA^1Or9rOO33XqN_!}4NE1sn$a3Mzo6@~Vt1C%S`ewt#B zHkJDJ&=$wE2Qz+N2iE~UF+tGhcAN%eHb^35X&%%%)+&%^ju>qNY*a-495)9Ta~_6b z1&B?^kpAOL4A3ZH)+Q$EXKK4r?8BlT-p1V_u`@C4rDY-I0}x8a^| z5_7L4<}Y@QU4SCR(Zzr<=xSD;Mbk`gHCNqQfVo;)z*%mltANkEg(Jm#bw1Dj>~g`t zyO!B`~w~O%0!pXXyPVd3ylpt+e$U{jkYnhGCvyw7CE^hYf;e2G8=alUN3liOG3WQ#QWGSn_PotzQC}#=D=MV!CD7xuSHU$uS-wL z(O)dKV=$ewb4vNOf;k4S>V_0<>Hc1~#61NSx7Rk;#hVsXz2WhnM|rqUd?Ql{MIFNp z5?QUSuG>#Eo#Q>{)QET^3~$7Ru@i--SpK1Rm2~FO9!&AmKOwN2Dil zQv4nR2kT+M7ovpUQw?#xpC7&G;*#ZfZ!!C>$n1|x`G)tXRV_o0v_;e2Bb~WhDzzW0 zYrVyTH8fTV$QKjH@YD6)AJI*t&vu``N}2VKozXxRjo61Q{qp^kgP(cNh8JT+C{w&0 zX-}c=2$vIdcGPpyjiS(T)dCORS1J?lgnhnz6RN)+H_lWv{BM9E&OL6%%E0hNxg4Z%KOc^H?k~>6+ z;SW4`L?vnuFzw5aj($4;HPk~3I&+361TnAS=jc9)KcJMYXXb0~qW9>nv^U7czHF;}wQ9ZD5ut+tg4~{7t-Pbkwj0&-QIQ zdosSKFL~MWVs4SnVtE4wKaXZQjb}oIw>XR&K*T!kM~rwnlBTCE{ytU!kux%^dc$+s z1Ff#tYND7o7kdwCt(GY)kR%{m^{Q*u4=Mm7-X+_?G7D#{{=#PVYKxwAB;e&uLcYE@ zpU-bt-D8HRpV{qxz$sS7>h|p|S5^4gW*=aP%NFk5O3B}Ub*^�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%0D#QgDKJt1U7l?wR;-yEQQ&|PKKIAc}k v*i{+E;t_G}tB3IZ|NAf8D+eDeTwee4LFmrFuTN_K>#r`zNW11A$G86j?ZALH literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..dbb7b2e885ec7783251bfd779d51e0e7eff385b1 GIT binary patch literal 13553 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..586bd8e --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,77 @@ + + + Otter + + Veuillez saisir les détails de votre instance Funkwhale pour accéder à son contenu + Nom d\'hôte + Nom d\'utilisateur + Mot de passe + Se connecter + Connexion + + Cela ne semble pas être un nom d\hôte valide + Le nom d\'hôte Funkwhale devrait être sécurisé à travers HTTPS + + Cast + Rechercher + + Paramètres + Licences open source + + Recherchez des artistes, albums ou morceaux + Saisissez vos termes de recherche ci-dessus et validez pour rechercher dans votre collection. + Aucun résultat n\'a été trouvé pour votre recherche + + Général + Qualité des médias + Meilleure qualité + Fichiers plus légers + Les pistes de meilleure qualité seront utilisées + Les pistes les plus légères seront utilisées + Taille du cache + %d Go seront utilisés pour mettre en cache les pistes pour la lecture hors-ligne + Autres + Mode nuit + Toujours activé (mode sombre) + Le mode nuit sera toujours préféré + Toujours désactivé (mode clair) + Le mode jour sera toujours préféré + Suivre les préférences du système + Le mode nuit suivra les préférence système + Déconnexion + + Artistes + Albums + Playlists + Favoris + + Contrôle de lecture + Contrôler la lecture musicale + Lecture aléatoire + Ajouter + Votre liste de lecture est vide + Retirer + Ajouter à la liste de lecture + Prochaine écoute + + Ajouter à une playlist + Add to favorites + + Lecture / pause + Piste précédente + Piste suivante + + Cette piste ne peut pas être jouée + + + %d album + %d albums + + + Logo de l\'application + Image de l\'artiste + Couverture de l\'album + + Déconnexion + Etes-vous certains de vouloir vous déconnecter de votre instance Funkwhale ? + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..cc9facb --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,10 @@ + + + #121212 + #71a5cd + + #525252 + #caffffff + + #caffffff + \ No newline at end of file diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml new file mode 100644 index 0000000..413f260 --- /dev/null +++ b/app/src/main/res/values/array.xml @@ -0,0 +1,24 @@ + + + + @string/settings_media_quality_quality + @string/settings_media_quality_size + + + + quality + size + + + + @string/settings_night_mode_on + @string/settings_night_mode_off + @string/settings_night_mode_system + + + + on + off + system + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..ee90bdf --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,16 @@ + + + @android:color/background_light + @android:color/background_light + + #327eae + #3d3e40 + #d35400 + + #dadada + #e17055 + + @android:color/black + + @color/colorPrimary + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..02a0efd --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + @color/colorPrimary + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..5269a06 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,77 @@ + + + Otter + + Please enter the details of your Funkwhale instance to access its content + Host name + Username + Password + Log in + Logging in + + This could not be understood as a valid URL + The Funkwhale hostname should be secure through HTTPS + + Cast + Search + + Settings + Open source licences + + Search artists, albums and tracks + Enter your search terms above and hit enter to search your collection + No results were found for your query + + General + Media quality + Best quality + Smallest size + Best available track will be played + Smallest available track will be played + Media cache size + %d GB will be used to store tracks for offline playback + Other + Night mode + Always on (dark mode) + Night mode will always be preferred + Always off (light mode) + Light mode will always be preferred + Follow system settings + Night mode will follow system settings + Sign out + + Artists + Albums + Playlists + Favorites + + Media controls + Control media playback + Shuffle + Queue + Your queue is empty + Remove + Add to queue + Play next + + Add to playlist + Ajouter aux favoris + + Toggle playback + Previous track + Next track + + This track could not be played + + + %d album + %d albums + + + Application logo + Artist art + Album cover + + Sign out + Are you sure you want to sign out of your Funkwhale instance? + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..3783e6e --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml new file mode 100644 index 0000000..240407f --- /dev/null +++ b/app/src/main/res/xml/settings.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..dfbafcc --- /dev/null +++ b/build.gradle @@ -0,0 +1,38 @@ +buildscript { + ext.kotlin_version = '1.3.50' + + repositories { + google() + jcenter() + + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +plugins { + id 'org.jlleitschuh.gradle.ktlint' version '8.1.0' +} + +allprojects { + repositories { + google() + jcenter() + } +} + +subprojects { + apply plugin: 'org.jlleitschuh.gradle.ktlint' + + ktlint { + debug = false + verbose = false + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..23339e0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8adb0b4 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Aug 28 14:01:52 CEST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'