29 Commits
Author SHA1 Message Date
austinried 0465147a7c bump sdk version for google play 2021-08-26 15:19:03 +09:00
austinried 2e96c61b82 let's do some pre-release builds 2021-08-26 14:38:22 +09:00
austinried 08470aaf00 Merge branch 'master' of github.com:austinried/subtracks 2021-08-26 13:41:38 +09:00
aux4 8ee98327fe update readme 2021-08-26 13:41:07 +09:00
austinried a91f455534 add scripts for bumping versions 2021-08-26 13:33:15 +09:00
austinried ac175d091f ignore html for languages 2021-08-26 12:58:35 +09:00
austinried 872dde4e1c remove bad button style 2021-08-26 12:49:26 +09:00
austinried 23807a3506 update header for better dark mode 2021-08-26 12:48:06 +09:00
austinried a3fda31743 added licenses and webview for them
added link to github page
2021-08-26 12:47:56 +09:00
austinried b8f04e5426 update readme 2021-08-26 08:18:29 +09:00
austinried 46eb293f98 fix net state not being right on startup 2021-08-26 08:17:39 +09:00
austinried 5af187ba48 lint builds 2021-08-26 08:07:32 +09:00
austinried 9847cdb894 fix for search clearing text/not scrolling 2021-08-25 16:32:04 +09:00
austinried fac2b62ec2 improved large album/playlist performance
switched to flatlist for all of those
2021-08-25 16:13:35 +09:00
austinried 81b5bfb56b readme update 2021-08-25 11:26:03 +09:00
austinried 7abf5c0e88 settings wording 2021-08-25 10:43:36 +09:00
austinried ed4a32cba3 remove UI option to disable estimateContentLength 2021-08-25 10:29:59 +09:00
austinried 0778c49b18 Merge branch 'master' of github.com:austinried/subtracks 2021-08-25 10:25:03 +09:00
austinried 3db1d795dc fix player crashing at the end of transcoded track 2021-08-25 10:24:42 +09:00
aux4 07fc618fd2 specify android app in readme 2021-08-24 16:00:21 +09:00
austinried 75da5d16ac Merge branch 'master' of github.com:austinried/subtracks 2021-08-24 15:58:01 +09:00
austinried 1d40ac5b35 stop building on non-app file changes 2021-08-24 15:57:46 +09:00
aux4 6bdd052472 added download badge to readme 2021-08-24 15:52:34 +09:00
austinried 74632eff10 added "get it on" badges" 2021-08-24 15:49:41 +09:00
austinried 3055726fb3 better name/group artifacts 2021-08-24 15:31:22 +09:00
aux4 d10a29b931 fix readme link, add server examples 2021-08-24 14:46:07 +09:00
aux4 75a264ca83 fix readme image formatting 2021-08-24 14:27:27 +09:00
austinried dcd764bd68 add images/features/coming soon to readme 2021-08-24 14:23:00 +09:00
aux4 1a596077fa Merge pull request #1 from austinried/github-actions-test
Use actions to build and sign PRs/releases
2021-08-24 11:27:12 +09:00
43 changed files with 25260 additions and 188 deletions
+3
View File
@@ -1,3 +1,6 @@
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf
# ignore these paths for GitHub language percentages
android/app/src/main/assets/licenses/* linguist-vendored=true
+20 -3
View File
@@ -2,10 +2,18 @@ name: build-release-debugsign
on:
push:
paths-ignore:
- assets/**
- README.md
- LICENSE
branches:
- master
- release/*
pull_request:
paths-ignore:
- assets/**
- README.md
- LICENSE
branches:
- master
@@ -19,7 +27,11 @@ jobs:
node-version: '14'
cache: 'yarn'
- run: |
yarn
yarn install
- run: |
yarn lint --max-warnings 0
- run: |
yarn license
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/gradle-build-action@v1
with:
@@ -28,7 +40,12 @@ jobs:
configuration-cache-enabled: true
build-root-directory: android
arguments: assembleRelease -PsignReleaseWithDebug=true
- run: |
mkdir bin
- run: >
mv android/app/build/outputs/apk/release/app-release.apk
bin/subtracks-${GITHUB_SHA}.apk
- uses: actions/upload-artifact@v2
with:
name: app-release.apk
path: android/app/build/outputs/apk/release/app-release.apk
name: subtracks-testing
path: bin/*
+16 -7
View File
@@ -14,8 +14,13 @@ jobs:
with:
node-version: '14'
cache: 'yarn'
- name: Get subtracks version
run: |
echo "SUBTRACKS_VERSION=$(node -e "console.log(require('./package.json').version);")" >> $GITHUB_ENV
- run: |
yarn
yarn install
- run: |
yarn license
- uses: gradle/wrapper-validation-action@v1
- name: Retrieve keystore file
env:
@@ -33,10 +38,11 @@ jobs:
-PSUBTRACKS_UPLOAD_KEY_ALIAS=subtracks
-PSUBTRACKS_UPLOAD_KEY_PASSWORD="$SUBTRACKS_UPLOAD_KEY_PASSWORD"
-PSUBTRACKS_UPLOAD_STORE_PASSWORD="$SUBTRACKS_UPLOAD_STORE_PASSWORD"
- uses: actions/upload-artifact@v2
with:
name: app-release.apk
path: android/app/build/outputs/apk/release/app-release.apk
- run: |
mkdir bin
- run: >
mv android/app/build/outputs/apk/release/app-release.apk
bin/subtracks-${SUBTRACKS_VERSION}.apk
- name: Build release AAB
env:
SUBTRACKS_UPLOAD_KEY_PASSWORD: ${{ secrets.SUBTRACKS_UPLOAD_KEY_PASSWORD }}
@@ -48,7 +54,10 @@ jobs:
-PSUBTRACKS_UPLOAD_KEY_ALIAS=subtracks
-PSUBTRACKS_UPLOAD_KEY_PASSWORD="$SUBTRACKS_UPLOAD_KEY_PASSWORD"
-PSUBTRACKS_UPLOAD_STORE_PASSWORD="$SUBTRACKS_UPLOAD_STORE_PASSWORD"
- run: >
mv android/app/build/outputs/bundle/release/app-release.aab
bin/subtracks-${SUBTRACKS_VERSION}.aab
- uses: actions/upload-artifact@v2
with:
name: app-release.aab
path: android/app/build/outputs/bundle/release/app-release.aab
name: subtracks-release
path: bin/*
+38 -2
View File
@@ -1,2 +1,38 @@
# Subtracks
Subtracks is an open source music streaming app for [Subsonic-compatible](http://www.subsonic.org/pages/api.jsp) servers. Its primary goals are to give you fast, clean, and beautiful access to your music in the style of modern media player apps.
<img src="assets/header.png" alt="subtracks logo" width="500"/>
#
Subtracks is an Android open source music streaming app for [Subsonic-API-compatible](http://www.subsonic.org/pages/api.jsp) servers ([Subsonic](http://www.subsonic.org/pages/index.jsp), [Navidrome](https://www.navidrome.org/), [Airsonic](https://airsonic.github.io/), and more). It's designed to give you clean and convenient access to your music in the style of modern media players.
# Screenshots
<p float="left">
<img src="assets/screenshots/01_home.png" alt="home" width="200"/>
<img src="assets/screenshots/02_now-playing.png" alt="now playing" width="200"/>
<img src="assets/screenshots/03_library-albums.png" alt="library albums" width="200"/>
<img src="assets/screenshots/04_album.png" alt="album view" width="200"/>
</p>
# Download
<p float="left">
<a href="https://github.com/austinried/subtracks/releases/latest" disabled><img src="assets/github-badge.png" width="250"/></a>
</p>
# Features
- Album and artist art display by default (full-res in detail/now playing views)
- Mulitple server support
- Filterable/sortable album and artist library views
- Search artists, albums, and songs
- Scrobble support
- Max bitrate and buffer size settings
- Loop and shuffle playback options
- Long-press for context menu shortcuts
# Coming Soon™
- Offline support
- Customizable home screen categories
- Browse by folder support (currently only browses by tags)
- Multiple music folder awareness
- By year/by genre albums filter
- Playlist mangement (add/remove/sort/edit)
- Queue management (add/remove/sort)
- Multi-select actions
- Performance & animation improvements
+74 -1
View File
@@ -135,7 +135,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
versionName '0.1.0'
}
splits {
abi {
@@ -230,3 +230,76 @@ task copyDownloadableDepsToLibs(type: Copy) {
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
class Version {
private int major
private int minor
private int patch
private int code
Version(int code, String version) {
this.code = code
def (major, minor, patch) = version.tokenize('.')
this.major = major.toInteger()
this.minor = minor.toInteger()
this.patch = patch.toInteger()
}
@SuppressWarnings("unused")
void bumpMajor() {
major += 1
minor = 0
patch = 0
code += 1
}
@SuppressWarnings("unused")
void bumpMinor() {
minor += 1
patch = 0
code += 1
}
@SuppressWarnings("unused")
void bumpPatch() {
patch += 1
code += 1
}
String getName() { "$major.$minor.$patch" }
int getCode() { code }
}
tasks.addRule("Pattern: bump<TYPE>Version") { String taskName ->
if (taskName.matches("bump(Major|Minor|Patch)Version")) {
task(taskName) {
doLast {
String type = (taskName - 'bump' - 'Version')
println "Bumping ${type.toLowerCase()} version…"
int oldVersionCode = android.defaultConfig.versionCode
String oldVersionName = android.defaultConfig.versionName
version = new Version(oldVersionCode, oldVersionName)
version."bump$type"()
String newVersionName = version.getName()
String newVersionCode = version.getCode()
println "$oldVersionName ($oldVersionCode) → $newVersionName ($newVersionCode)"
def updated = buildFile.getText()
updated = updated.replaceFirst("versionName '$oldVersionName'", "versionName '$newVersionName'")
updated = updated.replaceFirst("versionCode $oldVersionCode", "versionCode $newVersionCode")
buildFile.setText(updated)
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+12 -3
View File
@@ -2,10 +2,10 @@
buildscript {
ext {
buildToolsVersion = "29.0.3"
buildToolsVersion = "30.0.3"
minSdkVersion = 21
compileSdkVersion = 29
targetSdkVersion = 29
compileSdkVersion = 30
targetSdkVersion = 30
ndkVersion = "20.1.5948944"
// react-native-async-storage next
@@ -14,6 +14,9 @@ buildscript {
repositories {
google()
jcenter()
maven {
url 'https://plugins.gradle.org/m2/'
}
}
dependencies {
classpath("com.android.tools.build:gradle:4.1.0")
@@ -22,8 +25,14 @@ buildscript {
// react-native-async-storage next
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "gradle.plugin.com.cookpad.android.plugin:plugin:1.2.8"
}
}
apply plugin: "com.cookpad.android.plugin.license-tools"
licenseTools {
outputHtml = "../../../app/src/main/assets/licenses/android_licenses.html"
}
allprojects {
repositories {
+482
View File
@@ -0,0 +1,482 @@
- artifact: androidx.activity:activity:+
name: activity
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.annotation:annotation:+
name: annotation
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.appcompat:appcompat-resources:+
name: appcompat-resources
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.appcompat:appcompat:+
name: appcompat
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.arch.core:core-common:+
name: core-common
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.arch.core:core-runtime:+
name: core-runtime
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.asynclayoutinflater:asynclayoutinflater:+
name: asynclayoutinflater
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.cardview:cardview:+
name: cardview
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.collection:collection:+
name: collection
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.coordinatorlayout:coordinatorlayout:+
name: coordinatorlayout
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.core:core:+
name: core
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.cursoradapter:cursoradapter:+
name: cursoradapter
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.customview:customview:+
name: customview
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.documentfile:documentfile:+
name: documentfile
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.drawerlayout:drawerlayout:+
name: drawerlayout
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.fragment:fragment:+
name: fragment
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.interpolator:interpolator:+
name: interpolator
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.legacy:legacy-support-core-ui:+
name: legacy-support-core-ui
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.legacy:legacy-support-core-utils:+
name: legacy-support-core-utils
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-common:+
name: lifecycle-common
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-livedata-core:+
name: lifecycle-livedata-core
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-livedata:+
name: lifecycle-livedata
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-runtime:+
name: lifecycle-runtime
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-viewmodel-savedstate:+
name: lifecycle-viewmodel-savedstate
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-viewmodel:+
name: lifecycle-viewmodel
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.loader:loader:+
name: loader
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.localbroadcastmanager:localbroadcastmanager:+
name: localbroadcastmanager
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.media:media:+
name: media
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.palette:palette:+
name: palette
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.print:print:+
name: print
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.recyclerview:recyclerview:+
name: recyclerview
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.room:room-common:+
name: room-common
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.room:room-ktx:+
name: room-ktx
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.room:room-runtime:+
name: room-runtime
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.savedstate:savedstate:+
name: savedstate
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.slidingpanelayout:slidingpanelayout:+
name: slidingpanelayout
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.sqlite:sqlite-framework:+
name: sqlite-framework
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.sqlite:sqlite:+
name: sqlite
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.swiperefreshlayout:swiperefreshlayout:+
name: swiperefreshlayout
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.transition:transition:+
name: transition
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.vectordrawable:vectordrawable-animated:+
name: vectordrawable-animated
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.vectordrawable:vectordrawable:+
name: vectordrawable
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.versionedparcelable:versionedparcelable:+
name: versionedparcelable
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.viewpager2:viewpager2:+
name: viewpager2
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.viewpager:viewpager:+
name: viewpager
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.webkit:webkit:+
name: webkit
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/webkit#1.4.0
- artifact: com.facebook.fbjni:fbjni-java-only:+
name: fbjni-java-only
copyrightHolder: Original Author
license: APACHE-2
licenseUrl: https://github.com/facebookincubator/fbjni/blob/master/LICENSE
url: https://github.com/facebookincubator/fbjni
- artifact: com.facebook.fresco:drawee:+
name: drawee
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.fresco:fbcore:+
name: fbcore
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.fresco:fresco:+
name: fresco
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.fresco:imagepipeline-base:+
name: imagepipeline-base
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.fresco:imagepipeline-okhttp3:+
name: imagepipeline-okhttp3
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.fresco:imagepipeline:+
name: imagepipeline
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.fresco:nativeimagefilters:+
name: nativeimagefilters
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.fresco:nativeimagetranscoder:+
name: nativeimagetranscoder
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/fresco/blob/master/LICENSE
url: https://github.com/facebook/fresco
- artifact: com.facebook.infer.annotation:infer-annotation:+
name: infer-annotation
copyrightHolder: Original Author
license: BSD license
licenseUrl: https://github.com/facebook/infer/blob/master/LICENSE
url: http://fbinfer.com/
- artifact: com.facebook.react:react-native:+
name: react-native
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/react-native/blob/master/LICENSE
url: https://github.com/facebook/react-native
- artifact: com.facebook.soloader:annotation:+
name: annotation
copyrightHolder: Original Author
license: Apache-2
licenseUrl: https://github.com/facebook/soloader/blob/master/LICENSE
url: https://github.com/facebook/soloader
- artifact: com.facebook.soloader:nativeloader:+
name: nativeloader
copyrightHolder: Original Author
license: Apache-2
licenseUrl: https://github.com/facebook/soloader/blob/master/LICENSE
url: https://github.com/facebook/soloader
- artifact: com.facebook.soloader:soloader:+
name: soloader
copyrightHolder: Original Author
license: Apache-2
licenseUrl: https://github.com/facebook/soloader/blob/master/LICENSE
url: https://github.com/facebook/soloader
- artifact: com.facebook.yoga:proguard-annotations:+
name: proguard-annotations
copyrightHolder: Original Author
license: MIT License
licenseUrl: https://github.com/facebook/yoga/blob/master/LICENSE
url: https://github.com/facebook/yoga
- artifact: com.github.bumptech.glide:annotations:+
name: annotations
copyrightHolder: Original Author
license: Simplified BSD License
licenseUrl: http://www.opensource.org/licenses/bsd-license
url: https://github.com/bumptech/glide
- artifact: com.github.bumptech.glide:disklrucache:+
name: disklrucache
copyrightHolder: Original Author
license: Simplified BSD License
licenseUrl: http://www.opensource.org/licenses/bsd-license
url: https://github.com/bumptech/glide
- artifact: com.github.bumptech.glide:gifdecoder:+
name: gifdecoder
copyrightHolder: Original Author
license: Simplified BSD License
licenseUrl: http://www.opensource.org/licenses/bsd-license
url: https://github.com/bumptech/glide
- artifact: com.github.bumptech.glide:glide:+
name: glide
copyrightHolder: Original Author
license: Simplified BSD License
licenseUrl: http://www.opensource.org/licenses/bsd-license
url: https://github.com/bumptech/glide
- artifact: com.google.android.exoplayer:exoplayer-core:+
name: exoplayer-core
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.google.android.exoplayer:exoplayer-dash:+
name: exoplayer-dash
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.google.android.exoplayer:exoplayer-hls:+
name: exoplayer-hls
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.google.android.exoplayer:exoplayer-smoothstreaming:+
name: exoplayer-smoothstreaming
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.google.android.material:material:+
name: material
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.google.code.findbugs:jsr305:+
name: jsr305
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://findbugs.sourceforge.net/
- artifact: com.parse.bolts:bolts-tasks:+
name: bolts-tasks
copyrightHolder: Original Author
license: BSD License
licenseUrl: https://github.com/BoltsFramework/Bolts-Android/blob/master/LICENSE
url: https://github.com/BoltsFramework/Bolts-Android
- artifact: com.squareup.okhttp3:okhttp-urlconnection:+
name: okhttp-urlconnection
copyrightHolder: Original Author
license: Apache-2.0 License
licenseUrl: https://github.com/square/okhttp/blob/master/LICENSE.txt
url: https://github.com/square/okhttp
- artifact: com.squareup.okhttp3:okhttp:+
name: okhttp
copyrightHolder: Original Author
license: Apache-2.0 License
licenseUrl: https://github.com/square/okhttp/blob/master/LICENSE.txt
url: https://github.com/square/okhttp
- artifact: com.squareup.okio:okio:+
name: okio
copyrightHolder: Original Author
license: Apache-2.0 License
licenseUrl: https://github.com/square/okio/blob/master/LICENSE.txt
url: https://github.com/square/okio
- artifact: javax.inject:javax.inject:+
name: javax.inject
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://code.google.com/p/atinject/
- artifact: org.jetbrains.kotlin:kotlin-stdlib-common:+
name: kotlin-stdlib-common
copyrightHolder: Original Author
license: The Apache License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: org.jetbrains.kotlin:kotlin-stdlib:+
name: kotlin-stdlib
copyrightHolder: Original Author
license: The Apache License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-android:+
name: kotlinx-coroutines-android
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:+
name: kotlinx-coroutines-core-jvm
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: org.jetbrains:annotations:+
name: annotations
copyrightHolder: Original Author
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://www.jetbrains.org
@@ -0,0 +1,54 @@
import React from 'react'
import { FlatList, FlatListProps, useWindowDimensions, View, StyleSheet } from 'react-native'
import colors from '@app/styles/colors'
import GradientBackground, { GradientBackgroundProps } from '@app/components/GradientBackground'
import { useLayout } from '@react-native-community/hooks'
import NothingHere from './NothingHere'
import ImageGradientBackground, { ImageGradientBackgroundProps } from './ImageGradientBackground'
export type BackgroundHeaderFlatListPropsBase<ItemT> = FlatListProps<ItemT> & {
contentMarginTop?: number
}
export type BackgroundHeaderFlatListProp<ItemT> = BackgroundHeaderFlatListPropsBase<ItemT> & {
BackgroundComponent: typeof ImageGradientBackground | typeof GradientBackground
backgroundProps?: ImageGradientBackgroundProps | GradientBackgroundProps
}
function BackgroundHeaderFlatList<ItemT>(props: BackgroundHeaderFlatListProp<ItemT>) {
const window = useWindowDimensions()
const headerLayout = useLayout()
let marginBottom = -window.height + (props.contentMarginTop || 0)
if (props.ListHeaderComponent) {
marginBottom += headerLayout.height || window.height
}
const headerStyle = { marginBottom }
return (
<FlatList
{...props}
contentContainerStyle={[props.contentContainerStyle, { minHeight: window.height }]}
style={[props.style, styles.list]}
ListHeaderComponent={
<props.BackgroundComponent position="relative" {...props.backgroundProps}>
<View onLayout={headerLayout.onLayout}>{props.ListHeaderComponent}</View>
</props.BackgroundComponent>
}
ListHeaderComponentStyle={[headerStyle]}
ListEmptyComponent={<NothingHere style={styles.nothing} />}
/>
)
}
const styles = StyleSheet.create({
list: {
backgroundColor: colors.gradient.low,
},
nothing: {
width: '100%',
},
})
export default BackgroundHeaderFlatList
+15 -2
View File
@@ -3,14 +3,27 @@ import { useWindowDimensions, ViewStyle } from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import colorStyles from '@app/styles/colors'
const GradientBackground: React.FC<{
export type GradientBackgroundPropsBase = {
height?: number | string
width?: number | string
position?: 'relative' | 'absolute'
style?: ViewStyle
}
export type GradientBackgroundProps = GradientBackgroundPropsBase & {
colors?: string[]
locations?: number[]
}> = ({ height, width, position, style, colors, locations, children }) => {
}
const GradientBackground: React.FC<GradientBackgroundProps> = ({
height,
width,
position,
style,
colors,
locations,
children,
}) => {
const layout = useWindowDimensions()
return (
+7 -22
View File
@@ -1,28 +1,13 @@
import GradientBackground, { GradientBackgroundProps } from '@app/components/GradientBackground'
import React from 'react'
import { FlatList, FlatListProps, StyleSheet, useWindowDimensions } from 'react-native'
import colors from '@app/styles/colors'
import GradientBackground from '@app/components/GradientBackground'
import BackgroundHeaderFlatList, { BackgroundHeaderFlatListPropsBase } from './BackgroundHeaderFlatList'
function GradientFlatList<ItemT>(props: FlatListProps<ItemT>) {
const layout = useWindowDimensions()
export type GradientFlatListProps<ItemT> = BackgroundHeaderFlatListPropsBase<ItemT> & {
backgroundProps?: GradientBackgroundProps
}
const contentContainerStyle = StyleSheet.flatten(props.contentContainerStyle)
return (
<FlatList
{...props}
style={{
...(props.style as any),
backgroundColor: colors.gradient.low,
}}
ListHeaderComponent={() => <GradientBackground position="relative" />}
ListHeaderComponentStyle={{
marginBottom: -layout.height,
marginHorizontal: -(contentContainerStyle.paddingHorizontal || 0),
top: -(contentContainerStyle.paddingTop || 0),
}}
/>
)
function GradientFlatList<ItemT>(props: GradientFlatListProps<ItemT>) {
return <BackgroundHeaderFlatList BackgroundComponent={GradientBackground} {...props} />
}
export default GradientFlatList
+8 -5
View File
@@ -1,21 +1,24 @@
import GradientBackground from '@app/components/GradientBackground'
import colors from '@app/styles/colors'
import dimensions from '@app/styles/dimensions'
import React from 'react'
import { ScrollViewProps, useWindowDimensions } from 'react-native'
import React, { ForwardedRef, PropsWithChildren } from 'react'
import { ScrollView, ScrollViewProps, useWindowDimensions } from 'react-native'
import Animated from 'react-native-reanimated'
const GradientScrollView: React.FC<
export type GradientScrollViewProps = PropsWithChildren<
Animated.AnimateProps<ScrollViewProps> & {
offset?: number
}
> = props => {
>
const GradientScrollView = React.forwardRef<ScrollView, GradientScrollViewProps>((props, ref) => {
const layout = useWindowDimensions()
const minHeight = layout.height - (dimensions.top() + dimensions.bottom())
return (
<Animated.ScrollView
ref={ref as ForwardedRef<Animated.ScrollView>}
overScrollMode="never"
{...props}
style={[props.style, { backgroundColor: colors.gradient.low }]}
@@ -24,6 +27,6 @@ const GradientScrollView: React.FC<
{props.children}
</Animated.ScrollView>
)
}
})
export default GradientScrollView
+2 -7
View File
@@ -1,16 +1,11 @@
import { useNavigation } from '@react-navigation/native'
import React, { useEffect, useState } from 'react'
import { ViewStyle } from 'react-native'
import ImageColors from 'react-native-image-colors'
import { AndroidImageColors } from 'react-native-image-colors/lib/typescript/types'
import colors from '@app/styles/colors'
import GradientBackground from '@app/components/GradientBackground'
import GradientBackground, { GradientBackgroundPropsBase } from '@app/components/GradientBackground'
export type ImageGradientBackgroundProps = {
height?: number | string
width?: number | string
position?: 'relative' | 'absolute'
style?: ViewStyle
export type ImageGradientBackgroundProps = GradientBackgroundPropsBase & {
imagePath?: string
onGetColor?: (color: string) => void
}
+13
View File
@@ -0,0 +1,13 @@
import React from 'react'
import BackgroundHeaderFlatList, { BackgroundHeaderFlatListPropsBase } from './BackgroundHeaderFlatList'
import ImageGradientBackground, { ImageGradientBackgroundProps } from './ImageGradientBackground'
export type ImageGradientFlatListProps<ItemT> = BackgroundHeaderFlatListPropsBase<ItemT> & {
backgroundProps?: ImageGradientBackgroundProps
}
function ImageGradientFlatList<ItemT>(props: ImageGradientFlatListProps<ItemT>) {
return <BackgroundHeaderFlatList BackgroundComponent={ImageGradientBackground} {...props} />
}
export default ImageGradientFlatList
+4 -3
View File
@@ -7,7 +7,7 @@ import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { useNavigation } from '@react-navigation/native'
import React, { useCallback } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native'
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
import IconMat from 'react-native-vector-icons/MaterialIcons'
import { AlbumContextPressable, ArtistContextPressable, SongContextPressable } from './ContextMenu'
@@ -55,7 +55,8 @@ const ListItem: React.FC<{
showStar?: boolean
listStyle?: 'big' | 'small'
subtitle?: string
}> = ({ item, contextId, queueId, onPress, showArt, showStar, subtitle, listStyle }) => {
style?: StyleProp<ViewStyle>
}> = ({ item, contextId, queueId, onPress, showArt, showStar, subtitle, listStyle, style }) => {
const navigation = useNavigation()
const starred = useStarred(item.id, item.itemType)
@@ -156,7 +157,7 @@ const ListItem: React.FC<{
}
return (
<View style={[styles.container, sizeStyle.container]}>
<View style={[styles.container, sizeStyle.container, style]}>
<PressableComponent>
{showArt && coverArt}
<View style={styles.text}>
+21 -1
View File
@@ -7,6 +7,7 @@ import SearchResultsView from '@app/screens/SearchResultsView'
import ServerView from '@app/screens/ServerView'
import SettingsView from '@app/screens/Settings'
import SongListView from '@app/screens/SongListView'
import WebViewScreen from '@app/screens/WebViewScreen'
import { selectSettings } from '@app/state/settings'
import { useStore } from '@app/state/store'
import colors from '@app/styles/colors'
@@ -122,6 +123,7 @@ const SearchTab = createTabStackNavigator(Search)
type SettingsStackParamList = {
main: undefined
server?: { id?: string }
web: { uri: string }
}
type ServerScreenNavigationProp = NativeStackNavigationProp<SettingsStackParamList, 'server'>
@@ -130,9 +132,16 @@ type ServerScreenProps = {
route: ServerScreenRouteProp
navigation: ServerScreenNavigationProp
}
const ServerScreen: React.FC<ServerScreenProps> = ({ route }) => <ServerView id={route.params?.id} />
type WebScreenNavigationProp = NativeStackNavigationProp<SettingsStackParamList, 'web'>
type WebScreenRouteProp = RouteProp<SettingsStackParamList, 'web'>
type WebScreenProps = {
route: WebScreenRouteProp
navigation: WebScreenNavigationProp
}
const WebScreen: React.FC<WebScreenProps> = ({ route }) => <WebViewScreen uri={route.params.uri} />
const SettingsStack = createNativeStackNavigator()
const SettingsTab = () => {
@@ -150,6 +159,17 @@ const SettingsTab = () => {
headerTitleStyle: styles.stackheaderTitleStyle,
}}
/>
<SettingsStack.Screen
name="web"
component={WebScreen}
options={{
title: 'Web View',
headerStyle: styles.stackheaderStyle,
headerHideShadow: true,
headerTintColor: 'white',
headerTitleStyle: styles.stackheaderTitleStyle,
}}
/>
</SettingsStack.Navigator>
)
}
+20
View File
@@ -28,7 +28,14 @@ const setNetState = (netState: 'mobile' | 'wifi') => {
})
}
const rebuildQueue = () => {
unstable_batchedUpdates(() => {
useStore.getState().rebuildQueue(useStore.getState().playerState === State.Playing)
})
}
let serviceCreated = false
const createService = async () => {
useStore.subscribe(
(currentTrack?: TrackExt) => {
@@ -40,6 +47,10 @@ const createService = async () => {
(prev, next) => prev?.id === next?.id,
)
NetInfo.fetch().then(state => {
setNetState(state.type === NetInfoStateType.cellular ? 'mobile' : 'wifi')
})
NetInfo.addEventListener(state => {
const currentType = useStore.getState().netState
const newType = state.type === NetInfoStateType.cellular ? 'mobile' : 'wifi'
@@ -112,6 +123,15 @@ const createService = async () => {
await TrackPlayer.seekTo(data.position)
})
})
TrackPlayer.addEventListener(Event.PlaybackError, data => {
const { code, message } = data as Record<string, string>
// fix for ExoPlayer aborting playback while esimating content length
if (code === 'playback-source' && message.includes('416')) {
rebuildQueue()
}
})
}
module.exports = async function () {
-4
View File
@@ -72,7 +72,6 @@ const AlbumsList = () => {
return (
<View style={styles.container}>
<GradientFlatList
contentContainerStyle={styles.listContent}
data={list.map(album => ({ album, size, height }))}
renderItem={AlbumListRenderItem}
keyExtractor={item => item.album.id}
@@ -100,9 +99,6 @@ const AlbumsList = () => {
}
const styles = StyleSheet.create({
listContent: {
minHeight: '100%',
},
container: {
flex: 1,
},
+3 -5
View File
@@ -11,7 +11,7 @@ import React, { useEffect, useState } from 'react'
import { StyleSheet, View } from 'react-native'
const ArtistRenderItem: React.FC<{ item: Artist }> = ({ item }) => (
<ListItem item={item} showArt={true} showStar={false} listStyle="big" />
<ListItem item={item} showArt={true} showStar={false} listStyle="big" style={styles.listItem} />
)
const filterOptions: OptionData[] = [
@@ -44,7 +44,6 @@ const ArtistsList = () => {
return (
<View style={styles.container}>
<GradientFlatList
contentContainerStyle={styles.listContent}
data={sortedList}
renderItem={ArtistRenderItem}
keyExtractor={item => item.id}
@@ -52,6 +51,7 @@ const ArtistsList = () => {
refreshing={refreshing}
overScrollMode="never"
windowSize={3}
contentMarginTop={6}
/>
<FilterButton
data={filterOptions}
@@ -71,10 +71,8 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
listContent: {
minHeight: '100%',
listItem: {
paddingHorizontal: 10,
paddingTop: 6,
},
})
+3 -5
View File
@@ -8,7 +8,7 @@ import React from 'react'
import { StyleSheet } from 'react-native'
const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => (
<ListItem item={item} showArt={true} showStar={false} listStyle="big" />
<ListItem item={item} showArt={true} showStar={false} listStyle="big" style={styles.listItem} />
)
const PlaylistsList = () => {
@@ -17,7 +17,6 @@ const PlaylistsList = () => {
return (
<GradientFlatList
contentContainerStyle={styles.listContent}
data={list}
renderItem={PlaylistRenderItem}
keyExtractor={item => item.id}
@@ -25,15 +24,14 @@ const PlaylistsList = () => {
refreshing={refreshing}
overScrollMode="never"
windowSize={5}
contentMarginTop={6}
/>
)
}
const styles = StyleSheet.create({
listContent: {
minHeight: '100%',
listItem: {
paddingHorizontal: 10,
paddingTop: 6,
},
})
+29 -22
View File
@@ -1,49 +1,56 @@
import GradientScrollView from '@app/components/GradientScrollView'
import GradientFlatList from '@app/components/GradientFlatList'
import ListItem from '@app/components/ListItem'
import NowPlayingBar from '@app/components/NowPlayingBar'
import { useSkipTo } from '@app/hooks/trackplayer'
import { Song } from '@app/models/music'
import { useStore } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import { selectTrackPlayerMap } from '@app/state/trackplayermap'
import React from 'react'
import { StyleSheet, View } from 'react-native'
const SongRenderItem: React.FC<{
item: {
song: Song
i: number
onPress: () => void
}
}> = ({ item }) => (
<ListItem
item={item.song}
queueId={item.i}
onPress={item.onPress}
showArt={true}
style={styles.listItem}
subtitle={`${item.song.artist} • ${item.song.album}`}
/>
)
const NowPlayingQueue = React.memo<{}>(() => {
const queue = useStore(selectTrackPlayer.queue)
const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong)
const skipTo = useSkipTo()
return (
<View style={styles.outerContainer}>
<GradientScrollView style={styles.container}>
<View style={styles.content}>
{queue.map(mapTrackExtToSong).map((song, i) => (
<ListItem
key={i}
item={song}
queueId={i}
onPress={() => skipTo(i)}
showArt={true}
subtitle={`${song.artist} • ${song.album}`}
/>
))}
</View>
</GradientScrollView>
<View style={styles.container}>
<GradientFlatList
data={queue.map(mapTrackExtToSong).map((song, i) => ({ song, i, onPress: () => skipTo(i) }))}
renderItem={SongRenderItem}
keyExtractor={(item, i) => i.toString()}
overScrollMode="never"
windowSize={7}
contentMarginTop={10}
/>
<NowPlayingBar />
</View>
)
})
const styles = StyleSheet.create({
outerContainer: {
flex: 1,
},
container: {
flex: 1,
},
content: {
alignItems: 'center',
paddingTop: 10,
listItem: {
paddingHorizontal: 20,
},
})
+6 -2
View File
@@ -17,6 +17,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'
import {
ActivityIndicator,
InteractionManager,
ScrollView,
StatusBar,
StyleSheet,
TextInput as ReactTextInput,
@@ -90,17 +91,20 @@ const Search = () => {
const [refreshing, setRefreshing] = useState(false)
const [text, setText] = useState('')
const searchBarRef = useRef<ReactTextInput>(null)
const scrollRef = useRef<ScrollView>(null)
useFocusEffect(
useCallback(() => {
const task = InteractionManager.runAfterInteractions(() => {
setTimeout(() => {
setText('')
setResults({ artists: [], albums: [], songs: [] })
searchBarRef.current?.focus()
scrollRef.current?.scrollTo({ y: 0, animated: true })
}, 50)
})
return () => task.cancel()
}, [searchBarRef]),
}, [searchBarRef, scrollRef]),
)
useActiveServerRefresh(
@@ -131,7 +135,7 @@ const Search = () => {
const resultsCount = results.albums.length + results.artists.length + results.songs.length
return (
<GradientScrollView style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
<GradientScrollView ref={scrollRef} style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
<View style={styles.content}>
<View style={styles.inputBar}>
<TextInput
+5 -5
View File
@@ -28,6 +28,7 @@ const ResultsListItem: React.FC<{ item: SearchListItemType }> = ({ item }) => {
showStar={false}
listStyle="small"
onPress={onPress}
style={styles.listItem}
/>
)
}
@@ -69,25 +70,24 @@ const SearchResultsView: React.FC<{
return (
<GradientFlatList
contentContainerStyle={styles.listContent}
data={list}
renderItem={SearchResultsRenderItem}
keyExtractor={item => item.id}
keyExtractor={(item, i) => i.toString()}
onRefresh={refresh}
refreshing={refreshing}
overScrollMode="never"
onEndReached={fetchNextPage}
removeClippedSubviews={true}
onEndReachedThreshold={2}
contentMarginTop={6}
windowSize={5}
/>
)
}
const styles = StyleSheet.create({
listContent: {
minHeight: '100%',
listItem: {
paddingHorizontal: 10,
paddingTop: 6,
},
})
+41 -12
View File
@@ -14,7 +14,7 @@ import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { useNavigation } from '@react-navigation/core'
import React, { useCallback, useState } from 'react'
import { KeyboardTypeOptions, Modal, Pressable, StatusBar, StyleSheet, Text, View } from 'react-native'
import { KeyboardTypeOptions, Linking, Modal, Pressable, StatusBar, StyleSheet, Text, View } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
@@ -195,8 +195,11 @@ const SettingsContent = React.memo(() => {
const servers = useStore(selectSettings.servers)
const scrobble = useStore(selectSettings.scrobble)
const setScrobble = useStore(selectSettings.setScrobble)
const estimateContentLength = useStore(selectSettings.estimateContentLength)
const setEstimateContentLength = useStore(selectSettings.setEstimateContentLength)
// doesn't seem to ever be a case where we want this off
// will remove later if there isn't a use case for disabling
// const estimateContentLength = useStore(selectSettings.estimateContentLength)
// const setEstimateContentLength = useStore(selectSettings.setEstimateContentLength)
const maxBitrateWifi = useStore(selectSettings.maxBitrateWifi)
const setMaxBitrateWifi = useStore(selectSettings.setMaxBitrateWifi)
@@ -245,14 +248,14 @@ const SettingsContent = React.memo(() => {
buttonStyle="hollow"
/>
<Header style={styles.header}>Network</Header>
<BitrateModal title="Max bitrate (Wi-Fi)" bitrate={maxBitrateWifi} setBitrate={setMaxBitrateWifi} />
<BitrateModal title="Max bitrate (mobile)" bitrate={maxBitrateMobile} setBitrate={setMaxBitrateMobile} />
<SettingsSwitch
<BitrateModal title="Maximum bitrate (Wi-Fi)" bitrate={maxBitrateWifi} setBitrate={setMaxBitrateWifi} />
<BitrateModal title="Maximum bitrate (mobile)" bitrate={maxBitrateMobile} setBitrate={setMaxBitrateMobile} />
{/* <SettingsSwitch
title="Estimate content length"
subtitle='Send the "estimateContentLength" flag when streaming. Helps fix issues with seeking when the server is transcoding songs.'
value={estimateContentLength}
setValue={setEstimateContentLength}
/>
/> */}
<SettingsTextModal
title="Minimum buffer time"
value={minBuffer.toString()}
@@ -278,10 +281,32 @@ const SettingsContent = React.memo(() => {
<Button
disabled={clearing}
style={styles.button}
title="Clear image cache"
title="Clear Image Cache"
onPress={clear}
buttonStyle="hollow"
/>
<Header style={styles.header}>About</Header>
<Button
disabled={clearing}
style={styles.button}
title="Project Homepage"
onPress={() => Linking.openURL('https://github.com/austinried/subtracks')}
buttonStyle="hollow"
/>
<Button
disabled={clearing}
style={styles.button}
title="Licenses (NPM)"
onPress={() => navigation.navigate('web', { uri: 'file:///android_asset/licenses/npm_licenses.txt' })}
buttonStyle="hollow"
/>
<Button
disabled={clearing}
style={styles.button}
title="Licenses (Android)"
onPress={() => navigation.navigate('web', { uri: 'file:///android_asset/licenses/android_licenses.html' })}
buttonStyle="hollow"
/>
</View>
)
})
@@ -305,9 +330,6 @@ const styles = StyleSheet.create({
paddingHorizontal: 20,
paddingBottom: 40,
},
text: {
color: 'white',
},
serverActive: {
paddingLeft: 12,
},
@@ -317,6 +339,14 @@ const styles = StyleSheet.create({
button: {
marginTop: 16,
},
licenseButton: {
marginHorizontal: 10,
flex: 1,
},
licenses: {
flexDirection: 'row',
justifyContent: 'space-between',
},
modalBackdrop: {
flex: 1,
},
@@ -365,7 +395,6 @@ const styles = StyleSheet.create({
},
modalTextSubmit: {
marginLeft: 15,
// backgroundColor: 'green',
},
})
+74 -63
View File
@@ -1,10 +1,9 @@
import CoverArt from '@app/components/CoverArt'
import GradientBackground from '@app/components/GradientBackground'
import HeaderBar from '@app/components/HeaderBar'
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
import ImageGradientFlatList from '@app/components/ImageGradientFlatList'
import ListItem from '@app/components/ListItem'
import ListPlayerControls from '@app/components/ListPlayerControls'
import NothingHere from '@app/components/NothingHere'
import { useCoverArtFile } from '@app/hooks/cache'
import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music'
import { AlbumWithSongs, PlaylistWithSongs, Song } from '@app/models/music'
@@ -23,15 +22,42 @@ const SongListDetailsFallback = React.memo(() => (
</GradientBackground>
))
const Songs = React.memo<{
songs: Song[]
name: string
const SongRenderItem: React.FC<{
item: {
song: Song
contextId?: string
queueId?: number
subtitle?: string
onPress?: () => void
showArt?: boolean
}
}> = ({ item }) => (
<ListItem
item={item.song}
contextId={item.contextId}
queueId={item.queueId}
subtitle={item.subtitle}
onPress={item.onPress}
showArt={item.showArt}
style={styles.listItem}
/>
)
const SongListDetails = React.memo<{
title: string
type: SongListType
itemId: string
}>(({ songs, name, type, itemId }) => {
songList?: AlbumWithSongs | PlaylistWithSongs
subtitle?: string
}>(({ title, songList, subtitle, type }) => {
const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail')
const [headerColor, setHeaderColor] = useState<string | undefined>(undefined)
const setQueue = useStore(selectTrackPlayer.setQueue)
const _songs = [...songs]
if (!songList) {
return <SongListDetailsFallback />
}
const _songs = [...songList.songs]
let typeName = ''
if (type === 'album') {
@@ -49,46 +75,6 @@ const Songs = React.memo<{
typeName = 'Playlist'
}
return (
<>
<ListPlayerControls
style={styles.controls}
songs={_songs}
typeName={typeName}
queueName={name}
queueContextId={itemId}
queueContextType={type}
/>
<View style={styles.songs}>
{_songs.map((s, i) => (
<ListItem
key={i}
item={s}
contextId={itemId}
queueId={i}
subtitle={s.artist}
onPress={() => setQueue(_songs, name, type, itemId, i)}
showArt={false}
/>
))}
</View>
</>
)
})
const SongListDetails = React.memo<{
title: string
type: SongListType
songList?: AlbumWithSongs | PlaylistWithSongs
subtitle?: string
}>(({ title, songList, subtitle, type }) => {
const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail')
const [headerColor, setHeaderColor] = useState<string | undefined>(undefined)
if (!songList) {
return <SongListDetailsFallback />
}
return (
<View style={styles.container}>
<HeaderBar
@@ -96,21 +82,43 @@ const SongListDetails = React.memo<{
title={title}
contextItem={songList.itemType === 'album' ? songList : undefined}
/>
<ImageGradientScrollView
imagePath={coverArtFile?.file?.path}
style={styles.container}
onGetColor={setHeaderColor}>
<View style={styles.content}>
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
<Text style={styles.title}>{songList.name}</Text>
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
{songList.songs.length > 0 ? (
<Songs songs={songList.songs} name={songList.name} type={type} itemId={songList.id} />
) : (
<NothingHere height={300} width={250} />
)}
</View>
</ImageGradientScrollView>
<ImageGradientFlatList
data={_songs.map((s, i) => ({
song: s,
contextId: songList.id,
queueId: i,
subtitle: s.artist,
onPress: () => setQueue(_songs, songList.name, type, songList.id, i),
showArt: songList.itemType === 'playlist',
}))}
renderItem={SongRenderItem}
keyExtractor={(item, i) => i.toString()}
backgroundProps={{
imagePath: coverArtFile?.file?.path,
style: styles.container,
onGetColor: setHeaderColor,
}}
overScrollMode="never"
windowSize={7}
contentMarginTop={26}
ListHeaderComponent={
<View style={styles.content}>
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
<Text style={styles.title}>{songList.name}</Text>
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
{songList.songs.length > 0 && (
<ListPlayerControls
style={styles.controls}
songs={_songs}
typeName={typeName}
queueName={songList.name}
queueContextId={songList.id}
queueContextType={type}
/>
)}
</View>
}
/>
</View>
)
})
@@ -185,6 +193,9 @@ const styles = StyleSheet.create({
alignItems: 'center',
paddingTop: 100,
},
listItem: {
paddingHorizontal: 20,
},
})
export default SongListView
+10
View File
@@ -0,0 +1,10 @@
import React from 'react'
import { WebView } from 'react-native-webview'
const WebViewScreen: React.FC<{
uri: string
}> = ({ uri }) => {
return <WebView source={{ uri }} />
}
export default WebViewScreen
+1 -2
View File
@@ -135,8 +135,7 @@ export const createMusicMapSlice = (set: SetState<Store>, get: GetState<Store>):
mapPlaylistWithSongs: async playlist => {
return {
...get().mapPlaylistListItem(playlist),
// passing cover art here is a temp fix to improve large playlist performance
songs: await get().mapChildrenToSongs(playlist.songs, playlist.coverArt),
songs: await get().mapChildrenToSongs(playlist.songs),
coverArt: playlist.coverArt,
}
},
+3 -3
View File
@@ -64,7 +64,7 @@ export type TrackPlayerSlice = {
netState: 'mobile' | 'wifi'
setNetState: (netState: 'mobile' | 'wifi') => Promise<void>
rebuildQueue: () => Promise<void>
rebuildQueue: (forcePlay?: boolean) => Promise<void>
buildStreamUri: (id: string) => string
resetTrackPlayerState: () => void
@@ -284,7 +284,7 @@ export const createTrackPlayerSlice = (set: SetState<Store>, get: GetState<Store
get().rebuildQueue()
},
rebuildQueue: async () => {
rebuildQueue: async forcePlay => {
return trackPlayerCommands.enqueue(async () => {
const queue = await getQueue()
if (!queue.length) {
@@ -326,7 +326,7 @@ export const createTrackPlayerSlice = (set: SetState<Store>, get: GetState<Store
await TrackPlayer.seekTo(position)
if (state === State.Playing) {
if (state === State.Playing || forcePlay) {
await TrackPlayer.play()
}
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

+11 -3
View File
@@ -1,14 +1,21 @@
{
"name": "subtracks",
"version": "1.0.0",
"version": "0.1.0",
"private": true,
"license": "GPL-3.0-only",
"scripts": {
"android": "react-native run-android",
"android-release": "./android/gradlew -p android assembleRelease -PsignReleaseWithDebug=true && adb install -r android/app/build/outputs/apk/release/app-release.apk && adb shell am start -n com.subtracks/.MainActivity",
"android:release": "./android/gradlew -p android assembleRelease -PsignReleaseWithDebug=true && adb install -r android/app/build/outputs/apk/release/app-release.apk && adb shell am start -n com.subtracks/.MainActivity",
"start": "react-native start",
"test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"license:npm": "yarn licenses generate-disclaimer --production | grep -v '^info\\s\"\\?fsevents' > android/app/src/main/assets/licenses/npm_licenses.txt",
"license:android": "./android/gradlew -p android checkLicenses && ./android/gradlew -p android generateLicensePage",
"license:update": "./android/gradlew -p android updateLicenses",
"license": "yarn license:npm && yarn license:android",
"version:patch": "yarn version --patch --no-git-tag-version && ./android/gradlew -p android bumpPatchVersion",
"version:minor": "yarn version --minor --no-git-tag-version && ./android/gradlew -p android bumpMinorVersion",
"version:major": "yarn version --major --no-git-tag-version && ./android/gradlew -p android bumpMajorVersion"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.15.5",
@@ -36,6 +43,7 @@
"react-native-tab-view": "^2.16.0",
"react-native-track-player": "^2.0.3",
"react-native-vector-icons": "^8.1.0",
"react-native-webview": "^11.13.0",
"uuid": "^8.3.2",
"xmldom": "^0.5.0",
"zustand": "^3.5.7"
+14 -6
View File
@@ -2648,16 +2648,16 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
@@ -3470,7 +3470,7 @@ interpret@^1.0.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
invariant@^2.2.4:
invariant@2.2.4, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
@@ -5606,6 +5606,14 @@ react-native-vector-icons@^8.1.0:
prop-types "^15.7.2"
yargs "^16.1.1"
react-native-webview@^11.13.0:
version "11.13.0"
resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-11.13.0.tgz#a2eca0f87b2ae9bba0dd8144594aeff9947cc5d6"
integrity sha512-jjQAKWv8JzRmcn76fMe4lXD84AAeR7kn43kAmUe1GX312BMLaP+RbKlgpYAlNuOBXL0YirItGKDrpaD0bNROOA==
dependencies:
escape-string-regexp "2.0.0"
invariant "2.2.4"
react-native@0.64.1:
version "0.64.1"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.64.1.tgz#cd38f5b47b085549686f34eb0c9dcd466f307635"