Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • bugfix/46-adjust-now-playing-top-margin
  • deploy-in-docker
  • develop
  • enhancement/speed-up-pipelines
  • fix-ci-for-tags
  • gradle-v8
  • housekeeping/mavenCentral
  • housekeeping/remove-warnings
  • renovate/io.mockk-mockk-1.x
  • technical/skip-metadata-file-for-branches
  • technical/update-compile-sdk-version-docker
  • technical/update-jvmTarget-version
  • technical/upgrade-appcompat-1.5.x
  • technical/upgrade-exoplayer
  • 0.0.1
  • 0.1
  • 0.1.1
  • 0.1.2
  • 0.1.3
  • 0.1.4
  • 0.1.5
  • 0.1.5-1
  • 0.2.0
  • 0.2.1
  • 0.2.1-1
  • 0.3.0
  • fdroid-dummy-1
  • fdroid-dummy-2
  • fdroid-dummy-3
  • fdroid-dummy-4
  • fdroid-dummy-5
31 results

Target

Select target project
  • funkwhale/funkwhale-android
  • creak/funkwhale-android
  • Keunes/funkwhale-android
  • Mouath/funkwhale-android
4 results
Select Git revision
  • bugfix/46-adjust-now-playing-top-margin
  • bugfix/90-error-playing-user-radio
  • deploy-in-docker
  • develop
  • enhancement/89-update-appstore-metadata
  • enhancement/speed-up-pipelines
  • housekeeping/remove-warnings
  • renovate/configure
  • 0.0.1
  • 0.1
  • 0.1.1
11 results
Show changes
Commits on Source (429)
Showing
with 548 additions and 682 deletions
*.iml *.iml
.gradle **/.gradle
/local.properties /local.properties
/.idea /.idea
.DS_Store .DS_Store
/build **/build
/captures /captures
.externalNativeBuild .externalNativeBuild
*.keystore *.keystore
image: jangrewe/gitlab-ci-android # This image lives in https://dev.funkwhale.audio/funkwhale/ci
image: $CI_REGISTRY/funkwhale/ci/android:latest
variables: variables:
COBERTURA_REPORT: '$CI_PROJECT_DIR/app/build/reports/cobertura.xml' COBERTURA_REPORT: '$CI_PROJECT_DIR/app/build/reports/cobertura.xml'
...@@ -6,11 +7,20 @@ variables: ...@@ -6,11 +7,20 @@ variables:
JACOCO_XML_LOCATION: '$CI_PROJECT_DIR/app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml' JACOCO_XML_LOCATION: '$CI_PROJECT_DIR/app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml'
stages: stages:
- build_ci_env
- test - test
- visualize - visualize
- build - build
- test-after-build
- deploy - deploy
cache: &global_cache
key: ${CI_PIPELINE_ID}
paths:
- .gradle/wrapper
- .gradle/caches
policy: pull
.gradle-default: .gradle-default:
before_script: before_script:
- export GRADLE_USER_HOME=$(pwd)/.gradle - export GRADLE_USER_HOME=$(pwd)/.gradle
...@@ -19,11 +29,6 @@ stages: ...@@ -19,11 +29,6 @@ stages:
script: script:
- echo "Overwrite me" - echo "Overwrite me"
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
.build: .build:
stage: build stage: build
variables: variables:
...@@ -34,7 +39,7 @@ stages: ...@@ -34,7 +39,7 @@ stages:
before_script: before_script:
- git fetch --unshallow --tags - git fetch --unshallow --tags
after_script: after_script:
- export versionCode=`$ANDROID_HOME/build-tools/30.0.2/aapt dump badging $apk_file | grep versionCode | awk '{print $3}' | sed s/versionCode=//g | sed s/\'//g` - export versionCode=`$ANDROID_HOME/build-tools/30.0.3/aapt dump badging $apk_file | grep versionCode | awk '{print $3}' | sed s/versionCode=//g | sed s/\'//g`
- apt update && apt install gettext-base - apt update && apt install gettext-base
- cat $metadata_template | envsubst > $metadata_file - cat $metadata_template | envsubst > $metadata_file
extends: .gradle-default extends: .gradle-default
...@@ -43,6 +48,9 @@ stages: ...@@ -43,6 +48,9 @@ stages:
- $apk_file - $apk_file
- $metadata_file - $metadata_file
- $output_metadata - $output_metadata
cache:
# inherit all global cache settings
<<: *global_cache
test: test:
extends: .gradle-default extends: .gradle-default
...@@ -50,17 +58,30 @@ test: ...@@ -50,17 +58,30 @@ test:
except: except:
- tags - tags
script: script:
- ./gradlew test jacocoTestReport - ./gradlew --no-daemon --stacktrace test jacocoTestReport
- awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print covered, "/", instructions, " instructions covered"; print 100*covered/instructions, "% covered" }' $JACOCO_CSV_LOCATION - awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print covered, "/", instructions, " instructions covered"; print 100*covered/instructions, "% covered" }' $JACOCO_CSV_LOCATION
artifacts: artifacts:
reports: reports:
junit: app/build/test-results/test**/TEST-*.xml junit: app/build/test-results/test**/TEST-*.xml
paths: paths:
- $JACOCO_XML_LOCATION - $JACOCO_XML_LOCATION
cache:
# inherit all global cache settings
<<: *global_cache
# override the policy
policy: pull-push
test_nonfree_code:
stage: test-after-build
image: registry.funkwhale.audio/funkwhale/ci/android-fdroidserver
script:
- fdroid scanner -v app/build/outputs/apk/*/app-*.apk |& tee output.txt
- cat output.txt
- (! grep "CRITICAL" output.txt)
coverage: coverage:
stage: visualize stage: visualize
image: gjrtimmer/jacoco2cobertura:1.0.8 image: haynes/jacoco2cobertura:1.0.9
script: script:
# convert report from jacoco to cobertura, use relative project path # convert report from jacoco to cobertura, use relative project path
- 'python /opt/cover2cover.py $JACOCO_XML_LOCATION $CI_PROJECT_DIR/app/src/main/java > app/build/reports/cobertura.xml' - 'python /opt/cover2cover.py $JACOCO_XML_LOCATION $CI_PROJECT_DIR/app/src/main/java > app/build/reports/cobertura.xml'
...@@ -71,13 +92,15 @@ coverage: ...@@ -71,13 +92,15 @@ coverage:
- tags - tags
artifacts: artifacts:
reports: reports:
cobertura: $COBERTURA_REPORT coverage_report:
coverage_format: cobertura
path: $COBERTURA_REPORT
build-develop: build-develop:
extends: .build extends: .build
script: script:
- echo -n $PREVIEW_SIGNING_KEY_STORE | base64 -d > app/android.keystore - echo -n $PREVIEW_SIGNING_KEY_STORE | base64 -d > app/android.keystore
- ./gradlew assembleDebug -Psigning.store=android.keystore -Psigning.store_passphrase=$PREVIEW_SIGNING_KEY_PASS -Psigning.key_passphrase=$PREVIEW_SIGNING_KEY_PASS - ./gradlew --stacktrace --no-daemon assembleDebug -x check -Psigning.store=android.keystore -Psigning.store_passphrase=$PREVIEW_SIGNING_KEY_PASS -Psigning.key_passphrase=$PREVIEW_SIGNING_KEY_PASS
only: only:
- develop - develop
...@@ -90,45 +113,51 @@ build-release: ...@@ -90,45 +113,51 @@ build-release:
extends: .build extends: .build
script: script:
- echo -n $SIGNING_KEY_STORE | base64 -d > app/android.keystore - echo -n $SIGNING_KEY_STORE | base64 -d > app/android.keystore
- ./gradlew assembleRelease -Psigning.store=android.keystore -Psigning.store_passphrase=$SIGNING_KEY_PASS -Psigning.key_passphrase=$SIGNING_KEY_PASS - ./gradlew --stacktrace --no-daemon assembleRelease -Psigning.store=android.keystore -Psigning.store_passphrase=$SIGNING_KEY_PASS -Psigning.key_passphrase=$SIGNING_KEY_PASS
only: only:
- tags - tags
build-bleeding-edge: build-bleeding-edge:
extends: .build extends: .build
script: script:
- ./gradlew assembleDebug - ./gradlew --stacktrace --no-daemon -x check assembleDebug
except: except:
- develop - develop
- tags - tags
.deploy: .deploy:
image: debian image: curlimages/curl:latest
before_script: script:
- apt update && apt -y install openssh-server - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file $FILE "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/$PACKAGE/$CI_COMMIT_SHORT_SHA/$PACKAGE-$CI_COMMIT_SHORT_SHA.apk"'
deploy-develop: deploy-develop:
extends: .deploy extends: .deploy
stage: deploy stage: deploy
only: only:
- develop - develop
script: variables:
- eval `ssh-agent -s` FILE: app/build/outputs/apk/debug/app-debug.apk
- ssh-add <(echo "$SSH_PRIVATE_KEY") PACKAGE: audio.funkwhale.ffa.dev
- scp -o StrictHostKeyChecking=no app/build/outputs/apk/debug/app-debug.apk fdroid@apps.funkwhale.audio:/srv/fdroid/fdroid/develop/repo/audio.funkwhale.ffa.dev-$CI_COMMIT_SHORT_SHA.apk
- scp -o StrictHostKeyChecking=no app/build/outputs/apk/debug/output-metadata.json fdroid@apps.funkwhale.audio:/srv/fdroid/fdroid/develop/output-metadata.json
- scp -o StrictHostKeyChecking=no metadata/audio.funkwhale.android.dev.yml fdroid@apps.funkwhale.audio:/srv/fdroid/fdroid/develop/metadata/audio.funkwhale.ffa.dev.yml
- ssh -o StrictHostKeyChecking=no fdroid@apps.funkwhale.audio 'docker run --rm -u $(id -u):$(id -g) -v /srv/fdroid/fdroid/develop:/repo registry.gitlab.com/fdroid/docker-executable-fdroidserver:master update'
deploy-release: deploy-release:
extends: .deploy extends: .deploy
stage: deploy stage: deploy
only: only:
- tags - tags
script: variables:
- eval `ssh-agent -s` FILE: app/build/outputs/apk/release/app-release.apk
- ssh-add <(echo "$SSH_PRIVATE_KEY") PACKAGE: audio.funkwhale.ffa
- scp -o StrictHostKeyChecking=no app/build/outputs/apk/release/app-release.apk fdroid@apps.funkwhale.audio:/srv/fdroid/fdroid/develop/repo/audio.funkwhale.ffa-$CI_COMMIT_TAG.apk
- scp -o StrictHostKeyChecking=no app/build/outputs/apk/release/output-metadata.json fdroid@apps.funkwhale.audio:/srv/fdroid/fdroid/develop/output-metadata.json trigger-fdroid-update-develop:
- scp -o StrictHostKeyChecking=no metadata/audio.funkwhale.android.yml fdroid@apps.funkwhale.audio:/srv/fdroid/fdroid/develop/metadata/audio.funkwhale.ffa.yml stage: .post
- ssh -o StrictHostKeyChecking=no fdroid@apps.funkwhale.audio 'docker run --rm -u $(id -u):$(id -g) -v /srv/fdroid/fdroid/develop:/repo registry.gitlab.com/fdroid/docker-executable-fdroidserver:master update' only:
- develop
image: curlimages/curl:7.88.1
script: curl "https://fdroid.funkwhale.audio/hooks/update-index?name=audio.funkwhale.ffa.dev&version=$CI_COMMIT_SHORT_SHA"
trigger-fdroid-update-release:
stage: .post
only:
- tags
image: curlimages/curl:7.88.1
script: curl "https://fdroid.funkwhale.audio/hooks/update-index?name=audio.funkwhale.ffa&version=$CI_COMMIT_SHORT_SHA"
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
...@@ -22,6 +13,10 @@ A clear and concise description of what the bug is. ...@@ -22,6 +13,10 @@ A clear and concise description of what the bug is.
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Actual behavior**
A clear and consise description of what actually happened, instead.
**Screenshots** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
...@@ -36,3 +31,5 @@ If applicable, add screenshots to help explain your problem. ...@@ -36,3 +31,5 @@ If applicable, add screenshots to help explain your problem.
**Logs** **Logs**
Add any related logs from ADB or from the "Copy logs" setting. Add any related logs from ADB or from the "Copy logs" setting.
/label ~"Type: Bug"
# Enable auto-env through the sdkman_auto_env config # Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below # Add key=value pairs of SDKs to use below
java=11.0.11.hs-adpt java=11.0.13-tem
java temurin-11.0.16+101
0.3.0 (2023-12-12)
Features:
- Add option to limit bandwidth usage by streaming transcoded music
- Improve player bottom sheet, in particular fling support
Enhancements:
- Refactor CoverArt.withContext().
Bugfixes:
- Fix buffering progress bar display
- Fix landscape view induced MainActivity leak.
- Fix Too Many Receivers exception
0.2.1 (2023-04-18)
Bugfixes:
- Removed navigation-dynamic-features-fragment, which has proprietary dependencies and isn't needed
0.2.0 (2023-04-05)
Features:
- Add filtering functionality to favorites view (thanks @PhieF)
- Allow backward skip after pause by configurable number of seconds (contributed by hdasch)
- Use the track cover in an album track list if one is available
Bugfixes:
- Make the mini player overlay stay on top (contributed by @christophehenry)
- Use Picasso stableKey for better caching against pre-signed URLs (thanks @rickosborne)
0.1.5 (2022-07-04)
Bugfixes:
- Fix App crashes when interacting with playlist (@Mouath)
- Fix leaked database cursor resource
- Fix playback order to respect preference setting on albums fragment
- Fix the removal of existing downloads
- Fix unresponsive bluetooth buttons with Oreo and later (thanks @hdasch)
- Fix warnings in log output due to leaked BufferedReader resource (thanks @hdasch)
- Fixes problem where users are logged out sporadically (thanks to @hdasch)
0.1.4 (2021-09-18)
Bugfixes:
- Fix application crash when opening playlists view (#99)
0.1.3 (2021-09-17)
Bugfixes:
- Disable landscape mode to avoid application crashes (#93)
- Fix handling of hostname 'https://' scheme prefix (#88)
- Remember scroll positions in list views (Artists/Albums/...) (#95)
- Remove trailing slash from hostname (#92)
- Use correct radio identifier for user radio (#90)
Other:
- Add hard coded version information for F-Droid deployment (#97)
- Automatically update the favorites list view (#28)
- Update Fastlane metadata for app store deployments (#89)
0.1.1 (2021-08-31) 0.1.1 (2021-08-31)
Features: Features:
......
# Funkwhale for Android # Funkwhale for Android
This is the official Android music player for [Funkwhale](https://funkwhale.audio), native to both Android (developed in Kotlin) and to Funkwhale (uses its native API instead of Subsonic). This is the official Android music player for [Funkwhale](https://funkwhale.audio), native to both Android (developed in Kotlin) and to Funkwhale (uses its native API instead of Subsonic).
It is based on the amazing [Otter](https://github.com/apognu/otter) made by [apognu](https://github.com/apognu) and would not be possible without his groundwork! It is based on the amazing [Otter](https://github.com/apognu/otter) made by [apognu](https://github.com/apognu) and would not be possible without his groundwork!
...@@ -7,15 +7,14 @@ You can get help and discuss Funkwhale on Matrix on [#funkwhale-android:matrix.o ...@@ -7,15 +7,14 @@ You can get help and discuss Funkwhale on Matrix on [#funkwhale-android:matrix.o
## Installation ## Installation
Currently you can install a preview version of Funkwhale for Android through a selfhosted [F-Droid repository](https://fdroid.funkwhale.audio/develop/). We have an official version available on F-Droid and the Google Play-Store, but you can also install a preview version of Funkwhale for Android™ through our selfhosted [F-Droid repository](https://fdroid.funkwhale.audio/develop/).
You'll have to add this repository to your F-Droid client, please visit the link above for further instructions. Once you added the repository, you can You'll have to add this repository to your F-Droid client, please visit the link above for further instructions. Once you added the repository, you can use F-Droid as usual and search for "Funkwhale".
use F-Droid as usual and search for "Funkwhale".
## State ## State
Funkwhale for Android is work in Progress. Please bear with us, there will be bugs, there will be crashes and there will be performance and UX issues. Funkwhale for Android is work in Progress. Please bear with us, there will be bugs, there will be crashes and there will be performance and UX issues.
Here is the list of Funkwhale for Android's features: Here is the list of Funkwhale for Android's features:
* Basic collection browsing (artists, albums and tracks) * Basic collection browsing (artists, albums and tracks)
* Playlists listing * Playlists listing
...@@ -27,20 +26,16 @@ Here is the list of Funkwhale for Android's features: ...@@ -27,20 +26,16 @@ Here is the list of Funkwhale for Android's features:
* Radios playback * Radios playback
* Dark mode! 🎉 * Dark mode! 🎉
Funkwhale for Android will try to behave as you would expect a mobile music player to. That means it integrates with the OS's media controls (including headset controls) or pause on incoming calls. If there is anything you would like it to do, please [open an issue](https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/issues/new). Funkwhale for Android will try to behave as you would expect a mobile music player to. That means it integrates with the OS's media controls (including headset controls) or pause on incoming calls. If there is anything you would like it to do, please [open an issue](https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/issues/new).
## Screenshots ## Screenshots
<img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png" width="200" /> <img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png" width="33%" />
<img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png" width="200" /> <img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png" width="33%" />
<img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png" width="200" /> <img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png" width="33%" />
<img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png" width="200" />
<img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png" width="200" />
<img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png" width="200" />
<img src="https://dev.funkwhale.audio/funkwhale/funkwhale-android/-/raw/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png" width="200" />
## Translation ## Translation
Funkwhale for Android is being translated by the community through [Weblate](https://translate.funkwhale.audio/settings/funkwhale/ffa). If you would like to contribute to its localization or add a new language, you can help out there. Funkwhale for Android is being translated by the community through [Weblate](https://translate.funkwhale.audio/settings/funkwhale/ffa). If you would like to contribute to its localization or add a new language, you can help out there.
[![Translation status](https://translate.funkwhale.audio/widgets/funkwhale/-/ffa/multi-auto.svg)](https://translate.funkwhale.audio/engage/funkwhale/) [![Translation status](https://translate.funkwhale.audio/widgets/funkwhale/-/ffa/multi-auto.svg)](https://translate.funkwhale.audio/engage/funkwhale/)
...@@ -5,12 +5,16 @@ import java.util.Properties ...@@ -5,12 +5,16 @@ import java.util.Properties
plugins { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") id("kotlin-android")
id("androidx.navigation.safeargs.kotlin")
id("kotlin-parcelize")
id("kotlin-kapt")
id("org.jlleitschuh.gradle.ktlint") version "8.1.0" id("org.jlleitschuh.gradle.ktlint") version "11.2.0"
id("com.gladed.androidgitversion") version "0.4.14" id("com.gladed.androidgitversion") version "0.4.14"
id("com.github.triplet.play") version "2.4.2" id("com.github.triplet.play") version "3.8.1"
id("de.mobilej.unmock") id("de.mobilej.unmock")
id("com.github.ben-manes.versions") id("com.github.ben-manes.versions")
id("org.jetbrains.kotlin.android")
jacoco jacoco
} }
...@@ -26,33 +30,41 @@ unMock { ...@@ -26,33 +30,41 @@ unMock {
} }
androidGitVersion { androidGitVersion {
codeFormat = "MMNNPPBBB" codeFormat = "MMNNPPBBB" // Keep in sync with version_code() in dist/create_release.sh
format = "%tag%%-count%%-commit%%-branch%" format = "%tag%%-count%%-commit%%-branch%"
} }
android { android {
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
namespace = "audio.funkwhale.ffa"
testCoverage { testCoverage {
version = Versions.jacoco version = "0.8.7"
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() jvmTarget = JavaVersion.VERSION_17.toString()
} }
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
dataBinding = true
}
packagingOptions {
resources.excludes.add("META-INF/LICENSE.md")
resources.excludes.add("META-INF/LICENSE-notice.md")
} }
lint { lint {
disable += listOf("MissingTranslation", "ExtraTranslation") disable += listOf("MissingTranslation", "ExtraTranslation")
} }
compileSdk = 30 compileSdk = 33
defaultConfig { defaultConfig {
...@@ -62,7 +74,7 @@ android { ...@@ -62,7 +74,7 @@ android {
versionName = androidGitVersion.name() versionName = androidGitVersion.name()
minSdk = 24 minSdk = 24
targetSdk = 30 targetSdk = 33
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
...@@ -141,64 +153,75 @@ ktlint { ...@@ -141,64 +153,75 @@ ktlint {
} }
play { play {
isEnabled = props.hasProperty("play.credentials") enabled.set(props.hasProperty("play.credentials"))
if (isEnabled) { if (enabled.get()) {
serviceAccountCredentials = file(props.getProperty("play.credentials")) serviceAccountCredentials.set(file(props.getProperty("play.credentials")))
defaultToAppBundles = true defaultToAppBundles.set(true)
track = "beta" track.set("beta")
} }
} }
dependencies { dependencies {
val navVersion: String by rootProject.extra
val lifecycleVersion: String by rootProject.extra
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar")))) implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
implementation("androidx.appcompat:appcompat:1.3.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.core:core-ktx:1.6.0") implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha03") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0") implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
implementation("androidx.preference:preference-ktx:1.1.1") implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.recyclerview:recyclerview:1.2.1") implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("com.google.android.material:material:1.4.0") implementation("com.google.android.material:material:1.9.0") {
implementation("com.android.support.constraint:constraint-layout:2.0.4") exclude("androidx.constraintlayout")
}
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("com.google.android.exoplayer:exoplayer-core:${Versions.exoPlayer}") implementation("com.google.android.exoplayer:exoplayer-core:2.18.1")
implementation("com.google.android.exoplayer:exoplayer-ui:${Versions.exoPlayer}") implementation("com.google.android.exoplayer:exoplayer-ui:2.18.1")
implementation("com.google.android.exoplayer:extension-mediasession:${Versions.exoPlayer}") implementation("com.google.android.exoplayer:extension-mediasession:2.18.1")
implementation("io.insert-koin:koin-core:${Versions.koin}") implementation("io.insert-koin:koin-core:3.5.3")
implementation("io.insert-koin:koin-android:${Versions.koin}") implementation("io.insert-koin:koin-android:3.5.3")
testImplementation("io.insert-koin:koin-test:${Versions.koin}") testImplementation("io.insert-koin:koin-test:3.5.3")
implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:${Versions.exoPlayerExtensions}") { implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:789a4f83169cff5c7a91655bb828fde2cfde671a") {
isTransitive = false isTransitive = false
} }
implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-flac:${Versions.exoPlayerExtensions}") { implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-flac:789a4f83169cff5c7a91655bb828fde2cfde671a") {
isTransitive = false isTransitive = false
} }
implementation("com.aliassadi:power-preference-lib:${Versions.powerPreference}") implementation("com.github.AliAsadi:PowerPreference:2.1.1")
implementation("com.github.kittinunf.fuel:fuel:${Versions.fuel}") implementation("com.github.kittinunf.fuel:fuel:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-coroutines:${Versions.fuel}") implementation("com.github.kittinunf.fuel:fuel-coroutines:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-android:${Versions.fuel}") implementation("com.github.kittinunf.fuel:fuel-android:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-gson:${Versions.fuel}") implementation("com.github.kittinunf.fuel:fuel-gson:2.3.1")
implementation("com.google.code.gson:gson:${Versions.gson}") implementation("com.google.code.gson:gson:2.10.1")
implementation("com.squareup.picasso:picasso:2.71828") implementation("com.squareup.picasso:picasso:2.71828")
implementation("jp.wasabeef:picasso-transformations:2.4.0") implementation("jp.wasabeef:picasso-transformations:2.4.0")
implementation("net.openid:appauth:${Versions.openIdAppAuth}") implementation("net.openid:appauth:0.11.1")
implementation("androidx.navigation:navigation-fragment-ktx:$navVersion")
implementation("androidx.navigation:navigation-ui-ktx:$navVersion")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.12.0") testImplementation("io.mockk:mockk:1.13.4")
testImplementation("androidx.test:core:1.4.0") testImplementation("androidx.test:core:1.5.0")
testImplementation("io.strikt:strikt-core:${Versions.strikt}") testImplementation("io.strikt:strikt-core:0.34.1")
testImplementation("org.robolectric:robolectric:${Versions.robolectric}") testImplementation("org.robolectric:robolectric:4.9.2")
debugImplementation("io.sentry:sentry-android:6.17.0")
androidTestImplementation("io.mockk:mockk-android:${Versions.mockk}") androidTestImplementation("io.mockk:mockk-android:1.13.4")
androidTestImplementation("androidx.navigation:navigation-testing:$navVersion")
} }
project.afterEvaluate { project.afterEvaluate {
...@@ -241,13 +264,15 @@ project.afterEvaluate { ...@@ -241,13 +264,15 @@ project.afterEvaluate {
sourceDirectories.setFrom(files(listOf(mainSrc))) sourceDirectories.setFrom(files(listOf(mainSrc)))
classDirectories.setFrom(files(listOf(debugTree))) classDirectories.setFrom(files(listOf(debugTree)))
executionData.setFrom(fileTree(project.buildDir) { executionData.setFrom(
fileTree(project.buildDir) {
setIncludes( setIncludes(
listOf( listOf(
"outputs/unit_test_code_coverage/debugUnitTest/*.exec", "outputs/unit_test_code_coverage/debugUnitTest/*.exec",
"outputs/code_coverage/debugAndroidTest/connected/**/*.ec" "outputs/code_coverage/debugAndroidTest/connected/**/*.ec"
) )
) )
}) }
)
} }
} }
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="audio.funkwhale.ffa"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
...@@ -21,7 +22,8 @@ ...@@ -21,7 +22,8 @@
<activity <activity
android:name=".activities.SplashActivity" android:name=".activities.SplashActivity"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:noHistory="true"> android:noHistory="true"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
...@@ -35,23 +37,33 @@ ...@@ -35,23 +37,33 @@
<activity <activity
android:name=".activities.LoginActivity" android:name=".activities.LoginActivity"
android:configChanges="screenSize|orientation" android:configChanges="screenSize|orientation"
android:launchMode="singleInstance" /> android:launchMode="singleInstance"
android:screenOrientation="portrait" />
<activity android:name=".activities.MainActivity" /> <activity
android:name=".activities.MainActivity" />
<activity <activity
android:name=".activities.SearchActivity" android:name=".activities.DownloadsActivity"
android:launchMode="singleTop" /> android:screenOrientation="portrait" />
<activity android:name=".activities.DownloadsActivity" /> <activity
android:name=".activities.SettingsActivity"
android:screenOrientation="portrait" />
<activity android:name=".activities.SettingsActivity" /> <activity
android:name=".activities.LicencesActivity"
android:screenOrientation="portrait" />
<activity android:name=".activities.LicencesActivity" /> <activity
android:name="net.openid.appauth.AuthorizationManagementActivity"
android:launchMode="@integer/launch_mode_for_app_auth"
tools:replace="android:launchMode" />
<service <service
android:name=".playback.PlayerService" android:name=".playback.PlayerService"
android:foregroundServiceType="mediaPlayback"> android:foregroundServiceType="mediaPlayback"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
...@@ -70,12 +82,14 @@ ...@@ -70,12 +82,14 @@
</service> </service>
<receiver android:name="androidx.media.session.MediaButtonReceiver"> <receiver android:name="androidx.media.session.MediaButtonReceiver"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<meta-data android:name="io.sentry.dsn" android:value="https://4e377f47d01242baae2d9d8bd689c3ef@am.funkwhale.audio/4" />
</application> </application>
</manifest> </manifest>
...@@ -5,13 +5,13 @@ import android.content.Context ...@@ -5,13 +5,13 @@ import android.content.Context
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import audio.funkwhale.ffa.koin.authModule import audio.funkwhale.ffa.koin.authModule
import audio.funkwhale.ffa.koin.exoplayerModule import audio.funkwhale.ffa.koin.exoplayerModule
import audio.funkwhale.ffa.utils.* import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.FFACache
import com.preference.PowerPreference import com.preference.PowerPreference
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import java.util.Locale
class FFA : Application() { class FFA : Application() {
...@@ -23,11 +23,6 @@ class FFA : Application() { ...@@ -23,11 +23,6 @@ class FFA : Application() {
var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null
val eventBus: BroadcastChannel<Event> = BroadcastChannel(10)
val commandBus: BroadcastChannel<Command> = BroadcastChannel(10)
val requestBus: BroadcastChannel<Request> = BroadcastChannel(10)
val progressBus: BroadcastChannel<Triple<Int, Int, Int>> = ConflatedBroadcastChannel()
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
...@@ -78,7 +73,7 @@ class FFA : Application() { ...@@ -78,7 +73,7 @@ class FFA : Application() {
builder.appendLine(e.toString()) builder.appendLine(e.toString())
FFACache.set(this@FFA, "crashdump", builder.toString().toByteArray()) FFACache.set(this@FFA, "crashdump", builder.toString())
} }
} }
......
...@@ -14,7 +14,6 @@ import com.google.android.exoplayer2.offline.DownloadManager ...@@ -14,7 +14,6 @@ import com.google.android.exoplayer2.offline.DownloadManager
import kotlinx.coroutines.Dispatchers.Default import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.java.KoinJavaComponent.inject import org.koin.java.KoinJavaComponent.inject
...@@ -65,20 +64,19 @@ class DownloadsActivity : AppCompatActivity() { ...@@ -65,20 +64,19 @@ class DownloadsActivity : AppCompatActivity() {
private fun refresh() { private fun refresh() {
lifecycleScope.launch(Main) { lifecycleScope.launch(Main) {
val cursor = exoDownloadManager.downloadIndex.getDownloads()
adapter.downloads.clear() adapter.downloads.clear()
exoDownloadManager.downloadIndex.getDownloads()
.use { cursor ->
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val download = cursor.download val download = cursor.download
download.getMetadata()?.let { info -> download.getMetadata()?.let { info ->
adapter.downloads.add(info.apply { adapter.downloads.add(
this.download = download info.apply { this.download = download }
}) )
}
} }
} }
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
} }
...@@ -101,15 +99,17 @@ class DownloadsActivity : AppCompatActivity() { ...@@ -101,15 +99,17 @@ class DownloadsActivity : AppCompatActivity() {
} }
private suspend fun refreshProgress() { private suspend fun refreshProgress() {
val cursor = exoDownloadManager.downloadIndex.getDownloads() exoDownloadManager.downloadIndex.getDownloads()
.use { cursor ->
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val download = cursor.download val download = cursor.download
download.getMetadata()?.let { info -> download.getMetadata()?.let { info ->
adapter.downloads.withIndex().associate { it.value to it.index } adapter.downloads.withIndex().associate { it.value to it.index }
.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match -> .filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
if (download.state == Download.STATE_DOWNLOADING && download.percentDownloaded != info.download?.percentDownloaded ?: 0) { if (download.state == Download.STATE_DOWNLOADING &&
download.percentDownloaded != (info.download?.percentDownloaded ?: 0)
) {
withContext(Main) { withContext(Main) {
adapter.downloads[match.second] = info.apply { adapter.downloads[match.second] = info.apply {
this.download = download this.download = download
...@@ -122,6 +122,7 @@ class DownloadsActivity : AppCompatActivity() { ...@@ -122,6 +122,7 @@ class DownloadsActivity : AppCompatActivity() {
} }
} }
} }
}
inner class DownloadChangedListener : DownloadsAdapter.OnDownloadChangedListener { inner class DownloadChangedListener : DownloadsAdapter.OnDownloadChangedListener {
override fun onItemRemoved(index: Int) { override fun onItemRemoved(index: Int) {
......
...@@ -90,7 +90,8 @@ class LicencesActivity : AppCompatActivity() { ...@@ -90,7 +90,8 @@ class LicencesActivity : AppCompatActivity() {
holder.licence.text = item.licence holder.licence.text = item.licence
} }
inner class ViewHolder(binding: RowLicenceBinding) : RecyclerView.ViewHolder(binding.root), inner class ViewHolder(binding: RowLicenceBinding) :
RecyclerView.ViewHolder(binding.root),
View.OnClickListener { View.OnClickListener {
val name = binding.name val name = binding.name
val licence = binding.licence val licence = binding.licence
......
package audio.funkwhale.ffa.activities package audio.funkwhale.ffa.activities
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.doOnLayout import androidx.core.view.doOnLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.ActivityLoginBinding import audio.funkwhale.ffa.databinding.ActivityLoginBinding
import audio.funkwhale.ffa.fragments.LoginDialog import audio.funkwhale.ffa.fragments.LoginDialog
import audio.funkwhale.ffa.utils.* import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.FuelResult
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.Userinfo
import com.github.kittinunf.fuel.Fuel import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.github.kittinunf.fuel.gson.gsonDeserializerOf
...@@ -37,13 +43,10 @@ class LoginActivity : AppCompatActivity() { ...@@ -37,13 +43,10 @@ class LoginActivity : AppCompatActivity() {
limitContainerWidth() limitContainerWidth()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private var resultLauncher =
super.onActivityResult(requestCode, resultCode, data) registerForActivityResult(StartActivityForResult()) { result ->
result.data?.let {
data?.let { oAuth.exchange(this, it) {
when (requestCode) {
0 -> {
oAuth.exchange(this, data) {
PowerPreference PowerPreference
.getFileByName(AppContext.PREFS_CREDENTIALS) .getFileByName(AppContext.PREFS_CREDENTIALS)
.setBoolean("anonymous", false) .setBoolean("anonymous", false)
...@@ -59,14 +62,19 @@ class LoginActivity : AppCompatActivity() { ...@@ -59,14 +62,19 @@ class LoginActivity : AppCompatActivity() {
} }
} }
} }
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
with(binding) { with(binding) {
val preferences = getPreferences(Context.MODE_PRIVATE)
val hn = preferences?.getString("hostname", "")
if (hn != null && !hn.isEmpty()) {
hostname.text = Editable.Factory.getInstance().newEditable(hn)
}
cleartext.setChecked(preferences?.getBoolean("cleartext", false) ?: false)
anonymous.setChecked(preferences?.getBoolean("anonymous", false) ?: false)
login.setOnClickListener { login.setOnClickListener {
var hostname = hostname.text.toString().trim() var hostname = hostname.text.toString().trim().trim('/')
try { try {
validateHostname(hostname, cleartext.isChecked)?.let { validateHostname(hostname, cleartext.isChecked)?.let {
...@@ -97,6 +105,12 @@ class LoginActivity : AppCompatActivity() { ...@@ -97,6 +105,12 @@ class LoginActivity : AppCompatActivity() {
hostnameField.error = message hostnameField.error = message
} }
if (hostnameField.error == null) {
val preferences = getPreferences(Context.MODE_PRIVATE)
preferences?.edit()?.putString("hostname", hostname)?.commit()
preferences?.edit()?.putBoolean("cleartext", cleartext.isChecked)?.commit()
preferences?.edit()?.putBoolean("anonymous", anonymous.isChecked)?.commit()
}
} }
} }
} }
...@@ -131,7 +145,7 @@ class LoginActivity : AppCompatActivity() { ...@@ -131,7 +145,7 @@ class LoginActivity : AppCompatActivity() {
oAuth.init(hostname) oAuth.init(hostname)
return oAuth.register { return oAuth.register {
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).setString("hostname", hostname) PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).setString("hostname", hostname)
oAuth.authorize(this) resultLauncher.launch(oAuth.authorizeIntent(this))
} }
} }
......
package audio.funkwhale.ffa.activities package audio.funkwhale.ffa.activities
import android.content.* import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
...@@ -36,8 +40,6 @@ class SettingsActivity : AppCompatActivity() { ...@@ -36,8 +40,6 @@ class SettingsActivity : AppCompatActivity() {
) )
.commit() .commit()
} }
fun getThemeResId(): Int = R.style.AppTheme
} }
class SettingsFragment : class SettingsFragment :
...@@ -47,7 +49,7 @@ class SettingsFragment : ...@@ -47,7 +49,7 @@ class SettingsFragment :
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
} }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
...@@ -56,14 +58,14 @@ class SettingsFragment : ...@@ -56,14 +58,14 @@ class SettingsFragment :
updateValues() updateValues()
} }
override fun onPreferenceTreeClick(preference: Preference?): Boolean { override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference?.key) { when (preference.key) {
"oss_licences" -> startActivity(Intent(activity, LicencesActivity::class.java)) "oss_licences" -> startActivity(Intent(activity, LicencesActivity::class.java))
"crash" -> { "crash" -> {
activity?.let { activity -> activity?.let { activity ->
(activity.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.also { clip -> (activity.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.also { clip ->
FFACache.get(activity, "crashdump")?.readLines()?.joinToString("\n").also { FFACache.getLines(activity, "crashdump")?.joinToString("\n").also {
clip.setPrimaryClip(ClipData.newPlainText("Funkwhale logs", it)) clip.setPrimaryClip(ClipData.newPlainText("Funkwhale logs", it))
Toast.makeText( Toast.makeText(
...@@ -112,6 +114,14 @@ class SettingsFragment : ...@@ -112,6 +114,14 @@ class SettingsFragment :
} }
} }
preferenceManager.findPreference<ListPreference>("bandwidth_limitation")?.let {
it.summary = when (it.value) {
"unlimited" -> activity.getString(R.string.settings_bandwidth_limitation_summary_unlimited)
"limited" -> activity.getString(R.string.settings_bandwidth_limitation_summary_limited)
else -> activity.getString(R.string.settings_bandwidth_limitation_summary_unlimited)
}
}
preferenceManager.findPreference<ListPreference>("play_order")?.let { preferenceManager.findPreference<ListPreference>("play_order")?.let {
it.summary = when (it.value) { it.summary = when (it.value) {
"shuffle" -> activity.getString(R.string.settings_play_order_shuffle_summary) "shuffle" -> activity.getString(R.string.settings_play_order_shuffle_summary)
...@@ -146,7 +156,7 @@ class SettingsFragment : ...@@ -146,7 +156,7 @@ class SettingsFragment :
} }
preferenceManager.findPreference<SeekBarPreference>("media_cache_size")?.let { preferenceManager.findPreference<SeekBarPreference>("media_cache_size")?.let {
it.summary = getString(R.string.settings_media_cache_size_summary, it.value) it.summary = getString(R.string.settings_media_cache_size_summary, it.value as Int) // manual cast to address a bug in AGP
} }
preferenceManager.findPreference<Preference>("version")?.let { preferenceManager.findPreference<Preference>("version")?.let {
......
...@@ -6,7 +6,9 @@ import android.os.Bundle ...@@ -6,7 +6,9 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.FFA
import audio.funkwhale.ffa.utils.* import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.Settings
import org.koin.java.KoinJavaComponent.inject import org.koin.java.KoinJavaComponent.inject
class SplashActivity : AppCompatActivity() { class SplashActivity : AppCompatActivity() {
......
...@@ -8,9 +8,7 @@ import androidx.recyclerview.widget.RecyclerView ...@@ -8,9 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.databinding.RowAlbumBinding import audio.funkwhale.ffa.databinding.RowAlbumBinding
import audio.funkwhale.ffa.fragments.FFAAdapter import audio.funkwhale.ffa.fragments.FFAAdapter
import audio.funkwhale.ffa.model.Album import audio.funkwhale.ffa.model.Album
import audio.funkwhale.ffa.utils.maybeLoad import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
class AlbumsAdapter( class AlbumsAdapter(
...@@ -19,6 +17,10 @@ class AlbumsAdapter( ...@@ -19,6 +17,10 @@ class AlbumsAdapter(
private val listener: OnAlbumClickListener private val listener: OnAlbumClickListener
) : FFAAdapter<Album, AlbumsAdapter.ViewHolder>() { ) : FFAAdapter<Album, AlbumsAdapter.ViewHolder>() {
init {
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
interface OnAlbumClickListener { interface OnAlbumClickListener {
fun onClick(view: View?, album: Album) fun onClick(view: View?, album: Album)
} }
...@@ -41,8 +43,7 @@ class AlbumsAdapter( ...@@ -41,8 +43,7 @@ class AlbumsAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val album = data[position] val album = data[position]
Picasso.get() CoverArt.requestCreator(album.cover())
.maybeLoad(maybeNormalizeUrl(album.cover()))
.fit() .fit()
.transform(RoundedCornersTransformation(8, 0)) .transform(RoundedCornersTransformation(8, 0))
.into(holder.art) .into(holder.art)
......
...@@ -8,9 +8,8 @@ import audio.funkwhale.ffa.R ...@@ -8,9 +8,8 @@ import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.RowAlbumGridBinding import audio.funkwhale.ffa.databinding.RowAlbumGridBinding
import audio.funkwhale.ffa.fragments.FFAAdapter import audio.funkwhale.ffa.fragments.FFAAdapter
import audio.funkwhale.ffa.model.Album import audio.funkwhale.ffa.model.Album
import audio.funkwhale.ffa.utils.maybeLoad import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
class AlbumsGridAdapter( class AlbumsGridAdapter(
...@@ -40,10 +39,8 @@ class AlbumsGridAdapter( ...@@ -40,10 +39,8 @@ class AlbumsGridAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val album = data[position] val album = data[position]
Picasso.get() CoverArt.requestCreator(maybeNormalizeUrl(album.cover()))
.maybeLoad(maybeNormalizeUrl(album.cover()))
.fit() .fit()
.placeholder(R.drawable.cover)
.transform(RoundedCornersTransformation(16, 0)) .transform(RoundedCornersTransformation(16, 0))
.into(holder.cover) .into(holder.cover)
......
...@@ -9,9 +9,8 @@ import audio.funkwhale.ffa.R ...@@ -9,9 +9,8 @@ import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.RowArtistBinding import audio.funkwhale.ffa.databinding.RowArtistBinding
import audio.funkwhale.ffa.fragments.FFAAdapter import audio.funkwhale.ffa.fragments.FFAAdapter
import audio.funkwhale.ffa.model.Artist import audio.funkwhale.ffa.model.Artist
import audio.funkwhale.ffa.utils.maybeLoad import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
class ArtistsAdapter( class ArtistsAdapter(
...@@ -42,6 +41,8 @@ class ArtistsAdapter( ...@@ -42,6 +41,8 @@ class ArtistsAdapter(
super.onItemRangeInserted(positionStart, itemCount) super.onItemRangeInserted(positionStart, itemCount)
} }
}) })
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
} }
override fun getItemCount() = active.size override fun getItemCount() = active.size
...@@ -60,15 +61,12 @@ class ArtistsAdapter( ...@@ -60,15 +61,12 @@ class ArtistsAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val artist = active[position] val artist = active[position]
artist.albums?.let { albums -> artist.cover()?.let { coverUrl ->
if (albums.isNotEmpty()) { CoverArt.requestCreator(maybeNormalizeUrl(coverUrl))
Picasso.get()
.maybeLoad(maybeNormalizeUrl(albums[0].cover?.urls?.original))
.fit() .fit()
.transform(RoundedCornersTransformation(8, 0)) .transform(RoundedCornersTransformation(8, 0))
.into(holder.art) .into(holder.art)
} }
}
holder.name.text = artist.name holder.name.text = artist.name
......
package audio.funkwhale.ffa.adapters package audio.funkwhale.ffa.adapters
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.fragment.app.FragmentPagerAdapter
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.fragments.* import audio.funkwhale.ffa.fragments.AlbumsGridFragment
import audio.funkwhale.ffa.fragments.ArtistsFragment
import audio.funkwhale.ffa.fragments.FavoritesFragment
import audio.funkwhale.ffa.fragments.PlaylistsFragment
import audio.funkwhale.ffa.fragments.RadiosFragment
class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { class BrowseTabsAdapter(val context: Fragment) : FragmentStateAdapter(context) {
var tabs = mutableListOf<Fragment>() override fun getItemCount() = 5
override fun getCount() = 5 override fun createFragment(position: Int): Fragment = when (position) {
override fun getItem(position: Int): Fragment {
tabs.getOrNull(position)?.let {
return it
}
val fragment = when (position) {
0 -> ArtistsFragment() 0 -> ArtistsFragment()
1 -> AlbumsGridFragment() 1 -> AlbumsGridFragment()
2 -> PlaylistsFragment() 2 -> PlaylistsFragment()
...@@ -25,12 +21,7 @@ class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : Fragm ...@@ -25,12 +21,7 @@ class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : Fragm
else -> ArtistsFragment() else -> ArtistsFragment()
} }
tabs.add(position, fragment) fun tabText(position: Int): String {
return fragment
}
override fun getPageTitle(position: Int): String {
return when (position) { return when (position) {
0 -> context.getString(R.string.artists) 0 -> context.getString(R.string.artists)
1 -> context.getString(R.string.albums) 1 -> context.getString(R.string.albums)
......