Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb0ccd00a3 | ||
|
|
cc77935cd1 | ||
|
|
de8a484211 | ||
|
|
49733ae1f1 | ||
|
|
9a8095d02d | ||
|
|
409ee3bc86 | ||
|
|
24e6b1c306 | ||
|
|
2c1dd06230 | ||
|
|
ad829ed93a | ||
|
|
0f1b8d6147 | ||
|
|
68b1a467f4 | ||
|
|
3e3523baa8 | ||
|
|
5055b0d9e8 | ||
|
|
d49c97c2b5 | ||
|
|
f3eac6102a | ||
|
|
92c58cedd9 | ||
|
|
02d12fef49 | ||
|
|
2624831e1a | ||
|
|
9576f6b4e7 | ||
|
|
5388f17347 | ||
|
|
c436abc77d | ||
|
|
06c533be48 | ||
|
|
4606a21d46 | ||
|
|
72c25075a4 | ||
|
|
4bc46489cb | ||
|
|
a87636c1fe | ||
|
|
84bc814952 | ||
|
|
3142fe7660 | ||
|
|
1ad2e30627 |
@@ -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
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 { 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}>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 => {
|
||||
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,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
|
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",
|
||||
"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"
|
||||
|
||||
@@ -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"
|
||||
|
||||