mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-24 04:40:56 +03:00
Compare commits
No commits in common. "develop" and "3.2.0" have entirely different histories.
157
.circleci/config.yml
Normal file
157
.circleci/config.yml
Normal file
@ -0,0 +1,157 @@
|
||||
version: 2.1
|
||||
parameters:
|
||||
memory-config:
|
||||
type: string
|
||||
default: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g"
|
||||
memory-config-debug:
|
||||
type: string
|
||||
default: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g -verbose:gc -Xlog:gc*"
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cimg/android:2022.03.1
|
||||
working_directory: ~/ultrasonic
|
||||
environment:
|
||||
JVM_OPTS: << pipeline.parameters.memory-config >>
|
||||
JAVA_TOOL_OPTIONS: << pipeline.parameters.memory-config >>
|
||||
GRADLE_OPTS: << pipeline.parameters.memory-config >>
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||
- v2-ultrasonic-{{ .Branch }}
|
||||
- v2-ultrasonic
|
||||
- run:
|
||||
name: configure gradle.properties for CI building
|
||||
command: |
|
||||
sed -i '/^org.gradle.jvmargs/d' gradle.properties
|
||||
sed -i 's/^org.gradle.daemon=true/org.gradle.daemon=false/g' gradle.properties
|
||||
cat gradle.properties
|
||||
- run:
|
||||
name: checkstyle
|
||||
command: ./gradlew -Pqc ktlintCheck
|
||||
- run:
|
||||
name: static analysis
|
||||
command: ./gradlew -Pqc detekt
|
||||
- run:
|
||||
name: build debug
|
||||
command: ./gradlew assembleDebug
|
||||
- run:
|
||||
name: unit-tests
|
||||
command: |
|
||||
./gradlew ciTest testDebugUnitTest
|
||||
./gradlew jacocoFullReport
|
||||
- run:
|
||||
name: lint
|
||||
command: ./gradlew :ultrasonic:lintRelease
|
||||
- run:
|
||||
name: build
|
||||
command: ./gradlew buildRelease
|
||||
- run:
|
||||
name: assemble release
|
||||
command: ./gradlew assembleRelease
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.gradle
|
||||
key: v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||
- store_artifacts:
|
||||
path: ultrasonic/build/reports
|
||||
destination: reports
|
||||
- store_artifacts:
|
||||
path: subsonic-api/build/reports
|
||||
destination: reports
|
||||
- store_artifacts:
|
||||
path: build/reports/jacoco/jacocoFullReport/
|
||||
push_translations:
|
||||
docker:
|
||||
- image: cimg/python:3.6
|
||||
working_directory: ~/ultrasonic
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: install transifex client
|
||||
command: |
|
||||
python -m venv ~/venv
|
||||
. ~/venv/bin/activate
|
||||
pip install transifex-client
|
||||
- run:
|
||||
name: configure transifex client
|
||||
command: echo $'[https://www.transifex.com]\nhostname = https://www.transifex.com\nusername = api\npassword = '"${TRANSIFEX_PASSWORD}"$'\n' > ~/.transifexrc
|
||||
- run:
|
||||
name: push changes in translation files
|
||||
command: |
|
||||
. ~/venv/bin/activate
|
||||
tx push -s
|
||||
generate_signed_apk:
|
||||
docker:
|
||||
- image: cimg/android:2022.03.1
|
||||
working_directory: ~/ultrasonic
|
||||
environment:
|
||||
JVM_OPTS: << pipeline.parameters.memory-config >>
|
||||
JAVA_TOOL_OPTIONS: << pipeline.parameters.memory-config >>
|
||||
GRADLE_OPTS: << pipeline.parameters.memory-config >>
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||
- v2-ultrasonic-{{ .Branch }}
|
||||
- v2-ultrasonic
|
||||
- run:
|
||||
name: decrypt ultrasonic-keystore
|
||||
command: openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d
|
||||
- run:
|
||||
name: build release apk
|
||||
command: ./gradlew build assembleRelease
|
||||
- run:
|
||||
name: sign release apk
|
||||
command: |
|
||||
export PATH="${JAVA_HOME}/bin:${PATH}"
|
||||
mkdir -p /tmp/ultrasonic-release
|
||||
${ANDROID_HOME}/build-tools/32.0.0/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||
${ANDROID_HOME}/build-tools/32.0.0/apksigner sign --verbose --ks ~/ultrasonic/ultrasonic-keystore --ks-pass pass:${ULTRASONIC_KEYSTORE_STOREPASS} --key-pass pass:${ULTRASONIC_KEYSTORE_KEYPASS} /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||
${ANDROID_HOME}/build-tools/32.0.0/apksigner verify --verbose /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||
- persist_to_workspace:
|
||||
root: /tmp/ultrasonic-release
|
||||
paths:
|
||||
- ultrasonic-*.apk*
|
||||
publish_github_signed_apk:
|
||||
docker:
|
||||
- image: cimg/go:1.18
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/ultrasonic-release
|
||||
- run:
|
||||
name: install ghr
|
||||
command: go install -v github.com/tcnksm/ghr@latest
|
||||
- run:
|
||||
name: publish release on github tag
|
||||
command: ghr -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} ${CIRCLE_TAG} /tmp/ultrasonic-release
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_push_translations:
|
||||
jobs:
|
||||
- build
|
||||
- push_translations:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- generate_signed_apk:
|
||||
filters:
|
||||
tags:
|
||||
only: /^[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- publish_github_signed_apk:
|
||||
requires:
|
||||
- generate_signed_apk
|
||||
filters:
|
||||
tags:
|
||||
only: /^[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
@ -1,2 +0,0 @@
|
||||
[*.{kt,kts}]
|
||||
ktlint_code_style = android_studio
|
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gradle" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-patch"]
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,7 +18,6 @@ out/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
.kotlin/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
|
151
.gitlab-ci.yml
151
.gitlab-ci.yml
@ -1,151 +0,0 @@
|
||||
default:
|
||||
image: registry.gitlab.com/ultrasonic/ci-android:1.2.0
|
||||
cache: &global_cache
|
||||
key:
|
||||
files:
|
||||
- gradle/wrapper/gradle-wrapper.properties
|
||||
paths:
|
||||
- .gradle/
|
||||
|
||||
variables:
|
||||
CACHE_FALLBACK_KEY: develop-protected
|
||||
MEMORY_CONFIG: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g"
|
||||
MEMORY_CONFIG_DEBUG: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g -verbose:gc -Xlog:gc*"
|
||||
JVM_OPTS: ${MEMORY_CONFIG}
|
||||
JAVA_TOOL_OPTIONS: ${MEMORY_CONFIG}
|
||||
GRADLE_OPTS: ${MEMORY_CONFIG}
|
||||
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/Ultrasonic/${CI_COMMIT_TAG}"
|
||||
PACKAGE_APK: "ultrasonic-${CI_COMMIT_TAG}.apk"
|
||||
PACKAGE_APK_IDSIG: "ultrasonic-${CI_COMMIT_TAG}.apk.idsig"
|
||||
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
|
||||
# The project id of https://gitlab.com/ultrasonic/ultrasonic/
|
||||
ROOT_PROJECT_ID: 37671564
|
||||
|
||||
stages:
|
||||
- Check
|
||||
- Build
|
||||
- Sign APK
|
||||
- Publish
|
||||
- Release
|
||||
|
||||
Check Style:
|
||||
stage: Check
|
||||
script: ./gradlew -Pqc ktlintCheck
|
||||
cache:
|
||||
# inherit all global cache settings
|
||||
<<: *global_cache
|
||||
policy: pull
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_TAG || $CI_PROJECT_ID != $ROOT_PROJECT_ID
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
|
||||
Static Analysis:
|
||||
stage: Check
|
||||
script: ./gradlew -Pqc detekt
|
||||
cache:
|
||||
# inherit all global cache settings
|
||||
<<: *global_cache
|
||||
policy: pull
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_TAG || $CI_PROJECT_ID != $ROOT_PROJECT_ID
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
|
||||
Lint:
|
||||
stage: Check
|
||||
script: ./gradlew :ultrasonic:lintRelease
|
||||
cache:
|
||||
# inherit all global cache settings
|
||||
<<: *global_cache
|
||||
policy: pull-push
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_TAG || $CI_PROJECT_ID != $ROOT_PROJECT_ID
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
|
||||
Unit Tests:
|
||||
stage: Check
|
||||
script: ./gradlew ciTest testDebugUnitTest
|
||||
cache:
|
||||
# inherit all global cache settings
|
||||
<<: *global_cache
|
||||
policy: pull-push
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_TAG || $CI_PROJECT_ID != $ROOT_PROJECT_ID
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
|
||||
Assemble Release:
|
||||
stage: Build
|
||||
script:
|
||||
- sed -i 's/applicationId \"org.moire.ultrasonic\"/applicationId "org.moire.ultrasonic.gitlab"/' ultrasonic/build.gradle
|
||||
- ./gradlew assembleRelease
|
||||
artifacts:
|
||||
name: ultrasonic-release-unsigned-${CI_COMMIT_SHA}
|
||||
paths:
|
||||
- ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_TAG || $CI_PROJECT_ID != $ROOT_PROJECT_ID
|
||||
|
||||
# We generate a signed package for each commit to develop as well as when making a release.
|
||||
# Since the develop signed apk are not persistent they can be downloaded for around 3 weeks before Gitlab deletes them.
|
||||
Generate Signed APK:
|
||||
stage: Sign APK
|
||||
# We don't need the gradle cache here
|
||||
cache: []
|
||||
script:
|
||||
- openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d
|
||||
- mkdir -p ${CI_PROJECT_DIR}/ultrasonic-release
|
||||
- ${ANDROID_HOME}/build-tools/*/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk ${CI_PROJECT_DIR}/ultrasonic-release/${PACKAGE_APK}
|
||||
- ${ANDROID_HOME}/build-tools/*/apksigner sign --verbose --ks ${CI_PROJECT_DIR}/ultrasonic-keystore --ks-pass pass:${ULTRASONIC_KEYSTORE_STOREPASS} --key-pass pass:${ULTRASONIC_KEYSTORE_KEYPASS} ${CI_PROJECT_DIR}/ultrasonic-release/${PACKAGE_APK}
|
||||
- ${ANDROID_HOME}/build-tools/*/apksigner verify --verbose ${CI_PROJECT_DIR}/ultrasonic-release/${PACKAGE_APK}
|
||||
artifacts:
|
||||
name: $PACKAGE_APK
|
||||
paths:
|
||||
- ultrasonic-release/
|
||||
rules:
|
||||
# Run when releasing a new tag
|
||||
- if: $CI_COMMIT_TAG && $CI_PROJECT_ID == $ROOT_PROJECT_ID
|
||||
# Or when adding a new commit to develop (but never inside merge events)
|
||||
- if: $CI_COMMIT_REF_NAME == "develop" && $CI_PROJECT_ID == $ROOT_PROJECT_ID && $CI_PIPELINE_SOURCE != "merge_request_event"
|
||||
variables:
|
||||
PACKAGE_APK: ultrasonic-${CI_COMMIT_SHA}.apk
|
||||
|
||||
|
||||
Publish Signed APK:
|
||||
stage: Publish
|
||||
image: curlimages/curl:latest
|
||||
script:
|
||||
- |
|
||||
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ultrasonic-release/${PACKAGE_APK} "${PACKAGE_REGISTRY_URL}/${PACKAGE_APK}"
|
||||
- |
|
||||
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ultrasonic-release/${PACKAGE_APK_IDSIG} "${PACKAGE_REGISTRY_URL}/${PACKAGE_APK_IDSIG}"
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG && $CI_PROJECT_ID == $ROOT_PROJECT_ID
|
||||
|
||||
Release:
|
||||
stage: Release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
script: |
|
||||
release-cli create --name "Release ${CI_COMMIT_TAG}" --tag-name ${CI_COMMIT_TAG} \
|
||||
--assets-link "{\"name\":\"${PACKAGE_APK}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${PACKAGE_APK}\"}" \
|
||||
--assets-link "{\"name\":\"${PACKAGE_APK_IDSIG}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${PACKAGE_APK_IDSIG}\"}"
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG && $CI_PROJECT_ID == $ROOT_PROJECT_ID
|
||||
|
||||
RoboTest:
|
||||
stage: Release
|
||||
image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest
|
||||
# We don't need the gradle cache here
|
||||
cache: []
|
||||
script:
|
||||
- curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash
|
||||
- gcloud auth activate-service-account --key-file .secure_files/firebase-key.json
|
||||
- gcloud firebase test android run --project ultrasonic-61089 --type robo --app ultrasonic-release/${PACKAGE_APK} --robo-directives click:button1= --device model=Nexus6,version=21,locale=en,orientation=portrait --device model=Pixel3,version=28,locale=fr,orientation=landscape
|
||||
rules:
|
||||
# Run when releasing a new tag
|
||||
- if: $CI_COMMIT_TAG && $CI_PROJECT_ID == $ROOT_PROJECT_ID
|
||||
# or when requested by using [ROBO] inside the commit message and merging to develop
|
||||
# Would be nice to be able to run it in a MR as well, but currently not possible
|
||||
# Because it would not have access to the protected keys.
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^\[ROBO\].*/ && $CI_PROJECT_ID == $ROOT_PROJECT_ID && $CI_COMMIT_REF_NAME == "develop" && $CI_PIPELINE_SOURCE != "merge_request_event"
|
||||
variables:
|
||||
PACKAGE_APK: ultrasonic-${CI_COMMIT_SHA}.apk
|
||||
|
@ -1,32 +0,0 @@
|
||||
## Motivation
|
||||
|
||||
Describe here what motivated you to make this enhancement request. What is
|
||||
currently happening and what would you like to improve. If you have a stack
|
||||
trace or any logs, please format them using Markdown triple backquote
|
||||
notation.
|
||||
|
||||
### Proposal
|
||||
|
||||
Tell us in detail what you propose. How you want to achieve what you would
|
||||
like to improve.
|
||||
|
||||
## System information
|
||||
|
||||
Include this section only if you consider that is relevant to this ticket,
|
||||
otherwise you may remove it.
|
||||
|
||||
### Ultrasonic client
|
||||
|
||||
* **Ultrasonic version**: *version of the app*
|
||||
* **Android version**: *Version of Android OS on the device*
|
||||
* **Device info**: *Device manufacturer, model*
|
||||
|
||||
### Server
|
||||
|
||||
* **Server name**: *Airsonic, Ampache, Supysonic...*
|
||||
* **Server version**: *version of server software*
|
||||
* **Protocol used**: *http or https (self certificate, letsencrypt...)*
|
||||
|
||||
## Additional notes
|
||||
|
||||
Include any extra notes here. Otherwise you may remove this section.
|
@ -1,27 +0,0 @@
|
||||
## Proposal
|
||||
|
||||
Describe your proposed feature request here. Use this template only to
|
||||
request a new feature that does not exist in Ultrasonic. If you are
|
||||
requesting an enhancement to a current feature, please use the enhancement
|
||||
template.
|
||||
|
||||
## System information
|
||||
|
||||
Include this section only if you consider that is relevant to this ticket,
|
||||
otherwise you may remove it.
|
||||
|
||||
### Ultrasonic client
|
||||
|
||||
* **Ultrasonic version**: *version of the app*
|
||||
* **Android version**: *Version of Android OS on the device*
|
||||
* **Device info**: *Device manufacturer, model*
|
||||
|
||||
### Server
|
||||
|
||||
* **Server name**: *Airsonic, Ampache, Supysonic...*
|
||||
* **Server version**: *version of server software*
|
||||
* **Protocol used**: *http or https (self certificate, letsencrypt...)*
|
||||
|
||||
## Additional notes
|
||||
|
||||
Include any extra notes here. Otherwise you may remove this section.
|
@ -1,12 +0,0 @@
|
||||
<!-- Please describe your changes here -->
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
- [ ] I have opened an issue where I discuss the change I wish to make.
|
||||
- [ ] I ran `./gradlew -Pqc ktlintCheck`, `./gradlew -Pqc detekt` and
|
||||
`./gradlew :ultrasonic:lintRelease` and no problems found. See
|
||||
[CONTRIBUTING](CONTRIBUTING.md) for further information.
|
||||
- [ ] All commits [are
|
||||
signed](https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/).
|
||||
- [ ] I agree to release my code and all other changes of this MR under the
|
||||
[GNU GPLv3](LICENSE) license.
|
@ -1,10 +0,0 @@
|
||||
#### Before merge:
|
||||
- [ ] MR is targetting the master branch
|
||||
- [ ] **Squash commits must be disabled!**
|
||||
- [ ] RoboTests (5 physical, 10 virtual) on a Release apk return no errors
|
||||
- [ ] Release notes present
|
||||
|
||||
#### After merge
|
||||
- [ ] ``git fetch``
|
||||
- [ ] Create an annotated and signed tag: ``git tag -sa``
|
||||
- [ ] Push the tag to git:``git push --tags``
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base"],
|
||||
"baseBranches": [ "develop" ],
|
||||
"labels": [ "housekeeping", "dependencies" ]
|
||||
}
|
5
.idea/codeStyles/Project.xml
generated
5
.idea/codeStyles/Project.xml
generated
@ -1,6 +1,11 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value />
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
2
.idea/inspectionProfiles/Project_Default.xml
generated
2
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,7 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Reformat" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||
<inspection_tool class="Reformat" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="processChangedFilesOnly" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
|
4
.idea/misc.xml
generated
Normal file
4
.idea/misc.xml
generated
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||
</project>
|
10
.tx/config
Normal file
10
.tx/config
Normal file
@ -0,0 +1,10 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = nl_NL:nl,pl_PL:pl,fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,tr_TR:tr,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs,sr:sr,he:iw,id:in,lt_LT:lt,km_KH:km-rKH,th_TH:th,ru_RU:ru,it_IT:it
|
||||
|
||||
[ultrasonic.app]
|
||||
file_filter = ultrasonic/src/main/res/values-<lang>/strings.xml
|
||||
source_file = ultrasonic/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
|
@ -1,83 +1,35 @@
|
||||
# Contributing
|
||||
|
||||
Ultrasonic development is a community project, and contributions are
|
||||
welcomed.
|
||||
Ultrasonic development is a community project, and contributions are welcomed.
|
||||
|
||||
First, see if your issue haven’t been yet reported
|
||||
[here](https://gitlab.com/ultrasonic/ultrasonic/issues), then, please, first
|
||||
discuss the change you wish to make via [a new
|
||||
issue](https://gitlab.com/ultrasonic/ultrasonic/issues/new).
|
||||
First, see if your issue haven’t been yet reported [here](https://github.com/ultrasonic/ultrasonic/issues),
|
||||
then, please, first discuss the change you wish to make via [a new issue](https://github.com/ultrasonic/ultrasonic/issues/new).
|
||||
|
||||
## Contributing Translations
|
||||
|
||||
Interested in help to translate Ultrasonic? You can contribute in our
|
||||
[Weblate team](https://hosted.weblate.org/projects/ultrasonic/).
|
||||
[Transifex team](https://www.transifex.com/ultrasonic/ultrasonic/).
|
||||
|
||||
## Contributing Code
|
||||
|
||||
By default, merge requests should be opened against the **develop** branch
|
||||
from your own branch, MR against the **master** branch should only be used
|
||||
for critical bug fixes.
|
||||
By default Pull Request should be opened against **develop** branch, PR against **master** branch should be used only
|
||||
for critical bugfixes.
|
||||
|
||||
### Here are a few guidelines you should follow before submitting:
|
||||
|
||||
1. **License Acceptance:** All contributions must be licensed as [GNU
|
||||
GPLv3](LICENSE) to be accepted. Use `git commit --signoff` to acknowledge
|
||||
this.
|
||||
2. **No Breakage**: New features or changes to existing ones must not
|
||||
degrade the user experience.
|
||||
3. **Coding standards**: best-practices should be followed, comment
|
||||
generously, and avoid "clever" algorithms. Refactoring existing messes is
|
||||
great, but watch out for breakage.
|
||||
4. **No large PR**: Try to limit the scope of PR only to the related issue,
|
||||
so it will be easier to review and test.
|
||||
5. **Make your own branch**: When you send us a merge request, please do it
|
||||
from your own branch. Avoid using the `develop` branch.
|
||||
1. **License Acceptance:** All contributions must be licensed as [GNU GPLv3](LICENSE) to be accepted.
|
||||
Use `git commit --signoff` to acknowledge this.
|
||||
2. **App is migrating to [Kotlin](https://kotlinlang.org/) programming language:** new Pull Requests
|
||||
should be written in this programming language.
|
||||
3. **No Breakage:** New features or changes to existing ones must not degrade the user experience.
|
||||
4. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms.
|
||||
Refactoring existing messes is great, but watch out for breakage.
|
||||
5. **No large PR:** Try to limit the scope of PR only to the related issue, so it will be easier to review
|
||||
and test.
|
||||
|
||||
### Merge request process
|
||||
### Pull Request Process
|
||||
|
||||
On each merge request GitLab runs a number of checks to make sure there are
|
||||
no problems.
|
||||
|
||||
Take special note of point five of the previous paragraph. Do not use the
|
||||
`develop` branch, make your own. Not following this step will cause your
|
||||
merge request to be rejected without even checking it.
|
||||
|
||||
#### Signed commits
|
||||
|
||||
Commits must be signed. [See here how to set it
|
||||
up](https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/).
|
||||
|
||||
#### KtLint
|
||||
|
||||
This programm checks if the source code is formatted correctly. You can run
|
||||
it yourself locally with
|
||||
```
|
||||
./gradlew -Pqc ktlintFormat
|
||||
```
|
||||
Running this command will fix common problems and will notify you of
|
||||
problems it couldn't fix automatically.
|
||||
|
||||
#### Detekt
|
||||
|
||||
Detekt is a static analyser. It helps to find potential bugs in our code.
|
||||
You can run it yourself locally with
|
||||
```
|
||||
./gradlew -Pqc detekt
|
||||
```
|
||||
There is a "baseline" file, in which errors which have been in the code base
|
||||
before are noted. Sometimes it is necessary to regenerate this file by
|
||||
running:
|
||||
```
|
||||
./gradlew -Pqc detektBaseline
|
||||
```
|
||||
|
||||
#### Lint
|
||||
|
||||
Lint looks for general problems in the code or unused resources etc. You can
|
||||
run it with
|
||||
```
|
||||
./gradlew -Pqc lintRelease
|
||||
```
|
||||
If there is a need to regenerate the baseline, remove
|
||||
`ultrasonic/lint-baseline.xml` and rerun the command.
|
||||
1. Ensure [all commits are signed-off](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/about-commit-signature-verification).
|
||||
2. Check tests for the new code are added.
|
||||
3. Check code style is passing.
|
||||
4. Check code static analysis is passing.
|
||||
|
@ -2,20 +2,13 @@
|
||||
|
||||
Describe your problem here. Describe what you want to happen, and what
|
||||
happens if you try to do it. If you have a stack trace or any logs, please
|
||||
format them using Markdown triple backquote notation.
|
||||
format them using GitHub triple backquote notation.
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
Describe how somebody else could observe the same behavior you do. Don't
|
||||
share here any logins and passwords!
|
||||
|
||||
### Please provide a log!
|
||||
|
||||
Many issues are hard to reproduce without a log file. Please enable logging by opening the setting and scrolling to the bottom.
|
||||
There you will find an option to activate loging and to export the file. Please note that the log might contain personal information,
|
||||
such as the name of the tracks you listened to. If you don't want to post it publicly, let us know, and we will find a way.
|
||||
|
||||
|
||||
## System information
|
||||
|
||||
### Ultrasonic client
|
54
README.md
54
README.md
@ -1,14 +1,14 @@
|
||||
# Ultrasonic
|
||||
[](https://circleci.com/gh/ultrasonic)
|
||||
[]()
|
||||
[](https://ktlint.github.io/)
|
||||
|
||||
Ultrasonic is free and open-source music streaming Android client for
|
||||
[Subsonic][subsonic] [API][subapi] (version 1.7.0 or higher) compatible
|
||||
servers.
|
||||
Ultrasonic is free and open-source music streaming Android client for [Subsonic](http://www.subsonic.org/) [API](http://www.subsonic.org/pages/api.jsp) (version 1.7.0 or higher) compatible servers.
|
||||
|
||||
## Help wanted
|
||||
|
||||
We currently don't have that much time to spend developing Subsonic, so any
|
||||
contributions or active developers are always welcomed.
|
||||
Have a look at [CONTRIBUTING](CONTRIBUTING.md) to get started.
|
||||
|
||||
## Download
|
||||
|
||||
@ -16,20 +16,24 @@ App is available to download at following stores:
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="70">](https://play.google.com/store/apps/details?id=org.moire.ultrasonic)
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="70">](https://f-droid.org/packages/org.moire.ultrasonic/)
|
||||
[<img src="https://ultrasonic.gitlab.io/assets/img/get-it-on-gitlab.png" alt="Get it on GitLab" height="70">](https://gitlab.com/ultrasonic/ultrasonic/-/releases)
|
||||
[<img src="https://ultrasonic.github.io/assets/img/get-it-on-github.png" alt="Get it on GitHub" height="70">](https://github.com/ultrasonic/ultrasonic/releases)
|
||||
|
||||
**Warning**: All three versions (Google Play, F-Droid and the APKs) are not
|
||||
compatible (not signed by the same key)! You must uninstall one to install
|
||||
the other, which will delete all your data.
|
||||
the other, which will delete all your data.
|
||||
|
||||
If you want to use the version downloaded from F-Droid or from GitLab with
|
||||
**Android Auto**, you must enable Unknown Sources as it is described in
|
||||
[this wiki page][wikiaa].
|
||||
If you want to use the version downloaded from F-Droid or form Github with **Android Auto**, you must enable Unknown Sources as it is described in [this wiki page](https://github.com/ultrasonic/ultrasonic/wiki/Using-Ultrasonic-with-Android-Auto).
|
||||
|
||||
## Bugs and issues
|
||||
|
||||
First, see if your issue haven’t been yet reported [here][issues], otherwise
|
||||
open [a new issue][newissue].
|
||||
First, see if your issue haven’t been yet reported [here](https://github.com/ultrasonic/ultrasonic/issues),
|
||||
otherwise open [a new issue](https://github.com/ultrasonic/ultrasonic/issues/new).
|
||||
|
||||
### Known (not our) bugs
|
||||
|
||||
If you are using *Madsonic 5.1.X* several sections of Ultrasonic will not
|
||||
work. This is caused by bad implementation of Subsonic API by Madsonic. For
|
||||
more info about this you can read [this bug](https://github.com/ultrasonic/ultrasonic/issues/129).
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -37,28 +41,16 @@ See [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
## Supported (tested) Subsonic API implementations
|
||||
|
||||
- [Subsonic][subsonic]
|
||||
- [Airsonic-Advanced][airsonic]
|
||||
- [Supysonic][supysonic]
|
||||
- [Ampache][ampache]
|
||||
- [Subsonic](http://www.subsonic.org/pages/index.jsp)
|
||||
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
|
||||
- [Supysonic](https://github.com/spl0k/supysonic)
|
||||
- [Ampache](https://ampache.org/)
|
||||
|
||||
Other *Subsonic API* implementations should work as well as long as they
|
||||
follow API [documentation][subapi].
|
||||
Other *Subsonic API* implementations should work as well as long as they follow API
|
||||
[documentation](http://www.subsonic.org/pages/api.jsp).
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under the terms of the GNU General Public License
|
||||
version 3 (GPLv3).
|
||||
This software is licensed under the terms of the GNU General Public License version 3 (GPLv3).
|
||||
|
||||
Full text of the license is available in the [LICENSE](LICENSE) file and
|
||||
[online][gpl3].
|
||||
|
||||
[wikiaa]: https://gitlab.com/ultrasonic/ultrasonic/-/wikis/Using-Ultrasonic-with-Android-Auto
|
||||
[issues]: https://gitlab.com/ultrasonic/ultrasonic/-/issues
|
||||
[newissue]: https://gitlab.com/ultrasonic/ultrasonic/-/issues/new
|
||||
[subsonic]: http://www.subsonic.org/
|
||||
[subapi]: http://www.subsonic.org/pages/api.jsp
|
||||
[airsonic]: https://github.com/airsonic-advanced/airsonic-advanced
|
||||
[supysonic]: https://github.com/spl0k/supysonic
|
||||
[ampache]: https://ampache.org/
|
||||
[gpl3]: https://opensource.org/licenses/gpl-3.0.html
|
||||
Full text of the license is available in the [LICENSE](LICENSE) file and [online](https://opensource.org/licenses/gpl-3.0.html).
|
||||
|
28
build.gradle
28
build.gradle
@ -1,5 +1,3 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
apply from: 'gradle/versions.gradle'
|
||||
@ -12,15 +10,14 @@ buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven { url = "https://plugins.gradle.org/m2/" }
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath libs.gradle
|
||||
classpath libs.kotlin
|
||||
classpath libs.ktlintGradle
|
||||
classpath libs.detekt
|
||||
classpath libs.navigationSafeArgs
|
||||
classpath libs.jacoco
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,32 +26,27 @@ allprojects {
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
// Set Kotlin JVM target to the same for all subprojects
|
||||
tasks.withType(KotlinCompile).configureEach {
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "21"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).tap {
|
||||
configureEach {
|
||||
options.compilerArgs.add("-Xlint:deprecation")
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'gradle_scripts/jacoco.gradle'
|
||||
|
||||
wrapper {
|
||||
gradleVersion = libs.versions.gradle.get()
|
||||
distributionType = "all"
|
||||
}
|
||||
gradleVersion(libs.versions.gradle.get())
|
||||
distributionType("all")
|
||||
}
|
||||
|
@ -1,20 +1,14 @@
|
||||
plugins {
|
||||
alias libs.plugins.ksp
|
||||
}
|
||||
|
||||
apply from: bootstrap.androidModule
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
ext {
|
||||
jacocoExclude = [
|
||||
'**/domain/**'
|
||||
]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.core
|
||||
implementation libs.roomRuntime
|
||||
implementation libs.roomKtx
|
||||
ksp libs.room
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = 'org.moire.ultrasonic.subsonic.domain'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
}
|
||||
kapt libs.room
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
<?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="org.moire.ultrasonic.subsonic.domain">
|
||||
</manifest>
|
@ -1,21 +1,10 @@
|
||||
/*
|
||||
* Album.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.Date
|
||||
|
||||
@Entity(tableName = "albums", primaryKeys = ["id", "serverId"])
|
||||
data class Album(
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
@PrimaryKey override var id: String,
|
||||
override var parent: String? = null,
|
||||
override var album: String? = null,
|
||||
override var title: String? = null,
|
||||
@ -31,7 +20,7 @@ data class Album(
|
||||
override var genre: String? = null,
|
||||
override var starred: Boolean = false,
|
||||
override var path: String? = null,
|
||||
override var closeness: Int = 0
|
||||
override var closeness: Int = 0,
|
||||
) : MusicDirectory.Child() {
|
||||
override var isDirectory = true
|
||||
override var isVideo = false
|
||||
|
@ -1,23 +1,14 @@
|
||||
/*
|
||||
* Artist.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "artists", primaryKeys = ["id", "serverId"])
|
||||
@Entity(tableName = "artists")
|
||||
data class Artist(
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
@PrimaryKey override var id: String,
|
||||
override var name: String? = null,
|
||||
override var index: String? = null,
|
||||
override var coverArt: String? = null,
|
||||
override var albumCount: Long? = null,
|
||||
override var closeness: Int = 0
|
||||
) : ArtistOrIndex(id, serverId)
|
||||
) : ArtistOrIndex(id)
|
||||
|
@ -1,21 +1,11 @@
|
||||
/*
|
||||
* ArtistOrIndex.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Ignore
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
abstract class ArtistOrIndex(
|
||||
@Ignore
|
||||
override var id: String,
|
||||
@Ignore
|
||||
open var serverId: Int,
|
||||
@Ignore
|
||||
override var name: String? = null,
|
||||
@Ignore
|
||||
open var index: String? = null,
|
||||
@ -28,15 +18,15 @@ abstract class ArtistOrIndex(
|
||||
) : GenericEntry() {
|
||||
|
||||
fun compareTo(other: ArtistOrIndex): Int {
|
||||
return when {
|
||||
when {
|
||||
this.closeness == other.closeness -> {
|
||||
0
|
||||
return 0
|
||||
}
|
||||
this.closeness > other.closeness -> {
|
||||
-1
|
||||
return -1
|
||||
}
|
||||
else -> {
|
||||
1
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,15 @@
|
||||
/*
|
||||
* Index.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "indexes", primaryKeys = ["id", "serverId"])
|
||||
@Entity(tableName = "indexes")
|
||||
data class Index(
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
@PrimaryKey override var id: String,
|
||||
override var name: String? = null,
|
||||
override var index: String? = null,
|
||||
override var coverArt: String? = null,
|
||||
override var albumCount: Long? = null,
|
||||
override var closeness: Int = 0,
|
||||
var musicFolderId: String? = null
|
||||
) : ArtistOrIndex(id, serverId)
|
||||
) : ArtistOrIndex(id)
|
||||
|
@ -1,10 +1,3 @@
|
||||
/*
|
||||
* MusicDirectory.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import java.util.Date
|
||||
@ -13,7 +6,10 @@ class MusicDirectory : ArrayList<MusicDirectory.Child>() {
|
||||
var name: String? = null
|
||||
|
||||
@JvmOverloads
|
||||
fun getChildren(includeDirs: Boolean = true, includeFiles: Boolean = true): List<Child> {
|
||||
fun getChildren(
|
||||
includeDirs: Boolean = true,
|
||||
includeFiles: Boolean = true
|
||||
): List<Child> {
|
||||
if (includeDirs && includeFiles) {
|
||||
return toList()
|
||||
}
|
||||
@ -35,7 +31,6 @@ class MusicDirectory : ArrayList<MusicDirectory.Child>() {
|
||||
|
||||
abstract class Child : GenericEntry() {
|
||||
abstract override var id: String
|
||||
abstract var serverId: Int
|
||||
abstract var parent: String?
|
||||
abstract var isDirectory: Boolean
|
||||
abstract var album: String?
|
||||
|
@ -1,22 +1,13 @@
|
||||
/*
|
||||
* MusicFolder.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/**
|
||||
* Represents a top level directory in which music or other media is stored.
|
||||
*/
|
||||
@Entity(tableName = "music_folders", primaryKeys = ["id", "serverId"])
|
||||
@Entity(tableName = "music_folders")
|
||||
data class MusicFolder(
|
||||
override val id: String,
|
||||
override val name: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
var serverId: Int
|
||||
@PrimaryKey override val id: String,
|
||||
override val name: String
|
||||
) : GenericEntry()
|
||||
|
@ -0,0 +1,12 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
enum class PlayerState {
|
||||
IDLE,
|
||||
DOWNLOADING,
|
||||
PREPARING,
|
||||
PREPARED,
|
||||
STARTED,
|
||||
STOPPED,
|
||||
PAUSED,
|
||||
COMPLETED
|
||||
}
|
@ -14,8 +14,4 @@ data class Playlist @JvmOverloads constructor(
|
||||
companion object {
|
||||
private const val serialVersionUID = -4160515427075433798L
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,4 @@ data class PodcastsChannel(
|
||||
companion object {
|
||||
private const val serialVersionUID = -4160515427075433798L
|
||||
}
|
||||
|
||||
override fun toString(): String = title.toString()
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
enum class RepeatMode {
|
||||
OFF {
|
||||
override operator fun next(): RepeatMode = ALL
|
||||
},
|
||||
ALL {
|
||||
override operator fun next(): RepeatMode = SINGLE
|
||||
},
|
||||
SINGLE {
|
||||
override operator fun next(): RepeatMode = OFF
|
||||
};
|
||||
|
||||
abstract operator fun next(): RepeatMode
|
||||
}
|
@ -1,22 +1,13 @@
|
||||
/*
|
||||
* Track.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
import java.util.Date
|
||||
|
||||
@Entity(tableName = "tracks", primaryKeys = ["id", "serverId"])
|
||||
@Entity
|
||||
data class Track(
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
@PrimaryKey override var id: String,
|
||||
override var parent: String? = null,
|
||||
override var isDirectory: Boolean = false,
|
||||
override var title: String? = null,
|
||||
@ -57,15 +48,15 @@ data class Track(
|
||||
}
|
||||
|
||||
fun compareTo(other: Track): Int {
|
||||
return when {
|
||||
when {
|
||||
this.closeness == other.closeness -> {
|
||||
0
|
||||
return 0
|
||||
}
|
||||
this.closeness > other.closeness -> {
|
||||
-1
|
||||
return -1
|
||||
}
|
||||
else -> {
|
||||
1
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,5 @@
|
||||
plugins {
|
||||
alias libs.plugins.ksp
|
||||
}
|
||||
|
||||
apply from: bootstrap.kotlinModule
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api libs.retrofit
|
||||
api libs.jacksonConverter
|
||||
@ -19,11 +10,21 @@ dependencies {
|
||||
}
|
||||
implementation libs.kotlinReflect // for jackson kotlin, but to use the same version
|
||||
implementation libs.okhttpLogging
|
||||
implementation libs.timber
|
||||
|
||||
testImplementation libs.kotlinJunit
|
||||
testImplementation libs.mockito
|
||||
testImplementation libs.mockitoInline
|
||||
testImplementation libs.mockitoKotlin
|
||||
testImplementation libs.kluent
|
||||
testImplementation libs.mockWebServer
|
||||
testImplementation libs.apacheCodecs
|
||||
}
|
||||
|
||||
ext {
|
||||
// Excluding data classes
|
||||
jacocoExclude = [
|
||||
'**/models/**',
|
||||
'**/di/**'
|
||||
]
|
||||
}
|
||||
|
@ -8,8 +8,7 @@ import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
|
||||
* Base class for integration tests for [SubsonicAPIClient] class.
|
||||
*/
|
||||
abstract class SubsonicAPIClientTest {
|
||||
@JvmField @Rule
|
||||
val mockWebServerRule = MockWebServerRule()
|
||||
@JvmField @Rule val mockWebServerRule = MockWebServerRule()
|
||||
|
||||
protected lateinit var config: SubsonicClientConfiguration
|
||||
protected lateinit var client: SubsonicAPIClient
|
||||
|
@ -11,8 +11,7 @@ import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
|
||||
* Base class for testing [okhttp3.Interceptor] implementations.
|
||||
*/
|
||||
abstract class BaseInterceptorTest {
|
||||
@Rule @JvmField
|
||||
val mockWebServerRule = MockWebServerRule()
|
||||
@Rule @JvmField val mockWebServerRule = MockWebServerRule()
|
||||
|
||||
lateinit var client: OkHttpClient
|
||||
|
||||
|
@ -1,10 +1,3 @@
|
||||
/*
|
||||
* ApiVersionCheckWrapper.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
@ -92,13 +85,7 @@ internal class ApiVersionCheckWrapper(
|
||||
checkVersion(V1_4_0)
|
||||
checkParamVersion(musicFolderId, V1_12_0)
|
||||
return api.search2(
|
||||
query,
|
||||
artistCount,
|
||||
artistOffset,
|
||||
albumCount,
|
||||
albumOffset,
|
||||
songCount,
|
||||
musicFolderId
|
||||
query, artistCount, artistOffset, albumCount, albumOffset, songCount, musicFolderId
|
||||
)
|
||||
}
|
||||
|
||||
@ -114,13 +101,7 @@ internal class ApiVersionCheckWrapper(
|
||||
checkVersion(V1_8_0)
|
||||
checkParamVersion(musicFolderId, V1_12_0)
|
||||
return api.search3(
|
||||
query,
|
||||
artistCount,
|
||||
artistOffset,
|
||||
albumCount,
|
||||
albumOffset,
|
||||
songCount,
|
||||
musicFolderId
|
||||
query, artistCount, artistOffset, albumCount, albumOffset, songCount, musicFolderId
|
||||
)
|
||||
}
|
||||
|
||||
@ -240,13 +221,7 @@ internal class ApiVersionCheckWrapper(
|
||||
checkParamVersion(estimateContentLength, V1_8_0)
|
||||
checkParamVersion(converted, V1_14_0)
|
||||
return api.stream(
|
||||
id,
|
||||
maxBitRate,
|
||||
format,
|
||||
timeOffset,
|
||||
videoSize,
|
||||
estimateContentLength,
|
||||
converted
|
||||
id, maxBitRate, format, timeOffset, videoSize, estimateContentLength, converted
|
||||
)
|
||||
}
|
||||
|
||||
@ -353,9 +328,8 @@ internal class ApiVersionCheckWrapper(
|
||||
private fun checkVersion(expectedVersion: SubsonicAPIVersions) {
|
||||
// If it is true, it is probably the first call with this server
|
||||
if (!isRealProtocolVersion) return
|
||||
if (currentApiVersion < expectedVersion) {
|
||||
if (currentApiVersion < expectedVersion)
|
||||
throw ApiNotSupportedException(currentApiVersion)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkParamVersion(param: Any?, expectedVersion: SubsonicAPIVersions) {
|
||||
|
@ -44,7 +44,7 @@ fun <T : SubsonicResponse> Response<T>.throwOnFailure(): Response<T> {
|
||||
val response = this
|
||||
|
||||
if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) {
|
||||
return this
|
||||
return this as Response<T>
|
||||
}
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Server error, code: " + response.code())
|
||||
|
@ -8,7 +8,6 @@ import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import okhttp3.Credentials
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
@ -44,7 +43,7 @@ class SubsonicAPIClient(
|
||||
config.minimalProtocolVersion,
|
||||
PasswordHexInterceptor(config.password),
|
||||
PasswordMD5Interceptor(config.password),
|
||||
config.forcePlainTextPassword
|
||||
config.enableLdapUserSupport
|
||||
)
|
||||
|
||||
var onProtocolChange: (SubsonicAPIVersions) -> Unit = {}
|
||||
@ -64,7 +63,6 @@ class SubsonicAPIClient(
|
||||
}
|
||||
|
||||
val okHttpClient: OkHttpClient = baseOkClient.newBuilder()
|
||||
// Disable HTTP2 because OkHttp with Exoplayer causes a bug. See https://github.com/square/okhttp/issues/6749
|
||||
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
||||
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
|
||||
.addInterceptor { chain ->
|
||||
@ -75,19 +73,7 @@ class SubsonicAPIClient(
|
||||
.addQueryParameter("c", config.clientID)
|
||||
.addQueryParameter("f", "json")
|
||||
.build()
|
||||
val newRequestBuilder = originalRequest.newBuilder().url(newUrl)
|
||||
if (originalRequest.url.username.isNotEmpty() &&
|
||||
originalRequest.url.password.isNotEmpty()
|
||||
) {
|
||||
newRequestBuilder.addHeader(
|
||||
"Authorization",
|
||||
Credentials.basic(
|
||||
originalRequest.url.username,
|
||||
originalRequest.url.password
|
||||
)
|
||||
)
|
||||
}
|
||||
chain.proceed(newRequestBuilder.build())
|
||||
chain.proceed(originalRequest.newBuilder().url(newUrl).build())
|
||||
}
|
||||
.addInterceptor(versionInterceptor)
|
||||
.addInterceptor(proxyPasswordInterceptor)
|
||||
@ -95,12 +81,10 @@ class SubsonicAPIClient(
|
||||
.apply { if (config.debug) addLogging() }
|
||||
.build()
|
||||
|
||||
val baseUrl = "${config.baseUrl}/rest/"
|
||||
|
||||
// Create the Retrofit instance, and register a special converter factory
|
||||
// It will update our protocol version to the correct version, once we made a successful call
|
||||
private val retrofit: Retrofit = Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.baseUrl("${config.baseUrl}/rest/")
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(
|
||||
VersionAwareJacksonConverterFactory.create(
|
||||
@ -125,16 +109,17 @@ class SubsonicAPIClient(
|
||||
|
||||
private fun OkHttpClient.Builder.addLogging() {
|
||||
val loggingInterceptor = HttpLoggingInterceptor(okLogger)
|
||||
loggingInterceptor.level = HttpLoggingInterceptor.Level.HEADERS
|
||||
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
this.addInterceptor(loggingInterceptor)
|
||||
}
|
||||
|
||||
@Suppress("CustomX509TrustManager", "TrustAllX509TrustManager")
|
||||
private fun OkHttpClient.Builder.allowSelfSignedCertificates() {
|
||||
val trustManager =
|
||||
|
||||
@Suppress("CustomX509TrustManager")
|
||||
object : X509TrustManager {
|
||||
@Suppress("TrustAllX509TrustManager")
|
||||
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
||||
@Suppress("TrustAllX509TrustManager")
|
||||
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
|
||||
}
|
||||
@ -154,17 +139,11 @@ class SubsonicAPIClient(
|
||||
return call.toStreamResponse()
|
||||
}
|
||||
|
||||
val isOffline by lazy {
|
||||
config.baseUrl == OFFLINE_DB_URL
|
||||
}
|
||||
|
||||
companion object {
|
||||
val jacksonMapper: ObjectMapper = ObjectMapper()
|
||||
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
|
||||
.registerModule(KotlinModule.Builder().build())
|
||||
|
||||
const val OFFLINE_DB_URL = "http://localhost"
|
||||
.registerModule(KotlinModule())
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,3 @@
|
||||
/*
|
||||
* SubsonicAPIDefinition.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
@ -54,9 +47,6 @@ interface SubsonicAPIDefinition {
|
||||
@GET("ping.view")
|
||||
fun ping(): Call<SubsonicResponse>
|
||||
|
||||
@GET("ping.view")
|
||||
suspend fun pingSuspend(): SubsonicResponse
|
||||
|
||||
@GET("getLicense.view")
|
||||
fun getLicense(): Call<LicenseResponse>
|
||||
|
||||
@ -90,7 +80,10 @@ interface SubsonicAPIDefinition {
|
||||
): Call<SubsonicResponse>
|
||||
|
||||
@GET("setRating.view")
|
||||
fun setRating(@Query("id") id: String, @Query("rating") rating: Int): Call<SubsonicResponse>
|
||||
fun setRating(
|
||||
@Query("id") id: String,
|
||||
@Query("rating") rating: Int
|
||||
): Call<SubsonicResponse>
|
||||
|
||||
@GET("getArtist.view")
|
||||
fun getArtist(@Query("id") id: String): Call<GetArtistResponse>
|
||||
@ -155,7 +148,8 @@ interface SubsonicAPIDefinition {
|
||||
@Query("public") public: Boolean? = null,
|
||||
@Query("songIdToAdd") songIdsToAdd: List<String>? = null,
|
||||
@Query("songIndexToRemove") songIndexesToRemove: List<Int>? = null
|
||||
): Call<SubsonicResponse>
|
||||
):
|
||||
Call<SubsonicResponse>
|
||||
|
||||
@GET("getPodcasts.view")
|
||||
fun getPodcasts(
|
||||
@ -163,12 +157,6 @@ interface SubsonicAPIDefinition {
|
||||
@Query("id") id: String? = null
|
||||
): Call<GetPodcastsResponse>
|
||||
|
||||
@GET("getPodcasts.view")
|
||||
suspend fun getPodcastsSuspend(
|
||||
@Query("includeEpisodes") includeEpisodes: Boolean? = null,
|
||||
@Query("id") id: String? = null
|
||||
): GetPodcastsResponse
|
||||
|
||||
@GET("getLyrics.view")
|
||||
fun getLyrics(
|
||||
@Query("artist") artist: String? = null,
|
||||
@ -223,7 +211,10 @@ interface SubsonicAPIDefinition {
|
||||
|
||||
@Streaming
|
||||
@GET("getCoverArt.view")
|
||||
fun getCoverArt(@Query("id") id: String, @Query("size") size: Long? = null): Call<ResponseBody>
|
||||
fun getCoverArt(
|
||||
@Query("id") id: String,
|
||||
@Query("size") size: Long? = null
|
||||
): Call<ResponseBody>
|
||||
|
||||
@Streaming
|
||||
@GET("stream.view")
|
||||
@ -263,9 +254,6 @@ interface SubsonicAPIDefinition {
|
||||
@GET("getShares.view")
|
||||
fun getShares(): Call<SharesResponse>
|
||||
|
||||
@GET("getShares.view")
|
||||
suspend fun getSharesSuspend(): SharesResponse
|
||||
|
||||
@GET("createShare.view")
|
||||
fun createShare(
|
||||
@Query("id") idsToShare: List<String>,
|
||||
@ -297,24 +285,15 @@ interface SubsonicAPIDefinition {
|
||||
@GET("getUser.view")
|
||||
fun getUser(@Query("username") username: String): Call<GetUserResponse>
|
||||
|
||||
@GET("getUser.view")
|
||||
suspend fun getUserSuspend(@Query("username") username: String): GetUserResponse
|
||||
|
||||
@GET("getChatMessages.view")
|
||||
fun getChatMessages(@Query("since") since: Long? = null): Call<ChatMessagesResponse>
|
||||
|
||||
@GET("getChatMessages.view")
|
||||
suspend fun getChatMessagesSuspend(@Query("since") since: Long? = null): ChatMessagesResponse
|
||||
|
||||
@GET("addChatMessage.view")
|
||||
fun addChatMessage(@Query("message") message: String): Call<SubsonicResponse>
|
||||
|
||||
@GET("getBookmarks.view")
|
||||
fun getBookmarks(): Call<BookmarksResponse>
|
||||
|
||||
@GET("getBookmarks.view")
|
||||
suspend fun getBookmarksSuspend(): BookmarksResponse
|
||||
|
||||
@GET("createBookmark.view")
|
||||
fun createBookmark(
|
||||
@Query("id") id: String,
|
||||
@ -328,9 +307,6 @@ interface SubsonicAPIDefinition {
|
||||
@GET("getVideos.view")
|
||||
fun getVideos(): Call<VideosResponse>
|
||||
|
||||
@GET("getVideos.view")
|
||||
suspend fun getVideosSuspend(): VideosResponse
|
||||
|
||||
@GET("getAvatar.view")
|
||||
fun getAvatar(@Query("username") username: String): Call<ResponseBody>
|
||||
}
|
||||
|
@ -29,25 +29,20 @@ enum class SubsonicAPIVersions(val subsonicVersions: String, val restApiVersion:
|
||||
V1_13_0("5.3", "1.13.0"),
|
||||
V1_14_0("6.0", "1.14.0"),
|
||||
V1_15_0("6.1", "1.15.0"),
|
||||
V1_16_0("6.1.2", "1.16.0")
|
||||
;
|
||||
V1_16_0("6.1.2", "1.16.0");
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
@JvmStatic @Throws(IllegalArgumentException::class)
|
||||
fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions {
|
||||
val versionComponents = apiVersion.split(".")
|
||||
|
||||
require(versionComponents.size >= 2) { "Unknown api version $apiVersion" }
|
||||
if (versionComponents.size < 2)
|
||||
throw IllegalArgumentException("Unknown api version $apiVersion")
|
||||
|
||||
try {
|
||||
val majorVersion = versionComponents[0].toInt()
|
||||
val minorVersion = versionComponents[1].toInt()
|
||||
val patchVersion = if (versionComponents.size > 2) {
|
||||
versionComponents[2].toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val patchVersion = if (versionComponents.size > 2) versionComponents[2].toInt()
|
||||
else 0
|
||||
|
||||
when (majorVersion) {
|
||||
1 -> when {
|
||||
|
@ -10,7 +10,7 @@ data class SubsonicClientConfiguration(
|
||||
val minimalProtocolVersion: SubsonicAPIVersions,
|
||||
val clientID: String,
|
||||
val allowSelfSignedCertificate: Boolean = false,
|
||||
val forcePlainTextPassword: Boolean = false,
|
||||
val enableLdapUserSupport: Boolean = false,
|
||||
val debug: Boolean = false,
|
||||
val isRealProtocolVersion: Boolean = false
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ class VersionAwareJacksonConverterFactory(
|
||||
type: Type,
|
||||
annotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): Converter<ResponseBody, *> {
|
||||
): Converter<ResponseBody, *>? {
|
||||
val javaType: JavaType = mapper!!.typeFactory.constructType(type)
|
||||
val reader: ObjectReader? = mapper!!.readerFor(javaType)
|
||||
return VersionAwareResponseBodyConverter<Any>(notifier, reader!!)
|
||||
@ -48,10 +48,7 @@ class VersionAwareJacksonConverterFactory(
|
||||
retrofit: Retrofit
|
||||
): Converter<*, RequestBody>? {
|
||||
return jacksonConverterFactory?.requestBodyConverter(
|
||||
type,
|
||||
parameterAnnotations,
|
||||
methodAnnotations,
|
||||
retrofit
|
||||
type, parameterAnnotations, methodAnnotations, retrofit
|
||||
)
|
||||
}
|
||||
|
||||
@ -66,7 +63,7 @@ class VersionAwareJacksonConverterFactory(
|
||||
}
|
||||
}
|
||||
|
||||
class VersionAwareResponseBodyConverter<T>(
|
||||
class VersionAwareResponseBodyConverter<T> (
|
||||
private val notifier: (SubsonicAPIVersions) -> Unit = {},
|
||||
private val adapter: ObjectReader
|
||||
) : Converter<ResponseBody, T> {
|
||||
|
@ -6,7 +6,6 @@ import okhttp3.Interceptor.Chain
|
||||
import okhttp3.Response
|
||||
|
||||
internal const val SOCKET_READ_TIMEOUT_DOWNLOAD = 30 * 1000
|
||||
|
||||
// Allow 20 seconds extra timeout pear MB offset.
|
||||
internal const val TIMEOUT_MILLIS_PER_OFFSET_BYTE = 0.02
|
||||
|
||||
|
@ -1,10 +1,3 @@
|
||||
/*
|
||||
* AlbumListOrderType.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.api.subsonic.models
|
||||
|
||||
/**
|
||||
@ -23,8 +16,7 @@ enum class AlbumListType(val typeName: String) {
|
||||
SORTED_BY_ARTIST("alphabeticalByArtist"),
|
||||
STARRED("starred"),
|
||||
BY_YEAR("byYear"),
|
||||
BY_GENRE("byGenre")
|
||||
;
|
||||
BY_GENRE("byGenre");
|
||||
|
||||
override fun toString(): String {
|
||||
return typeName
|
||||
|
@ -16,8 +16,7 @@ enum class JukeboxAction(val action: String) {
|
||||
CLEAR("clear"),
|
||||
REMOVE("remove"),
|
||||
SHUFFLE("shuffle"),
|
||||
SET_GAIN("setGain")
|
||||
;
|
||||
SET_GAIN("setGain");
|
||||
|
||||
override fun toString(): String {
|
||||
return action
|
||||
|
@ -10,8 +10,7 @@ class BookmarksResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("bookmarks")
|
||||
private val bookmarksWrapper = BookmarkWrapper()
|
||||
@JsonProperty("bookmarks") private val bookmarksWrapper = BookmarkWrapper()
|
||||
|
||||
val bookmarkList: List<Bookmark> get() = bookmarksWrapper.bookmarkList
|
||||
}
|
||||
|
@ -10,8 +10,7 @@ class ChatMessagesResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("chatMessages")
|
||||
private val wrapper = ChatMessagesWrapper()
|
||||
@JsonProperty("chatMessages") private val wrapper = ChatMessagesWrapper()
|
||||
|
||||
val chatMessages: List<ChatMessage> get() = wrapper.messagesList
|
||||
}
|
||||
|
@ -10,8 +10,7 @@ class GenresResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("genres")
|
||||
private val genresWrapper = GenresWrapper()
|
||||
@JsonProperty("genres") private val genresWrapper = GenresWrapper()
|
||||
val genresList: List<Genre> get() = genresWrapper.genresList
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,7 @@ class GetAlbumList2Response(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("albumList2")
|
||||
private val albumWrapper2 = AlbumWrapper2()
|
||||
@JsonProperty("albumList2") private val albumWrapper2 = AlbumWrapper2()
|
||||
|
||||
val albumList: List<Album>
|
||||
get() = albumWrapper2.albumList
|
||||
|
@ -10,8 +10,7 @@ class GetAlbumListResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("albumList")
|
||||
private val albumWrapper = AlbumWrapper()
|
||||
@JsonProperty("albumList") private val albumWrapper = AlbumWrapper()
|
||||
|
||||
val albumList: List<Album>
|
||||
get() = albumWrapper.albumList
|
||||
|
@ -10,8 +10,7 @@ class GetPodcastsResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("podcasts")
|
||||
private val channelsWrapper = PodcastChannelWrapper()
|
||||
@JsonProperty("podcasts") private val channelsWrapper = PodcastChannelWrapper()
|
||||
|
||||
val podcastChannels: List<PodcastChannel>
|
||||
get() = channelsWrapper.channelsList
|
||||
|
@ -10,8 +10,7 @@ class GetRandomSongsResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("randomSongs")
|
||||
private val songsWrapper = RandomSongsWrapper()
|
||||
@JsonProperty("randomSongs") private val songsWrapper = RandomSongsWrapper()
|
||||
|
||||
val songsList
|
||||
get() = songsWrapper.songsList
|
||||
|
@ -10,8 +10,7 @@ class GetSongsByGenreResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("songsByGenre")
|
||||
private val songsByGenreList = SongsByGenreWrapper()
|
||||
@JsonProperty("songsByGenre") private val songsByGenreList = SongsByGenreWrapper()
|
||||
|
||||
val songsList get() = songsByGenreList.songsList
|
||||
}
|
||||
|
@ -11,13 +11,11 @@ class JukeboxResponse(
|
||||
error: SubsonicError?,
|
||||
var jukebox: JukeboxStatus = JukeboxStatus()
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonSetter("jukeboxStatus")
|
||||
fun setJukeboxStatus(jukebox: JukeboxStatus) {
|
||||
@JsonSetter("jukeboxStatus") fun setJukeboxStatus(jukebox: JukeboxStatus) {
|
||||
this.jukebox = jukebox
|
||||
}
|
||||
|
||||
@JsonSetter("jukeboxPlaylist")
|
||||
fun setJukeboxPlaylist(jukebox: JukeboxStatus) {
|
||||
@JsonSetter("jukeboxPlaylist") fun setJukeboxPlaylist(jukebox: JukeboxStatus) {
|
||||
this.jukebox = jukebox
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,7 @@ class MusicFoldersResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("musicFolders")
|
||||
private val wrapper = MusicFoldersWrapper()
|
||||
@JsonProperty("musicFolders") private val wrapper = MusicFoldersWrapper()
|
||||
|
||||
val musicFolders get() = wrapper.musicFolders
|
||||
}
|
||||
|
@ -10,8 +10,7 @@ class SharesResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("shares")
|
||||
private val wrappedShares = SharesWrapper()
|
||||
@JsonProperty("shares") private val wrappedShares = SharesWrapper()
|
||||
|
||||
val shares get() = wrappedShares.share
|
||||
}
|
||||
|
@ -20,13 +20,12 @@ open class SubsonicResponse(
|
||||
) {
|
||||
@JsonDeserialize(using = Status.Companion.StatusJsonDeserializer::class)
|
||||
enum class Status(val jsonValue: String) {
|
||||
OK("ok"),
|
||||
ERROR("failed");
|
||||
OK("ok"), ERROR("failed");
|
||||
|
||||
companion object {
|
||||
fun getStatusFromJson(jsonValue: String) =
|
||||
values().firstOrNull { it.jsonValue == jsonValue }
|
||||
?: throw IllegalArgumentException("Unknown status value: $jsonValue")
|
||||
fun getStatusFromJson(jsonValue: String) = values()
|
||||
.filter { it.jsonValue == jsonValue }.firstOrNull()
|
||||
?: throw IllegalArgumentException("Unknown status value: $jsonValue")
|
||||
|
||||
class StatusJsonDeserializer : JsonDeserializer<Status>() {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): Status {
|
||||
|
@ -10,8 +10,7 @@ class VideosResponse(
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?
|
||||
) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("videos")
|
||||
private val videosWrapper = VideosWrapper()
|
||||
@JsonProperty("videos") private val videosWrapper = VideosWrapper()
|
||||
|
||||
val videosList: List<MusicDirectoryChild> get() = videosWrapper.videosList
|
||||
}
|
||||
|
@ -18,9 +18,7 @@ class ProxyPasswordInterceptorTest {
|
||||
|
||||
private val proxyInterceptor = ProxyPasswordInterceptor(
|
||||
V1_12_0,
|
||||
mockPasswordHexInterceptor,
|
||||
mockPasswordMd5Interceptor,
|
||||
false
|
||||
mockPasswordHexInterceptor, mockPasswordMd5Interceptor, false
|
||||
)
|
||||
|
||||
@Test
|
||||
@ -42,10 +40,8 @@ class ProxyPasswordInterceptorTest {
|
||||
@Test
|
||||
fun `Should use hex password if forceHex is true`() {
|
||||
val interceptor = ProxyPasswordInterceptor(
|
||||
V1_16_0,
|
||||
mockPasswordHexInterceptor,
|
||||
mockPasswordMd5Interceptor,
|
||||
true
|
||||
V1_16_0, mockPasswordHexInterceptor,
|
||||
mockPasswordMd5Interceptor, true
|
||||
)
|
||||
|
||||
interceptor.intercept(mockChain)
|
||||
|
@ -36,7 +36,6 @@ class AlbumListTypeTest {
|
||||
|
||||
@Test
|
||||
fun `Should return type name for toString call`() {
|
||||
AlbumListType.STARRED.typeName `should be equal to`
|
||||
AlbumListType.STARRED.toString()
|
||||
AlbumListType.STARRED.typeName `should be equal to` AlbumListType.STARRED.toString()
|
||||
}
|
||||
}
|
||||
|
43
detekt-baseline.xml
Normal file
43
detekt-baseline.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>ComplexCondition:DownloadHandler.kt$DownloadHandler.<no name provided>$!append && !playNext && !unpin && !background</ID>
|
||||
<ID>ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.<no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) )</ID>
|
||||
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file)</ID>
|
||||
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name)</ID>
|
||||
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Logging into file %s", file?.name)</ID>
|
||||
<ID>ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$String.format("BufferTask (%s)", downloadFile)</ID>
|
||||
<ID>ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$String.format("CheckCompletionTask (%s)", downloadFile)</ID>
|
||||
<ID>ImplicitDefaultLocale:ShareHandler.kt$ShareHandler$String.format("%d:%s", timeSpanAmount, timeSpanType)</ID>
|
||||
<ID>LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun doPlay(downloadFile: DownloadFile, position: Int, start: Boolean)</ID>
|
||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken )</ID>
|
||||
<ID>LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, private var data: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) )</ID>
|
||||
<ID>MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$60000</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$100000</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$86400L</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8L</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$5000L</ID>
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$3</ID>
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$4</ID>
|
||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$206</ID>
|
||||
<ID>NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean )</ID>
|
||||
<ID>NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
<ID>TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$exception: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$x: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$e: Exception</ID>
|
||||
<ID>TooGenericExceptionThrown:DownloadFile.kt$DownloadFile.DownloadTask$throw RuntimeException( String.format(Locale.ROOT, "Download of '%s' was cancelled", track) )</ID>
|
||||
<ID>TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service</ID>
|
||||
<ID>TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
||||
<ID>UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
@ -2,11 +2,14 @@ build:
|
||||
maxIssues: 0
|
||||
weights:
|
||||
complexity: 2
|
||||
formatting: 1
|
||||
LongParameterList: 1
|
||||
comments: 1
|
||||
|
||||
potential-bugs:
|
||||
active: true
|
||||
DuplicateCaseInWhenExpression:
|
||||
active: true
|
||||
EqualsWithHashCodeExist:
|
||||
active: true
|
||||
ExplicitGarbageCollectionCall:
|
||||
@ -46,32 +49,34 @@ complexity:
|
||||
LabeledExpression:
|
||||
active: false
|
||||
|
||||
|
||||
formatting:
|
||||
autoCorrect: true
|
||||
active: false
|
||||
|
||||
style:
|
||||
active: true
|
||||
NewLineAtEndOfFile:
|
||||
active: true
|
||||
ForbiddenComment:
|
||||
active: true
|
||||
comments:
|
||||
- reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
|
||||
value: 'FIXME:'
|
||||
- reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
|
||||
value: 'STOPSHIP:'
|
||||
values: 'FIXME:,STOPSHIP:'
|
||||
WildcardImport:
|
||||
active: true
|
||||
MaxLineLength:
|
||||
active: false
|
||||
active: true
|
||||
maxLineLength: 120
|
||||
excludePackageStatements: false
|
||||
excludeImportStatements: false
|
||||
MagicNumber:
|
||||
# 100 common in percentage, 1000 in milliseconds
|
||||
ignoreNumbers: ['-1', '0', '1', '2', '5', '10', '100', '256', '512', '1000', '1024', '4096']
|
||||
ignoreNumbers: ['-1', '0', '1', '2', '5', '10', '100', '256', '512', '1000', '1024']
|
||||
ignoreEnums: true
|
||||
ignorePropertyDeclaration: true
|
||||
UnnecessaryAbstractClass:
|
||||
active: false
|
||||
ReturnCount:
|
||||
max: 5
|
||||
ForbiddenImport:
|
||||
imports: ['android.app.AlertDialog']
|
||||
|
||||
comments:
|
||||
active: true
|
@ -1,37 +0,0 @@
|
||||
Release notes
|
||||
Ultrasonic 4.0.0 brings along a complete overhaul of how media is being played back. We have switched to a modern library to handle the playback and fixed wuite a few bugs in the process.
|
||||
Additionally the Offline mode now supports browsing based on ID3 tags. This requires additional metadata which will be missing for tracks downloaded with previous versions. If you enable this setting, the app will therefore show only the music that you have downloaded with 4.0.0 or later.
|
||||
|
||||
Bug fixes
|
||||
- #621: Empty notification shouldn't be visible.
|
||||
- #664: Weird behaviour and system crash.
|
||||
- #680: Now playing list should be able to distinguish duplicate items.
|
||||
- #696: Ultrasonic is activated with BT even when all media play functions are disabled.
|
||||
- #697: No sound after one track.
|
||||
- #717: Insert after current song not fully working.
|
||||
- #735: No longer showing any track data via bluetooth.
|
||||
- #738: App no longer appears as running or in notifications when resuming playback after wake.
|
||||
- #742: Pinned/downloaded music gets wiped/deleted on exit & start.
|
||||
- #759: Crash browsing by folder (unchecking browsing by ID3 label in config).
|
||||
- #767: Shuffle setting does not persist after reboot/resume.
|
||||
- #768: Album art doesn't appear in the media notification.
|
||||
- #769: The setting "settings.show_artist_picture" do nothing.
|
||||
- #776: Strange behavior in device rotation.
|
||||
- #785: Buttons below track list don't update anymore
|
||||
- #787: The down arrow is no longer animated when downloading new tracks.
|
||||
|
||||
Enhancements
|
||||
- #426: Start service only when playback is started by the user.
|
||||
- #432: Ultrasonic notification should be dismissible by swipe.
|
||||
- #553: Duration not read from metadata.
|
||||
- #581: Bluetooth not updating Now Playing information anymore.
|
||||
- #718: Add ID3 support when offline.
|
||||
- #727: Disable LoggingInterceptor for Streaming calls.
|
||||
- #739: Add more preload values.
|
||||
- #752: Target SDK 31.
|
||||
- #756: Android Auto needs work.
|
||||
- #775: Use Picasso to load images for the notification.
|
||||
|
||||
Others
|
||||
- #751: Remove Jacoco.
|
||||
- #786: Update to MediaBeta02.
|
@ -1,27 +0,0 @@
|
||||
Bug fixes
|
||||
- #765: Bad performance in Track scrolling.
|
||||
- #779: Starting playback from Bluetooth should display the Media
|
||||
notification.
|
||||
- #780: Album art isn't displayed on first play of a non-cached track.
|
||||
- #790: Items shouldn't be stuck in the ActivelyDownloading queue.
|
||||
- #792: LyricsFragment unaccessible.
|
||||
- #794: Equalizer broken.
|
||||
- #795: Backups not allowed.
|
||||
- #797: Server setting 'Jukebox By Default' broken.
|
||||
- #798: Jukebox mode in 4.0.0 seems mostly broken.
|
||||
- #799: Trying to open settings crashes the app.
|
||||
- #801: Last.FM scrobbling broken on v4.0.0.
|
||||
- #817: Crash when switching to Offline mode.
|
||||
|
||||
Enhancements
|
||||
- #734: Migrate Downloader.
|
||||
- #778: Do not show badge on Ultrasonic icon during playback.
|
||||
- #784: Redesign Album Art download and cache handling.
|
||||
- #796: Make the app more material.
|
||||
- #815: Download buttons don't show when a track is downloading.
|
||||
- #826: Reword the force plain text password setting.
|
||||
|
||||
Others
|
||||
- #510: Use SafeArgs for remaining fragments.
|
||||
- #793: Remove Visualizer.
|
||||
- #818: Add Renovatebot.
|
@ -1,6 +0,0 @@
|
||||
Bug fixes
|
||||
- #821: Multiple podcast selected = Never-ending buffering.
|
||||
- !847: Fix Download bug in Custom Locations.
|
||||
|
||||
Enhancements
|
||||
- !820: Updated widget handling and layouts, add Day&Night theme.
|
@ -1,8 +0,0 @@
|
||||
Bug fixes
|
||||
- #823: Version 4 caches incomplete files (poor download error handling?).
|
||||
- #832: Version 4.1.1 and develop: back(-navigation) no longer possible.
|
||||
- #835: Crashing when play all songs in offline mode.
|
||||
- #837: Saving playlist fails.
|
||||
|
||||
Enhancements
|
||||
- #816: Accented letters should not be added seperately in the Artists list.
|
@ -1,8 +0,0 @@
|
||||
Bug fixes
|
||||
- #833: Widget height is always a minimum of 2 rows instead of previous 1 row.
|
||||
- #840: DownloadTask shouldn't be started multiple times for a track.
|
||||
- !849: Bunch of fixes 💐.
|
||||
|
||||
Enhancements
|
||||
- #841: Remove whitespace as default text in server settings.
|
||||
- !865: Fix a small deprecation notice.
|
@ -1 +0,0 @@
|
||||
See 110
|
@ -1,7 +0,0 @@
|
||||
Bug fixes
|
||||
- #849: NoSuchElementException.
|
||||
- !874: Fix a crash.
|
||||
|
||||
Features
|
||||
- #806: Display albums sorted chronologically.
|
||||
- #843: Add Album cover view (Rework UI).
|
@ -1,5 +0,0 @@
|
||||
Bug fixes
|
||||
- #848: ConcurrentModification crash.
|
||||
|
||||
Enhancements
|
||||
- #859: "Download on Wi-Fi only" setting doesn't detect Wi-Fi when VPN is enabled.
|
@ -1,14 +0,0 @@
|
||||
Bug fixes
|
||||
- #831: Version 4.1.1 and develop: 'jukebox on/off' no longer shown in 'Now Playing'.
|
||||
- #850: ArrayIndexOutOfBoundsException.
|
||||
- #858: Playlist is empty on relauch.
|
||||
- #861: Podcast titles all shown as "null".
|
||||
- #863: Ultrasonic crashes on startup when pointed to subsonic demo server.
|
||||
- #864: Ultrasonic sends invalid URLs when Server Address ends in '/'.
|
||||
- #867: Ultrasonic shouldn't crash when deleting a playlist.
|
||||
- #872: Ultrasonic should handle custom cache location without creating multiple copies of a track.
|
||||
- #886: Jukebox crashes since 4.2.1.
|
||||
|
||||
Enhancements
|
||||
- #829: isJukeBoxAvailable is called on every visit of PlayerFragment.
|
||||
- #854: Remove Videos menu option for servers which don't support it.
|
@ -1,7 +0,0 @@
|
||||
Bug fixes
|
||||
- #831: Version 4.1.1 and develop: 'jukebox on/off' no longer shown in 'Now Playing'.
|
||||
|
||||
Enhancements
|
||||
- #827: Make app full compliant Android Auto to publish in Play Store.
|
||||
- #878: "Play shuffled" option for playlists always begins with the first track.
|
||||
- #891: Dump config to log file when logging is enabled.
|
@ -1,4 +0,0 @@
|
||||
Bug fixes
|
||||
- Fix a crash when a ID3 tag date is in a wrong format.
|
||||
- Fix a crash on API 31 (newest Android).
|
||||
- Fix empty search results.
|
@ -1,2 +0,0 @@
|
||||
Bug fixes
|
||||
- Fix a crash when downloading the album art.
|
@ -1,8 +0,0 @@
|
||||
Bug fixes
|
||||
- Fix various crashes
|
||||
|
||||
Changes since 4.2.0
|
||||
- #827: Make app full compliant Android Auto to publish in Play Store.
|
||||
- #878: "Play shuffled" option for playlists always begins with the first track.
|
||||
- #891: Dump config to log file when logging is enabled.
|
||||
- #854: Remove Videos menu option for servers which don't support it.
|
@ -1,8 +0,0 @@
|
||||
Bug fixes
|
||||
- Fix more exceptions
|
||||
|
||||
Changes since 4.2.0
|
||||
- #827: Make app full compliant Android Auto to publish in Play Store.
|
||||
- #878: "Play shuffled" option for playlists always begins with the first track.
|
||||
- #891: Dump config to log file when logging is enabled.
|
||||
- #854: Remove Videos menu option for servers which don't support it.
|
@ -1,10 +0,0 @@
|
||||
Features:
|
||||
- This releases focuses on shuffled playback. The view of the playlist will now present itself in the order it will actually play. You can toggle the shuffle mode to create a new order, while the past playback history will be preserved.
|
||||
- Use Coroutines for triggering the download or playback of music through the context menus
|
||||
- Enable Artists pictures by Default
|
||||
|
||||
Bug fixes:
|
||||
- Remove an unhelpful popup that "ID must be set"
|
||||
- Shuffle mode doesn't always play all tracks
|
||||
- Shuffle mode starts with the first track most of the time
|
||||
|
@ -1,10 +0,0 @@
|
||||
Features:
|
||||
- This releases focuses on shuffled playback. The view of the playlist will now present itself in the order it will actually play. You can toggle the shuffle mode to create a new order, while the past playback history will be preserved.
|
||||
- Use Coroutines for triggering the download or playback of music through the context menus
|
||||
- Enable Artists pictures by Default
|
||||
|
||||
Bug fixes:
|
||||
- Remove an unhelpful popup that "ID must be set"
|
||||
- Shuffle mode doesn't always play all tracks
|
||||
- Shuffle mode starts with the first track most of the time
|
||||
|
@ -1,10 +0,0 @@
|
||||
Features:
|
||||
- Revamp management of ratings. Tracks can be starred from the notification in Android 13, and the changes will show up everywhere immediately.
|
||||
- Add a setting to control the maximum bitrate when pinning music (can be used to avoid downloading lossless files like flac).
|
||||
- Modernize the Jukebox player.
|
||||
- The hardware keys can be used to set the Jukebox volume.
|
||||
- The current playlist shows a spinner when loading takes some time
|
||||
|
||||
Bug fixes:
|
||||
- Request correct bluetooth permission on Android 13 (needed to pause/play on connect)
|
||||
- Update dependencies (OkHttp, Material)
|
@ -1,12 +0,0 @@
|
||||
Features:
|
||||
- Search is accesible through a new icon on the main screen
|
||||
- Modernize Back Handling
|
||||
- Reenable R8 Code minification
|
||||
- Add a "Play Random Songs" shortcut
|
||||
|
||||
Bug fixes:
|
||||
- Tracks buttons flash a scrollbar sometimes in Android 13
|
||||
- Fix EndlessScrolling in genre listing
|
||||
- Couldn't delete a track when shuffle was active
|
||||
- Upgrade material to 1.9.0
|
||||
|
@ -1,15 +0,0 @@
|
||||
Features:
|
||||
- Search is accessible through a new icon on the main screen
|
||||
- Modernize Back Handling
|
||||
- Reenable R8 Code minification
|
||||
- Add a "Play Random Songs" shortcut
|
||||
|
||||
Bug fixes:
|
||||
- Readd the "Star" button to the Now Playing screen
|
||||
- Fix a rare crash when shuffling playlists with duplicate entries
|
||||
- Fix a crash when choosing "Play next" on an empty playlist.
|
||||
- Tracks buttons flash a scrollbar sometimes in Android 13
|
||||
- Fix EndlessScrolling in genre listing
|
||||
- Couldn't delete a track when shuffle was active
|
||||
- Upgrade material to 1.9.0
|
||||
|
@ -1,15 +0,0 @@
|
||||
Features:
|
||||
- Search is accessible through a new icon on the main screen
|
||||
- Modernize Back Handling
|
||||
- Reenable R8 Code minification
|
||||
- Add a "Play Random Songs" shortcut
|
||||
|
||||
Bug fixes:
|
||||
- Avoid triggering a bug in Supysonic
|
||||
- Readd the "Star" button to the Now Playing screen
|
||||
- Fix a rare crash when shuffling playlists with duplicate entries
|
||||
- Fix a crash when choosing "Play next" on an empty playlist.
|
||||
- Tracks buttons flash a scrollbar sometimes in Android 13
|
||||
- Fix EndlessScrolling in genre listing
|
||||
- Couldn't delete a track when shuffle was active
|
||||
|
@ -1,15 +0,0 @@
|
||||
Features:
|
||||
- Search is accessible through a new icon on the main screen
|
||||
- Modernize Back Handling
|
||||
- Reenable R8 Code minification
|
||||
- Add a "Play Random Songs" shortcut
|
||||
|
||||
Bug fixes:
|
||||
- Fix a few crashes
|
||||
- Avoid triggering a bug in Supysonic
|
||||
- Readd the "Star" button to the Now Playing screen
|
||||
- Fix a rare crash when shuffling playlists with duplicate entries
|
||||
- Fix a crash when choosing "Play next" on an empty playlist.
|
||||
- Tracks buttons flash a scrollbar sometimes in Android 13
|
||||
- Fix EndlessScrolling in genre listing
|
||||
- Couldn't delete a track when shuffle was active
|
@ -1,9 +0,0 @@
|
||||
### Features
|
||||
- Added custom buttons for shuffling the current queue and setting repeat mode (Android Auto)
|
||||
- Properly handling nested directory structures (Android Auto)
|
||||
- Add a toast when adding tracks to the playlist
|
||||
- Allow pinning when offline
|
||||
|
||||
### Dependencies
|
||||
- Update koin
|
||||
- Update media3 to v1.1.0
|
@ -1,12 +0,0 @@
|
||||
### Fixes
|
||||
- Fix a bug in 4.7.0 that repeat mode was activated by default.
|
||||
|
||||
### Features
|
||||
- Added custom buttons for shuffling the current queue and setting repeat mode (Android Auto)
|
||||
- Properly handling nested directory structures (Android Auto)
|
||||
- Add a toast when adding tracks to the playlist
|
||||
- Allow pinning when offline
|
||||
|
||||
### Dependencies
|
||||
- Update koin
|
||||
- Update media3 to v1.1.0
|
@ -1,5 +0,0 @@
|
||||
### Features
|
||||
- Improved display of rating stars
|
||||
- Completely modernize all older code parts
|
||||
- Updates for Android 14
|
||||
- Update dependencies
|
@ -1,18 +1,18 @@
|
||||
Ultrasonic is a Subsonic (and compatible servers) client to Android. You can use Ultrasonic to connect with your server and listen music.
|
||||
|
||||
Main features:
|
||||
* Small size & fast
|
||||
* Material You theme with dark and light variants
|
||||
* Thin
|
||||
* Fast
|
||||
* Dark and light theme
|
||||
* Multiple server support
|
||||
* Download tracks for offline playback
|
||||
* Offline Mode
|
||||
* Bookmarks
|
||||
* Playlists on server
|
||||
* Shuffled playback
|
||||
* Ramdom play
|
||||
* Jukebox mode
|
||||
* And much more!!
|
||||
* Server chat
|
||||
* And much more!!!
|
||||
|
||||
Note: Ultrasonic uses semantic release versions. Releases with a zero in the last digit introduce new features or significant changes, all other releases focus on fixing bugs.
|
||||
|
||||
The source code is available with GPL license in GitLab: https://gitlab.com/ultrasonic/ultrasonic
|
||||
If you have any issue, please post in: https://gitlab.com/ultrasonic/ultrasonic/issues
|
||||
Play store icon designed by: http://www.flaticon.com/authors/sebastien-gabriel
|
||||
The source code is available with GPL license in Github: https://github.com/ultrasonic/ultrasonic
|
||||
If you have any issue, please post in: https://github.com/ultrasonic/ultrasonic/issues
|
||||
Play store icon designed by: http://www.flaticon.com/authors/sebastien-gabriel
|
@ -1,37 +0,0 @@
|
||||
Notas de la versión
|
||||
Ultrasonic 4.0.0 trae consigo una revisión completa de cómo se reproducen los medios. Hemos cambiado a una biblioteca moderna para manejar la reproducción y hemos corregido algunos errores en el proceso.
|
||||
Además, el modo Offline soporta ahora la navegación basada en las etiquetas ID3. Esto requiere metadatos adicionales que faltarán para las pistas descargadas con versiones anteriores. Si activas esta opción, la aplicación sólo mostrará la música que hayas descargado con la versión 4.0.0 o posterior.
|
||||
|
||||
Corrección de errores
|
||||
- #621: Las notificaciones vacías no deberían ser visibles.
|
||||
- #664: Comportamiento extraño y caída del sistema.
|
||||
- #680: La lista de reproducción debería poder distinguir los elementos duplicados.
|
||||
- #696: Ultrasonic se activan con BT incluso cuando todas las funciones de reproducción de medios están desactivadas.
|
||||
- #697: No hay sonido después de una pista.
|
||||
- #717: Insertar después de la canción actual no funciona del todo.
|
||||
- #735: Ya no se muestran los datos de las pistas por bluetooth.
|
||||
- #738: La aplicación ya no aparece como en ejecución o en las notificaciones al reanudar la reproducción.
|
||||
- #742: La música anclada/descargada se borra al salir e iniciar.
|
||||
- #759: Se bloquea la navegación por carpetas (desmarcando la navegación por etiquetas ID3 en la configuración).
|
||||
- #767: La configuración de la reproducción aleatoria no persiste después de reiniciar/reanudar.
|
||||
- #768: La carátula del álbum no aparece en la notificación de medios.
|
||||
- #769: El ajuste "settings.show_artist_picture" no hace nada.
|
||||
- #776: Comportamiento extraño en la rotación del dispositivo.
|
||||
- #785: Los botones debajo de la lista de canciones ya no se actualizan.
|
||||
- #787: La flecha hacia abajo ya no se anima cuando se descargan nuevas pistas.
|
||||
|
||||
Mejoras
|
||||
- #426: Iniciar el servicio sólo cuando la reproducción es iniciada por el usuario.
|
||||
- #432: Las notificaciones de Ultrasonic deberían poder desactivarse al deslizar el dedo.
|
||||
- #553: La duración de las pistas no se lee de los metadatos.
|
||||
- #581: Bluetooth ya no actualiza la información de Reproduciendo ahora.
|
||||
- #718: Añadir soporte de ID3 cuando se está desconectado.
|
||||
- #727: Desactivar LoggingInterceptor para las llamadas de Streaming.
|
||||
- #739: Añadir más valores de precarga.
|
||||
- #752: Apuntar al SDK 31.
|
||||
- #756: Android Auto necesita trabajo.
|
||||
- #775: Usar Picasso para cargar imágenes para la notificación.
|
||||
|
||||
Otros
|
||||
- #751: Eliminar Jacoco.
|
||||
- #786: Actualizar a MediaBeta02.
|
@ -1,33 +0,0 @@
|
||||
Corrección de errores
|
||||
- #765: Mal rendimiento en el desplazamiento de pistas.
|
||||
- #779: El inicio de la reproducción desde Bluetooth debería mostrar la
|
||||
notificación de medios.
|
||||
- #780: La carátula del álbum no se muestra en la primera reproducción de
|
||||
una pista no almacenada.
|
||||
- #790: Los elementos no deberían quedarse en la cola de
|
||||
ActivelyDownloading.
|
||||
- #792: LyricsFragment no es accesible.
|
||||
- #794: Ecualizador roto.
|
||||
- #795: Copias de seguridad no permitidas.
|
||||
- #797: Configuración del servidor 'Jukebox por defecto' esta rota.
|
||||
- #798: El modo Jukebox en la versión 4.0.0 parece estar roto en su mayor
|
||||
parte.
|
||||
- #799: Al intentar abrir la configuración se bloquea la aplicación.
|
||||
- #801: El scrobbling de Last.FM no funciona en la versión 4.0.0.
|
||||
- #817: Se bloquea al cambiar al modo Offline.
|
||||
|
||||
Mejoras
|
||||
- #734: Migración del descargador.
|
||||
- #778: No mostrar la insignia en el icono de Ultrasonic durante la
|
||||
reproducción.
|
||||
- #784: Rediseño de la descarga de la carátula del álbum y manejo de la
|
||||
caché.
|
||||
- #796: Hacer la aplicación más material.
|
||||
- #815: Los botones de descarga no se muestran cuando una pista se está
|
||||
descargando.
|
||||
- #826: Reformular el ajuste de forzar la contraseña en texto plano.
|
||||
|
||||
Otros
|
||||
- #510: Usar SafeArgs para los fragmentos restantes.
|
||||
- #793: Eliminar el visualizador.
|
||||
- #818: Añadir Renovatebot.
|
@ -1,7 +0,0 @@
|
||||
Corrección de errores
|
||||
- #821: Múltiples podcasts seleccionados = Buffering interminable.
|
||||
- !847: Corrección del error de descarga en las ubicaciones personalizadas.
|
||||
|
||||
Mejoras
|
||||
- !820: Actualización del manejo de los widgets y de los diseños, adición
|
||||
del tema Day&Night.
|
@ -1,10 +0,0 @@
|
||||
Corrección de errores
|
||||
- #823: La versión 4 almacena en caché los archivos incompletos (¿mal manejo
|
||||
de errores de descarga?).
|
||||
- #832: Versión 4.1.1 y desarrollo: ya no es posible retroceder(-navegar).
|
||||
- #835: Crash al reproducir todas las canciones en modo offline.
|
||||
- #837: No se puede guardar la lista de reproducción.
|
||||
|
||||
Mejoras
|
||||
- #816: Las letras acentuadas no deben añadirse por separado en la lista de
|
||||
artistas.
|
@ -1,8 +0,0 @@
|
||||
Corrección de errores
|
||||
- #833: La altura del widget es siempre un mínimo de 2 filas en lugar de la anterior de 1 fila.
|
||||
- #840: DownloadTask no debería iniciarse varias veces para una canción.
|
||||
- !849: Un montón de correcciones 💐.
|
||||
|
||||
Mejoras
|
||||
- #841: Eliminar los espacios en blanco como texto por defecto en la configuración del servidor.
|
||||
- !865: Corregir un pequeño aviso de código obsoleto.
|
@ -1 +0,0 @@
|
||||
Ver 110
|
@ -1,8 +0,0 @@
|
||||
Corrección de errores
|
||||
- #849: NoSuchElementException.
|
||||
- !874: Corrección de un fallo.
|
||||
|
||||
Características
|
||||
- #806: Mostrar los álbumes ordenados cronológicamente.
|
||||
- #843: Añadir la vista de la portada del álbum (reconstrucción de la
|
||||
interfaz de usuario).
|
@ -1,5 +0,0 @@
|
||||
Corrección de errores
|
||||
- #848: Fallo de ConcurrentModification.
|
||||
|
||||
Mejoras
|
||||
- #859: El ajuste "Descargar sólo en Wi-Fi" no detecta la Wi-Fi cuando la VPN está activada.
|
@ -1,14 +0,0 @@
|
||||
Corrección de errores
|
||||
- #831: Versión 4.1.1 y desarrollo: 'jukebox encender/apagar' ya no se muestra en 'Reproduciendo ahora'.
|
||||
- #850: ArrayIndexOutOfBoundsException.
|
||||
- #858: La lista de reproducción está vacía.
|
||||
- #861: Los títulos de los podcasts se muestran como "null".
|
||||
- #863: Ultrasonic se bloquea al inicio cuando se apunta al servidor de demostración subsonic.
|
||||
- #864: Ultrasonic envía URLs inválidas cuando la dirección del servidor termina en '/'.
|
||||
- #867: Ultrasonic no debería bloquearse al borrar una lista de reproducción.
|
||||
- #872: Ultrasonic debería manejar la ubicación de caché personalizada sin crear múltiples copias de una pista.
|
||||
- #886: Jukebox se bloquea desde 4.2.1.
|
||||
|
||||
Mejoras
|
||||
- #829: isJukeBoxAvailable se llama en cada visita de PlayerFragment.
|
||||
- #854: Eliminar la opción de menú Videos para servidores que no la soportan.
|
@ -1,7 +0,0 @@
|
||||
Corrección de errores
|
||||
- #831: Versión 4.1.1 y desarrollo: 'jukebox on/off' ya no se muestra en 'Reproduciendo ahora'.
|
||||
|
||||
Mejoras
|
||||
- #827: Hacer aplicación completamente compatible con Android Auto para publicar en Play Store.
|
||||
- #878: "Reproducir aleatoriamente" ya no siempre comienza con la primera pista.
|
||||
- #891: Volcado de configuración al archivo de registro cuando el registro está habilitado.
|
@ -1,4 +0,0 @@
|
||||
Corrección de errores
|
||||
- Corrección de un fallo cuando la fecha de una etiqueta ID3 tiene un formato incorrecto.
|
||||
- Corrección de un fallo en la API 31 (versión de Android más reciente).
|
||||
- Corrección de resultados de búsqueda vacíos.
|
@ -1,2 +0,0 @@
|
||||
Corrección de errores
|
||||
- Corrección de un fallo al descargar la carátula del álbum.
|
@ -13,6 +13,6 @@ Características principales:
|
||||
* Chat del servidor
|
||||
* Y muchas mas!!!
|
||||
|
||||
El código esta disponible con licencia GPL en GitLab: https://gitlab.com/ultrasonic/ultrasonic
|
||||
Si tienes problemas puedes dejar tu petición en: https://gitlab.com/ultrasonic/ultrasonic/issues
|
||||
Icono diseñado por Sebastien Gabriel: http://www.flaticon.com/authors/sebastien-gabriel
|
||||
El código esta disponible con licencia GPL en Github: https://github.com/ultrasonic/ultrasonic
|
||||
Si tienes problemas puedes dejar tu petición en: https://github.com/ultrasonic/ultrasonic/issues
|
||||
Icono diseñado por Sebastien Gabriel: http://www.flaticon.com/authors/sebastien-gabriel
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user