Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0465147a7c | ||
|
|
2e96c61b82 | ||
|
|
08470aaf00 | ||
|
|
8ee98327fe | ||
|
|
a91f455534 | ||
|
|
ac175d091f | ||
|
|
872dde4e1c | ||
|
|
23807a3506 | ||
|
|
a3fda31743 | ||
|
|
b8f04e5426 | ||
|
|
46eb293f98 | ||
|
|
5af187ba48 | ||
|
|
9847cdb894 | ||
|
|
fac2b62ec2 | ||
|
|
81b5bfb56b | ||
|
|
7abf5c0e88 | ||
|
|
ed4a32cba3 | ||
|
|
0778c49b18 | ||
|
|
3db1d795dc | ||
|
|
07fc618fd2 | ||
|
|
75da5d16ac | ||
|
|
1d40ac5b35 | ||
|
|
6bdd052472 | ||
|
|
74632eff10 | ||
|
|
3055726fb3 | ||
|
|
d10a29b931 | ||
|
|
75a264ca83 | ||
|
|
dcd764bd68 | ||
|
|
1a596077fa |
@@ -1,3 +1,6 @@
|
|||||||
# Windows files should use crlf line endings
|
# Windows files should use crlf line endings
|
||||||
# https://help.github.com/articles/dealing-with-line-endings/
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
*.bat text eol=crlf
|
*.bat text eol=crlf
|
||||||
|
|
||||||
|
# ignore these paths for GitHub language percentages
|
||||||
|
android/app/src/main/assets/licenses/* linguist-vendored=true
|
||||||
|
|||||||
@@ -2,10 +2,18 @@ name: build-release-debugsign
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- assets/**
|
||||||
|
- README.md
|
||||||
|
- LICENSE
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- release/*
|
- release/*
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- assets/**
|
||||||
|
- README.md
|
||||||
|
- LICENSE
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
@@ -19,7 +27,11 @@ jobs:
|
|||||||
node-version: '14'
|
node-version: '14'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- run: |
|
- run: |
|
||||||
yarn
|
yarn install
|
||||||
|
- run: |
|
||||||
|
yarn lint --max-warnings 0
|
||||||
|
- run: |
|
||||||
|
yarn license
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
- uses: gradle/gradle-build-action@v1
|
- uses: gradle/gradle-build-action@v1
|
||||||
with:
|
with:
|
||||||
@@ -28,7 +40,12 @@ jobs:
|
|||||||
configuration-cache-enabled: true
|
configuration-cache-enabled: true
|
||||||
build-root-directory: android
|
build-root-directory: android
|
||||||
arguments: assembleRelease -PsignReleaseWithDebug=true
|
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
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: app-release.apk
|
name: subtracks-testing
|
||||||
path: android/app/build/outputs/apk/release/app-release.apk
|
path: bin/*
|
||||||
|
|||||||
@@ -14,8 +14,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '14'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
|
- name: Get subtracks version
|
||||||
|
run: |
|
||||||
|
echo "SUBTRACKS_VERSION=$(node -e "console.log(require('./package.json').version);")" >> $GITHUB_ENV
|
||||||
- run: |
|
- run: |
|
||||||
yarn
|
yarn install
|
||||||
|
- run: |
|
||||||
|
yarn license
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
- name: Retrieve keystore file
|
- name: Retrieve keystore file
|
||||||
env:
|
env:
|
||||||
@@ -33,10 +38,11 @@ jobs:
|
|||||||
-PSUBTRACKS_UPLOAD_KEY_ALIAS=subtracks
|
-PSUBTRACKS_UPLOAD_KEY_ALIAS=subtracks
|
||||||
-PSUBTRACKS_UPLOAD_KEY_PASSWORD="$SUBTRACKS_UPLOAD_KEY_PASSWORD"
|
-PSUBTRACKS_UPLOAD_KEY_PASSWORD="$SUBTRACKS_UPLOAD_KEY_PASSWORD"
|
||||||
-PSUBTRACKS_UPLOAD_STORE_PASSWORD="$SUBTRACKS_UPLOAD_STORE_PASSWORD"
|
-PSUBTRACKS_UPLOAD_STORE_PASSWORD="$SUBTRACKS_UPLOAD_STORE_PASSWORD"
|
||||||
- uses: actions/upload-artifact@v2
|
- run: |
|
||||||
with:
|
mkdir bin
|
||||||
name: app-release.apk
|
- run: >
|
||||||
path: android/app/build/outputs/apk/release/app-release.apk
|
mv android/app/build/outputs/apk/release/app-release.apk
|
||||||
|
bin/subtracks-${SUBTRACKS_VERSION}.apk
|
||||||
- name: Build release AAB
|
- name: Build release AAB
|
||||||
env:
|
env:
|
||||||
SUBTRACKS_UPLOAD_KEY_PASSWORD: ${{ secrets.SUBTRACKS_UPLOAD_KEY_PASSWORD }}
|
SUBTRACKS_UPLOAD_KEY_PASSWORD: ${{ secrets.SUBTRACKS_UPLOAD_KEY_PASSWORD }}
|
||||||
@@ -48,7 +54,10 @@ jobs:
|
|||||||
-PSUBTRACKS_UPLOAD_KEY_ALIAS=subtracks
|
-PSUBTRACKS_UPLOAD_KEY_ALIAS=subtracks
|
||||||
-PSUBTRACKS_UPLOAD_KEY_PASSWORD="$SUBTRACKS_UPLOAD_KEY_PASSWORD"
|
-PSUBTRACKS_UPLOAD_KEY_PASSWORD="$SUBTRACKS_UPLOAD_KEY_PASSWORD"
|
||||||
-PSUBTRACKS_UPLOAD_STORE_PASSWORD="$SUBTRACKS_UPLOAD_STORE_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
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: app-release.aab
|
name: subtracks-release
|
||||||
path: android/app/build/outputs/bundle/release/app-release.aab
|
path: bin/*
|
||||||
|
|||||||
@@ -1,2 +1,38 @@
|
|||||||
# Subtracks
|
<img src="assets/header.png" alt="subtracks logo" width="500"/>
|
||||||
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.
|
|
||||||
|
#
|
||||||
|
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
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ android {
|
|||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0.0"
|
versionName '0.1.0'
|
||||||
}
|
}
|
||||||
splits {
|
splits {
|
||||||
abi {
|
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)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
buildToolsVersion = "29.0.3"
|
buildToolsVersion = "30.0.3"
|
||||||
minSdkVersion = 21
|
minSdkVersion = 21
|
||||||
compileSdkVersion = 29
|
compileSdkVersion = 30
|
||||||
targetSdkVersion = 29
|
targetSdkVersion = 30
|
||||||
ndkVersion = "20.1.5948944"
|
ndkVersion = "20.1.5948944"
|
||||||
|
|
||||||
// react-native-async-storage next
|
// react-native-async-storage next
|
||||||
@@ -14,6 +14,9 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://plugins.gradle.org/m2/'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:4.1.0")
|
classpath("com.android.tools.build:gradle:4.1.0")
|
||||||
@@ -22,8 +25,14 @@ buildscript {
|
|||||||
|
|
||||||
// react-native-async-storage next
|
// react-native-async-storage next
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
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 {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -3,14 +3,27 @@ import { useWindowDimensions, ViewStyle } from 'react-native'
|
|||||||
import LinearGradient from 'react-native-linear-gradient'
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
import colorStyles from '@app/styles/colors'
|
import colorStyles from '@app/styles/colors'
|
||||||
|
|
||||||
const GradientBackground: React.FC<{
|
export type GradientBackgroundPropsBase = {
|
||||||
height?: number | string
|
height?: number | string
|
||||||
width?: number | string
|
width?: number | string
|
||||||
position?: 'relative' | 'absolute'
|
position?: 'relative' | 'absolute'
|
||||||
style?: ViewStyle
|
style?: ViewStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GradientBackgroundProps = GradientBackgroundPropsBase & {
|
||||||
colors?: string[]
|
colors?: string[]
|
||||||
locations?: number[]
|
locations?: number[]
|
||||||
}> = ({ height, width, position, style, colors, locations, children }) => {
|
}
|
||||||
|
|
||||||
|
const GradientBackground: React.FC<GradientBackgroundProps> = ({
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
position,
|
||||||
|
style,
|
||||||
|
colors,
|
||||||
|
locations,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
const layout = useWindowDimensions()
|
const layout = useWindowDimensions()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,28 +1,13 @@
|
|||||||
|
import GradientBackground, { GradientBackgroundProps } from '@app/components/GradientBackground'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FlatList, FlatListProps, StyleSheet, useWindowDimensions } from 'react-native'
|
import BackgroundHeaderFlatList, { BackgroundHeaderFlatListPropsBase } from './BackgroundHeaderFlatList'
|
||||||
import colors from '@app/styles/colors'
|
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
|
||||||
|
|
||||||
function GradientFlatList<ItemT>(props: FlatListProps<ItemT>) {
|
export type GradientFlatListProps<ItemT> = BackgroundHeaderFlatListPropsBase<ItemT> & {
|
||||||
const layout = useWindowDimensions()
|
backgroundProps?: GradientBackgroundProps
|
||||||
|
}
|
||||||
|
|
||||||
const contentContainerStyle = StyleSheet.flatten(props.contentContainerStyle)
|
function GradientFlatList<ItemT>(props: GradientFlatListProps<ItemT>) {
|
||||||
|
return <BackgroundHeaderFlatList BackgroundComponent={GradientBackground} {...props} />
|
||||||
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),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GradientFlatList
|
export default GradientFlatList
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import dimensions from '@app/styles/dimensions'
|
import dimensions from '@app/styles/dimensions'
|
||||||
import React from 'react'
|
import React, { ForwardedRef, PropsWithChildren } from 'react'
|
||||||
import { ScrollViewProps, useWindowDimensions } from 'react-native'
|
import { ScrollView, ScrollViewProps, useWindowDimensions } from 'react-native'
|
||||||
import Animated from 'react-native-reanimated'
|
import Animated from 'react-native-reanimated'
|
||||||
|
|
||||||
const GradientScrollView: React.FC<
|
export type GradientScrollViewProps = PropsWithChildren<
|
||||||
Animated.AnimateProps<ScrollViewProps> & {
|
Animated.AnimateProps<ScrollViewProps> & {
|
||||||
offset?: number
|
offset?: number
|
||||||
}
|
}
|
||||||
> = props => {
|
>
|
||||||
|
|
||||||
|
const GradientScrollView = React.forwardRef<ScrollView, GradientScrollViewProps>((props, ref) => {
|
||||||
const layout = useWindowDimensions()
|
const layout = useWindowDimensions()
|
||||||
|
|
||||||
const minHeight = layout.height - (dimensions.top() + dimensions.bottom())
|
const minHeight = layout.height - (dimensions.top() + dimensions.bottom())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.ScrollView
|
<Animated.ScrollView
|
||||||
|
ref={ref as ForwardedRef<Animated.ScrollView>}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
{...props}
|
{...props}
|
||||||
style={[props.style, { backgroundColor: colors.gradient.low }]}
|
style={[props.style, { backgroundColor: colors.gradient.low }]}
|
||||||
@@ -24,6 +27,6 @@ const GradientScrollView: React.FC<
|
|||||||
{props.children}
|
{props.children}
|
||||||
</Animated.ScrollView>
|
</Animated.ScrollView>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default GradientScrollView
|
export default GradientScrollView
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { ViewStyle } from 'react-native'
|
|
||||||
import ImageColors from 'react-native-image-colors'
|
import ImageColors from 'react-native-image-colors'
|
||||||
import { AndroidImageColors } from 'react-native-image-colors/lib/typescript/types'
|
import { AndroidImageColors } from 'react-native-image-colors/lib/typescript/types'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground, { GradientBackgroundPropsBase } from '@app/components/GradientBackground'
|
||||||
|
|
||||||
export type ImageGradientBackgroundProps = {
|
export type ImageGradientBackgroundProps = GradientBackgroundPropsBase & {
|
||||||
height?: number | string
|
|
||||||
width?: number | string
|
|
||||||
position?: 'relative' | 'absolute'
|
|
||||||
style?: ViewStyle
|
|
||||||
imagePath?: string
|
imagePath?: string
|
||||||
onGetColor?: (color: string) => void
|
onGetColor?: (color: string) => void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -7,7 +7,7 @@ import colors from '@app/styles/colors'
|
|||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React, { useCallback } from 'react'
|
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 IconFA5 from 'react-native-vector-icons/FontAwesome5'
|
||||||
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||||
import { AlbumContextPressable, ArtistContextPressable, SongContextPressable } from './ContextMenu'
|
import { AlbumContextPressable, ArtistContextPressable, SongContextPressable } from './ContextMenu'
|
||||||
@@ -55,7 +55,8 @@ const ListItem: React.FC<{
|
|||||||
showStar?: boolean
|
showStar?: boolean
|
||||||
listStyle?: 'big' | 'small'
|
listStyle?: 'big' | 'small'
|
||||||
subtitle?: string
|
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 navigation = useNavigation()
|
||||||
const starred = useStarred(item.id, item.itemType)
|
const starred = useStarred(item.id, item.itemType)
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ const ListItem: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, sizeStyle.container]}>
|
<View style={[styles.container, sizeStyle.container, style]}>
|
||||||
<PressableComponent>
|
<PressableComponent>
|
||||||
{showArt && coverArt}
|
{showArt && coverArt}
|
||||||
<View style={styles.text}>
|
<View style={styles.text}>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import SearchResultsView from '@app/screens/SearchResultsView'
|
|||||||
import ServerView from '@app/screens/ServerView'
|
import ServerView from '@app/screens/ServerView'
|
||||||
import SettingsView from '@app/screens/Settings'
|
import SettingsView from '@app/screens/Settings'
|
||||||
import SongListView from '@app/screens/SongListView'
|
import SongListView from '@app/screens/SongListView'
|
||||||
|
import WebViewScreen from '@app/screens/WebViewScreen'
|
||||||
import { selectSettings } from '@app/state/settings'
|
import { selectSettings } from '@app/state/settings'
|
||||||
import { useStore } from '@app/state/store'
|
import { useStore } from '@app/state/store'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
@@ -122,6 +123,7 @@ const SearchTab = createTabStackNavigator(Search)
|
|||||||
type SettingsStackParamList = {
|
type SettingsStackParamList = {
|
||||||
main: undefined
|
main: undefined
|
||||||
server?: { id?: string }
|
server?: { id?: string }
|
||||||
|
web: { uri: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerScreenNavigationProp = NativeStackNavigationProp<SettingsStackParamList, 'server'>
|
type ServerScreenNavigationProp = NativeStackNavigationProp<SettingsStackParamList, 'server'>
|
||||||
@@ -130,9 +132,16 @@ type ServerScreenProps = {
|
|||||||
route: ServerScreenRouteProp
|
route: ServerScreenRouteProp
|
||||||
navigation: ServerScreenNavigationProp
|
navigation: ServerScreenNavigationProp
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerScreen: React.FC<ServerScreenProps> = ({ route }) => <ServerView id={route.params?.id} />
|
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 SettingsStack = createNativeStackNavigator()
|
||||||
|
|
||||||
const SettingsTab = () => {
|
const SettingsTab = () => {
|
||||||
@@ -150,6 +159,17 @@ const SettingsTab = () => {
|
|||||||
headerTitleStyle: styles.stackheaderTitleStyle,
|
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>
|
</SettingsStack.Navigator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,14 @@ const setNetState = (netState: 'mobile' | 'wifi') => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rebuildQueue = () => {
|
||||||
|
unstable_batchedUpdates(() => {
|
||||||
|
useStore.getState().rebuildQueue(useStore.getState().playerState === State.Playing)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let serviceCreated = false
|
let serviceCreated = false
|
||||||
|
|
||||||
const createService = async () => {
|
const createService = async () => {
|
||||||
useStore.subscribe(
|
useStore.subscribe(
|
||||||
(currentTrack?: TrackExt) => {
|
(currentTrack?: TrackExt) => {
|
||||||
@@ -40,6 +47,10 @@ const createService = async () => {
|
|||||||
(prev, next) => prev?.id === next?.id,
|
(prev, next) => prev?.id === next?.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NetInfo.fetch().then(state => {
|
||||||
|
setNetState(state.type === NetInfoStateType.cellular ? 'mobile' : 'wifi')
|
||||||
|
})
|
||||||
|
|
||||||
NetInfo.addEventListener(state => {
|
NetInfo.addEventListener(state => {
|
||||||
const currentType = useStore.getState().netState
|
const currentType = useStore.getState().netState
|
||||||
const newType = state.type === NetInfoStateType.cellular ? 'mobile' : 'wifi'
|
const newType = state.type === NetInfoStateType.cellular ? 'mobile' : 'wifi'
|
||||||
@@ -112,6 +123,15 @@ const createService = async () => {
|
|||||||
await TrackPlayer.seekTo(data.position)
|
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 () {
|
module.exports = async function () {
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ const AlbumsList = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={list.map(album => ({ album, size, height }))}
|
data={list.map(album => ({ album, size, height }))}
|
||||||
renderItem={AlbumListRenderItem}
|
renderItem={AlbumListRenderItem}
|
||||||
keyExtractor={item => item.album.id}
|
keyExtractor={item => item.album.id}
|
||||||
@@ -100,9 +99,6 @@ const AlbumsList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listContent: {
|
|
||||||
minHeight: '100%',
|
|
||||||
},
|
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import React, { useEffect, useState } from 'react'
|
|||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
const ArtistRenderItem: React.FC<{ item: Artist }> = ({ item }) => (
|
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[] = [
|
const filterOptions: OptionData[] = [
|
||||||
@@ -44,7 +44,6 @@ const ArtistsList = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={sortedList}
|
data={sortedList}
|
||||||
renderItem={ArtistRenderItem}
|
renderItem={ArtistRenderItem}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
@@ -52,6 +51,7 @@ const ArtistsList = () => {
|
|||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
windowSize={3}
|
windowSize={3}
|
||||||
|
contentMarginTop={6}
|
||||||
/>
|
/>
|
||||||
<FilterButton
|
<FilterButton
|
||||||
data={filterOptions}
|
data={filterOptions}
|
||||||
@@ -71,10 +71,8 @@ const styles = StyleSheet.create({
|
|||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
listContent: {
|
listItem: {
|
||||||
minHeight: '100%',
|
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingTop: 6,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import React from 'react'
|
|||||||
import { StyleSheet } from 'react-native'
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => (
|
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 = () => {
|
const PlaylistsList = () => {
|
||||||
@@ -17,7 +17,6 @@ const PlaylistsList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={list}
|
data={list}
|
||||||
renderItem={PlaylistRenderItem}
|
renderItem={PlaylistRenderItem}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
@@ -25,15 +24,14 @@ const PlaylistsList = () => {
|
|||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
windowSize={5}
|
windowSize={5}
|
||||||
|
contentMarginTop={6}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listContent: {
|
listItem: {
|
||||||
minHeight: '100%',
|
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingTop: 6,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +1,56 @@
|
|||||||
import GradientScrollView from '@app/components/GradientScrollView'
|
import GradientFlatList from '@app/components/GradientFlatList'
|
||||||
import ListItem from '@app/components/ListItem'
|
import ListItem from '@app/components/ListItem'
|
||||||
import NowPlayingBar from '@app/components/NowPlayingBar'
|
import NowPlayingBar from '@app/components/NowPlayingBar'
|
||||||
import { useSkipTo } from '@app/hooks/trackplayer'
|
import { useSkipTo } from '@app/hooks/trackplayer'
|
||||||
|
import { Song } from '@app/models/music'
|
||||||
import { useStore } from '@app/state/store'
|
import { useStore } from '@app/state/store'
|
||||||
import { selectTrackPlayer } from '@app/state/trackplayer'
|
import { selectTrackPlayer } from '@app/state/trackplayer'
|
||||||
import { selectTrackPlayerMap } from '@app/state/trackplayermap'
|
import { selectTrackPlayerMap } from '@app/state/trackplayermap'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
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 NowPlayingQueue = React.memo<{}>(() => {
|
||||||
const queue = useStore(selectTrackPlayer.queue)
|
const queue = useStore(selectTrackPlayer.queue)
|
||||||
const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong)
|
const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong)
|
||||||
const skipTo = useSkipTo()
|
const skipTo = useSkipTo()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.outerContainer}>
|
<View style={styles.container}>
|
||||||
<GradientScrollView style={styles.container}>
|
<GradientFlatList
|
||||||
<View style={styles.content}>
|
data={queue.map(mapTrackExtToSong).map((song, i) => ({ song, i, onPress: () => skipTo(i) }))}
|
||||||
{queue.map(mapTrackExtToSong).map((song, i) => (
|
renderItem={SongRenderItem}
|
||||||
<ListItem
|
keyExtractor={(item, i) => i.toString()}
|
||||||
key={i}
|
overScrollMode="never"
|
||||||
item={song}
|
windowSize={7}
|
||||||
queueId={i}
|
contentMarginTop={10}
|
||||||
onPress={() => skipTo(i)}
|
/>
|
||||||
showArt={true}
|
|
||||||
subtitle={`${song.artist} • ${song.album}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
</GradientScrollView>
|
|
||||||
<NowPlayingBar />
|
<NowPlayingBar />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outerContainer: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
content: {
|
listItem: {
|
||||||
alignItems: 'center',
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'
|
|||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
InteractionManager,
|
InteractionManager,
|
||||||
|
ScrollView,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TextInput as ReactTextInput,
|
TextInput as ReactTextInput,
|
||||||
@@ -90,17 +91,20 @@ const Search = () => {
|
|||||||
const [refreshing, setRefreshing] = useState(false)
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
const [text, setText] = useState('')
|
const [text, setText] = useState('')
|
||||||
const searchBarRef = useRef<ReactTextInput>(null)
|
const searchBarRef = useRef<ReactTextInput>(null)
|
||||||
|
const scrollRef = useRef<ScrollView>(null)
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
const task = InteractionManager.runAfterInteractions(() => {
|
const task = InteractionManager.runAfterInteractions(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setText('')
|
setText('')
|
||||||
|
setResults({ artists: [], albums: [], songs: [] })
|
||||||
searchBarRef.current?.focus()
|
searchBarRef.current?.focus()
|
||||||
|
scrollRef.current?.scrollTo({ y: 0, animated: true })
|
||||||
}, 50)
|
}, 50)
|
||||||
})
|
})
|
||||||
return () => task.cancel()
|
return () => task.cancel()
|
||||||
}, [searchBarRef]),
|
}, [searchBarRef, scrollRef]),
|
||||||
)
|
)
|
||||||
|
|
||||||
useActiveServerRefresh(
|
useActiveServerRefresh(
|
||||||
@@ -131,7 +135,7 @@ const Search = () => {
|
|||||||
const resultsCount = results.albums.length + results.artists.length + results.songs.length
|
const resultsCount = results.albums.length + results.artists.length + results.songs.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GradientScrollView style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
|
<GradientScrollView ref={scrollRef} style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<View style={styles.inputBar}>
|
<View style={styles.inputBar}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const ResultsListItem: React.FC<{ item: SearchListItemType }> = ({ item }) => {
|
|||||||
showStar={false}
|
showStar={false}
|
||||||
listStyle="small"
|
listStyle="small"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
style={styles.listItem}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -69,25 +70,24 @@ const SearchResultsView: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={list}
|
data={list}
|
||||||
renderItem={SearchResultsRenderItem}
|
renderItem={SearchResultsRenderItem}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={(item, i) => i.toString()}
|
||||||
onRefresh={refresh}
|
onRefresh={refresh}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
onEndReached={fetchNextPage}
|
onEndReached={fetchNextPage}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
onEndReachedThreshold={2}
|
onEndReachedThreshold={2}
|
||||||
|
contentMarginTop={6}
|
||||||
|
windowSize={5}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listContent: {
|
listItem: {
|
||||||
minHeight: '100%',
|
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingTop: 6,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import colors from '@app/styles/colors'
|
|||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
import { useNavigation } from '@react-navigation/core'
|
import { useNavigation } from '@react-navigation/core'
|
||||||
import React, { useCallback, useState } from 'react'
|
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 { ScrollView } from 'react-native-gesture-handler'
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||||
|
|
||||||
@@ -195,8 +195,11 @@ const SettingsContent = React.memo(() => {
|
|||||||
const servers = useStore(selectSettings.servers)
|
const servers = useStore(selectSettings.servers)
|
||||||
const scrobble = useStore(selectSettings.scrobble)
|
const scrobble = useStore(selectSettings.scrobble)
|
||||||
const setScrobble = useStore(selectSettings.setScrobble)
|
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 maxBitrateWifi = useStore(selectSettings.maxBitrateWifi)
|
||||||
const setMaxBitrateWifi = useStore(selectSettings.setMaxBitrateWifi)
|
const setMaxBitrateWifi = useStore(selectSettings.setMaxBitrateWifi)
|
||||||
@@ -245,14 +248,14 @@ const SettingsContent = React.memo(() => {
|
|||||||
buttonStyle="hollow"
|
buttonStyle="hollow"
|
||||||
/>
|
/>
|
||||||
<Header style={styles.header}>Network</Header>
|
<Header style={styles.header}>Network</Header>
|
||||||
<BitrateModal title="Max bitrate (Wi-Fi)" bitrate={maxBitrateWifi} setBitrate={setMaxBitrateWifi} />
|
<BitrateModal title="Maximum bitrate (Wi-Fi)" bitrate={maxBitrateWifi} setBitrate={setMaxBitrateWifi} />
|
||||||
<BitrateModal title="Max bitrate (mobile)" bitrate={maxBitrateMobile} setBitrate={setMaxBitrateMobile} />
|
<BitrateModal title="Maximum bitrate (mobile)" bitrate={maxBitrateMobile} setBitrate={setMaxBitrateMobile} />
|
||||||
<SettingsSwitch
|
{/* <SettingsSwitch
|
||||||
title="Estimate content length"
|
title="Estimate content length"
|
||||||
subtitle='Send the "estimateContentLength" flag when streaming. Helps fix issues with seeking when the server is transcoding songs.'
|
subtitle='Send the "estimateContentLength" flag when streaming. Helps fix issues with seeking when the server is transcoding songs.'
|
||||||
value={estimateContentLength}
|
value={estimateContentLength}
|
||||||
setValue={setEstimateContentLength}
|
setValue={setEstimateContentLength}
|
||||||
/>
|
/> */}
|
||||||
<SettingsTextModal
|
<SettingsTextModal
|
||||||
title="Minimum buffer time"
|
title="Minimum buffer time"
|
||||||
value={minBuffer.toString()}
|
value={minBuffer.toString()}
|
||||||
@@ -278,10 +281,32 @@ const SettingsContent = React.memo(() => {
|
|||||||
<Button
|
<Button
|
||||||
disabled={clearing}
|
disabled={clearing}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
title="Clear image cache"
|
title="Clear Image Cache"
|
||||||
onPress={clear}
|
onPress={clear}
|
||||||
buttonStyle="hollow"
|
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>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -305,9 +330,6 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
paddingBottom: 40,
|
paddingBottom: 40,
|
||||||
},
|
},
|
||||||
text: {
|
|
||||||
color: 'white',
|
|
||||||
},
|
|
||||||
serverActive: {
|
serverActive: {
|
||||||
paddingLeft: 12,
|
paddingLeft: 12,
|
||||||
},
|
},
|
||||||
@@ -317,6 +339,14 @@ const styles = StyleSheet.create({
|
|||||||
button: {
|
button: {
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
},
|
},
|
||||||
|
licenseButton: {
|
||||||
|
marginHorizontal: 10,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
licenses: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
modalBackdrop: {
|
modalBackdrop: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
@@ -365,7 +395,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
modalTextSubmit: {
|
modalTextSubmit: {
|
||||||
marginLeft: 15,
|
marginLeft: 15,
|
||||||
// backgroundColor: 'green',
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
import HeaderBar from '@app/components/HeaderBar'
|
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 ListItem from '@app/components/ListItem'
|
||||||
import ListPlayerControls from '@app/components/ListPlayerControls'
|
import ListPlayerControls from '@app/components/ListPlayerControls'
|
||||||
import NothingHere from '@app/components/NothingHere'
|
|
||||||
import { useCoverArtFile } from '@app/hooks/cache'
|
import { useCoverArtFile } from '@app/hooks/cache'
|
||||||
import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music'
|
import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music'
|
||||||
import { AlbumWithSongs, PlaylistWithSongs, Song } from '@app/models/music'
|
import { AlbumWithSongs, PlaylistWithSongs, Song } from '@app/models/music'
|
||||||
@@ -23,15 +22,42 @@ const SongListDetailsFallback = React.memo(() => (
|
|||||||
</GradientBackground>
|
</GradientBackground>
|
||||||
))
|
))
|
||||||
|
|
||||||
const Songs = React.memo<{
|
const SongRenderItem: React.FC<{
|
||||||
songs: Song[]
|
item: {
|
||||||
name: string
|
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
|
type: SongListType
|
||||||
itemId: string
|
songList?: AlbumWithSongs | PlaylistWithSongs
|
||||||
}>(({ songs, name, type, itemId }) => {
|
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 setQueue = useStore(selectTrackPlayer.setQueue)
|
||||||
|
|
||||||
const _songs = [...songs]
|
if (!songList) {
|
||||||
|
return <SongListDetailsFallback />
|
||||||
|
}
|
||||||
|
|
||||||
|
const _songs = [...songList.songs]
|
||||||
let typeName = ''
|
let typeName = ''
|
||||||
|
|
||||||
if (type === 'album') {
|
if (type === 'album') {
|
||||||
@@ -49,46 +75,6 @@ const Songs = React.memo<{
|
|||||||
typeName = 'Playlist'
|
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 (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<HeaderBar
|
<HeaderBar
|
||||||
@@ -96,21 +82,43 @@ const SongListDetails = React.memo<{
|
|||||||
title={title}
|
title={title}
|
||||||
contextItem={songList.itemType === 'album' ? songList : undefined}
|
contextItem={songList.itemType === 'album' ? songList : undefined}
|
||||||
/>
|
/>
|
||||||
<ImageGradientScrollView
|
<ImageGradientFlatList
|
||||||
imagePath={coverArtFile?.file?.path}
|
data={_songs.map((s, i) => ({
|
||||||
style={styles.container}
|
song: s,
|
||||||
onGetColor={setHeaderColor}>
|
contextId: songList.id,
|
||||||
<View style={styles.content}>
|
queueId: i,
|
||||||
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
|
subtitle: s.artist,
|
||||||
<Text style={styles.title}>{songList.name}</Text>
|
onPress: () => setQueue(_songs, songList.name, type, songList.id, i),
|
||||||
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
|
showArt: songList.itemType === 'playlist',
|
||||||
{songList.songs.length > 0 ? (
|
}))}
|
||||||
<Songs songs={songList.songs} name={songList.name} type={type} itemId={songList.id} />
|
renderItem={SongRenderItem}
|
||||||
) : (
|
keyExtractor={(item, i) => i.toString()}
|
||||||
<NothingHere height={300} width={250} />
|
backgroundProps={{
|
||||||
)}
|
imagePath: coverArtFile?.file?.path,
|
||||||
</View>
|
style: styles.container,
|
||||||
</ImageGradientScrollView>
|
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>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -185,6 +193,9 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingTop: 100,
|
paddingTop: 100,
|
||||||
},
|
},
|
||||||
|
listItem: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default SongListView
|
export default SongListView
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -135,8 +135,7 @@ export const createMusicMapSlice = (set: SetState<Store>, get: GetState<Store>):
|
|||||||
mapPlaylistWithSongs: async playlist => {
|
mapPlaylistWithSongs: async playlist => {
|
||||||
return {
|
return {
|
||||||
...get().mapPlaylistListItem(playlist),
|
...get().mapPlaylistListItem(playlist),
|
||||||
// passing cover art here is a temp fix to improve large playlist performance
|
songs: await get().mapChildrenToSongs(playlist.songs),
|
||||||
songs: await get().mapChildrenToSongs(playlist.songs, playlist.coverArt),
|
|
||||||
coverArt: playlist.coverArt,
|
coverArt: playlist.coverArt,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export type TrackPlayerSlice = {
|
|||||||
netState: 'mobile' | 'wifi'
|
netState: 'mobile' | 'wifi'
|
||||||
setNetState: (netState: 'mobile' | 'wifi') => Promise<void>
|
setNetState: (netState: 'mobile' | 'wifi') => Promise<void>
|
||||||
|
|
||||||
rebuildQueue: () => Promise<void>
|
rebuildQueue: (forcePlay?: boolean) => Promise<void>
|
||||||
buildStreamUri: (id: string) => string
|
buildStreamUri: (id: string) => string
|
||||||
resetTrackPlayerState: () => void
|
resetTrackPlayerState: () => void
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ export const createTrackPlayerSlice = (set: SetState<Store>, get: GetState<Store
|
|||||||
get().rebuildQueue()
|
get().rebuildQueue()
|
||||||
},
|
},
|
||||||
|
|
||||||
rebuildQueue: async () => {
|
rebuildQueue: async forcePlay => {
|
||||||
return trackPlayerCommands.enqueue(async () => {
|
return trackPlayerCommands.enqueue(async () => {
|
||||||
const queue = await getQueue()
|
const queue = await getQueue()
|
||||||
if (!queue.length) {
|
if (!queue.length) {
|
||||||
@@ -326,7 +326,7 @@ export const createTrackPlayerSlice = (set: SetState<Store>, get: GetState<Store
|
|||||||
|
|
||||||
await TrackPlayer.seekTo(position)
|
await TrackPlayer.seekTo(position)
|
||||||
|
|
||||||
if (state === State.Playing) {
|
if (state === State.Playing || forcePlay) {
|
||||||
await TrackPlayer.play()
|
await TrackPlayer.play()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 649 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 905 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 437 KiB |
|
After Width: | Height: | Size: 444 KiB |
|
After Width: | Height: | Size: 149 KiB |
@@ -1,14 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "subtracks",
|
"name": "subtracks",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"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",
|
"start": "react-native start",
|
||||||
"test": "jest",
|
"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": {
|
"dependencies": {
|
||||||
"@react-native-async-storage/async-storage": "^1.15.5",
|
"@react-native-async-storage/async-storage": "^1.15.5",
|
||||||
@@ -36,6 +43,7 @@
|
|||||||
"react-native-tab-view": "^2.16.0",
|
"react-native-tab-view": "^2.16.0",
|
||||||
"react-native-track-player": "^2.0.3",
|
"react-native-track-player": "^2.0.3",
|
||||||
"react-native-vector-icons": "^8.1.0",
|
"react-native-vector-icons": "^8.1.0",
|
||||||
|
"react-native-webview": "^11.13.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"xmldom": "^0.5.0",
|
"xmldom": "^0.5.0",
|
||||||
"zustand": "^3.5.7"
|
"zustand": "^3.5.7"
|
||||||
|
|||||||
@@ -2648,16 +2648,16 @@ escape-html@~1.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
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:
|
escape-string-regexp@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
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:
|
escape-string-regexp@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
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"
|
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
||||||
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
|
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
|
||||||
|
|
||||||
invariant@^2.2.4:
|
invariant@2.2.4, invariant@^2.2.4:
|
||||||
version "2.2.4"
|
version "2.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||||
@@ -5606,6 +5606,14 @@ react-native-vector-icons@^8.1.0:
|
|||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
yargs "^16.1.1"
|
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:
|
react-native@0.64.1:
|
||||||
version "0.64.1"
|
version "0.64.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.64.1.tgz#cd38f5b47b085549686f34eb0c9dcd466f307635"
|
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.64.1.tgz#cd38f5b47b085549686f34eb0c9dcd466f307635"
|
||||||
|
|||||||