feat(vivo): add a vivoAndroidApp build that ships as com.spotify.music for Origin Island, stop forcing haze blur on Android 11 and below, bump to 2.3.0 and bump core
This commit is contained in:
parent
68e71a2525
commit
126c0fd302
19 changed files with 360 additions and 30 deletions
60
.github/workflows/android-release.yml
vendored
60
.github/workflows/android-release.yml
vendored
|
|
@ -75,6 +75,66 @@ jobs:
|
|||
name: app-full-release
|
||||
path: androidApp/build/outputs/apk/release/*.apk
|
||||
|
||||
# SimpMusic for vivo: androidApp under a package vivo's Origin Island follows (vivoAndroidApp),
|
||||
# Full build, arm64-v8a only, signed with the same key so the official-build check accepts it.
|
||||
build-vivo-release:
|
||||
name: Build vivo release version
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: "microsoft"
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Update Sentry Secrets
|
||||
env:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo 'SENTRY_DSN=${{ secrets.SENTRY_DSN }}' > ./local.properties
|
||||
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> ./local.properties
|
||||
|
||||
- name: Update Last.fm secrets
|
||||
env:
|
||||
LASTFM_API_KEY: ${{ secrets.LASTFM_API_KEY }}
|
||||
LASTFM_SECRET: ${{ secrets.LASTFM_SECRET }}
|
||||
run: |
|
||||
echo "LASTFM_API_KEY=${{ secrets.LASTFM_API_KEY }}" >> ./local.properties
|
||||
echo "LASTFM_SECRET=${{ secrets.LASTFM_SECRET }}" >> ./local.properties
|
||||
|
||||
- name: Update environment variables
|
||||
env:
|
||||
BASE_64_SIGNING_KEY: ${{ secrets.BASE_64_SIGNING_KEY }}
|
||||
run:
|
||||
echo $BASE_64_SIGNING_KEY | base64 -d > simpmusic.jks
|
||||
|
||||
- name: Update build product flavor
|
||||
run: |
|
||||
echo "" >> ./gradle.properties
|
||||
echo "isFullBuild=true" >> ./gradle.properties
|
||||
|
||||
- name: Build vivo release APK
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
run: ./build_and_sign_apk.sh --release --full --vivo
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-vivo-release
|
||||
path: vivoAndroidApp/build/outputs/apk/release/*.apk
|
||||
|
||||
build-foss-release:
|
||||
name: Build foss release version
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -33,6 +33,7 @@ sentry.properties
|
|||
/.claude/settings.local.json
|
||||
/androidApp/build/
|
||||
/androidApp/cache/
|
||||
/vivoAndroidApp/build/
|
||||
/lastfm/build/
|
||||
/lastfm-empty/build/
|
||||
# libmpv native libraries (downloaded by `./gradlew :composeApp:mpvSetupAll`)
|
||||
|
|
|
|||
20
CLAUDE.md
20
CLAUDE.md
|
|
@ -72,6 +72,7 @@
|
|||
- Android build configuration
|
||||
- Android resources (if needed)
|
||||
- Entry point for Android app
|
||||
- **vivoAndroidApp/** builds the same app under `com.spotify.music` for vivo's Origin Island. It has no code of its own: its source set points at androidApp's sources, resources and manifest. Its `build.gradle.kts` mirrors androidApp's, so keep the two in step.
|
||||
|
||||
#### 3. **core/**
|
||||
Contains core modules organized by functionality:
|
||||
|
|
@ -878,7 +879,8 @@ if (getPlatform() == Platform.Android) {
|
|||
- Setting: Settings → *Preferred audio language* (`preferred_audio_language`, a 2-letter code, empty = original), passed as `player(…, preferredAudioLanguage)`. **Deliberately not defaulted to the app language** the way the YouTube subtitle language is: that would replace the speaker's own voice with an AI dub (`dubbed-auto`) for every user whose language has one. `descriptive` tracks (narration for blind viewers) never count as the preferred language.
|
||||
- Not handled: the original has two variants, and `acont=original:drc=1` (dynamic-range compressed) is listed first, so it wins — which is what the itag-first code already picked. A URL already stored in `NewFormatEntity` is reused until it expires, so changing the setting does not affect a video played recently.
|
||||
|
||||
- **haze 1.7.2 → 2.0.0 (2026-09-24)**: 2.0 publishes no `haze-materials`; blur moved to **`haze-blur`** and the presets to **`haze-blur-materials`** (package `dev.chrisbanes.haze.blur.materials`, no opt-in). The mutable `Modifier.hazeEffect(state) { … }` DSL is gone — a `hazeEffect` still exists but is the generic custom-effect API (`factory, input, style`), so leftover calls fail as "no matching overload", not "unresolved". All 22 call sites are now `hazeBlur(HazeInput.Sources(hazeState), style)`: `Sources` is the 1:1 replacement for 1.x's state-based effect (`Backdrop` is only a native path behind an off-by-default flag, Android 37.2). The 13 tinted top bars share `barBlurStyle(tint, tintAlpha)` in `UIExt.kt`; the 9 preset bars use `HazeMaterials.ultraThin().then { blurEnabled(true) }`. `blurEnabled` left `rememberHazeState()` for the Style, and must stay forced on: minSdk is 26, so Android 8–11 take haze's RenderScript path, whose crashes and leaks 2.0 fixes.
|
||||
- **haze 1.7.2 → 2.0.0 (2026-09-24)**: 2.0 publishes no `haze-materials`; blur moved to **`haze-blur`** and the presets to **`haze-blur-materials`** (package `dev.chrisbanes.haze.blur.materials`, no opt-in). The mutable `Modifier.hazeEffect(state) { … }` DSL is gone — a `hazeEffect` still exists but is the generic custom-effect API (`factory, input, style`), so leftover calls fail as "no matching overload", not "unresolved". All 22 call sites are now `hazeBlur(HazeInput.Sources(hazeState), style)`: `Sources` is the 1:1 replacement for 1.x's state-based effect (`Backdrop` is only a native path behind an off-by-default flag, Android 37.2). The 13 tinted top bars share `barBlurStyle(tint, tintAlpha)` and the 9 plain ones `ultraThinBarStyle()`, both in `UIExt.kt`. Neither forces `blurEnabled`. haze blurs by default on Android 12+ and on Desktop, and everywhere else draws the style's `fallbackColorEffect`, which is set to the bar's own colour, opaque (2026-10-04).
|
||||
- **Do not force blur back on.** Below Android 12, haze blurs through RenderScript, which haze's own docs call experimental. That path still dies natively on haze 2.0 and 2.0.1, in `GrallocConsumer::lockNextBuffer`, while one blurred screen replaces another: Library into the four-tile screen above all, More albums too. In Sentry this is ANDROID-Q, -ND and -SE, on Android 10–11 only. A native crash leaves haze no chance to fall back. Forcing blur was why tapping Favorite / Followed / Most played / Downloaded crashed on a Realme 6 Pro running 2.2.0.
|
||||
- **A Style is compared by its recorded writes** (`HazeBlurStyle { }` records into a list; `RecordedHazeBlurStyle` has structural `equals`), so building one per recomposition is free — no `remember` needed.
|
||||
- `performanceMode` is left at `Default`, which is now `Adaptive` (downsamples under load); pass `HazePerformanceMode.Quality` if a bar looks softer than before.
|
||||
- haze 2.0 is built on Compose Multiplatform 1.12.0, i.e. skiko 0.150.x, and so is everything that moved with it. **That killed the Desktop tray at runtime, not at build time**: `composenativetray` 1.3.3 (the last release under `io.github.kdroidfilter`, built on Compose 1.10.3) calls `Image.encodeToData$default`, which skiko 0.150 no longer has — `NoSuchMethodError` on the first composition, from `Tray(…)` rendering its icon, while Gradle reports BUILD SUCCESSFUL. The tray moved to **`dev.nucleusframework:composenativetray` 2.1.6** (Compose 1.12.0), package `dev.nucleusframework.composenativetray`. `Tray`/`Item`/`Divider` kept their shape.
|
||||
|
|
@ -938,6 +940,22 @@ if (getPlatform() == Platform.Android) {
|
|||
- `MediaPlayerHandler.moveItemToPlayNext(position)` targets the slot right after `currentOrderIndex()`: `current + 1` from ahead, `current` from behind (the track leaving its slot shifts everything after it). Up/down/play-next share one `moveQueueItem`, which updates queueData only if the track is still at `from` — the player's own timeline event rebuilds the queue in the moved order too, and on Desktop it can arrive first, from the player thread.
|
||||
- **Calf removed; files are picked with each platform's own dialog (2026-10-04, issues #2546, #2559, #2580, #2593, #2595)**: Calf 0.14.0's desktop `rememberFilePickerLauncher` created its native dialog in a `LaunchedEffect` the moment the launcher was composed, so a failure there crashed any screen that merely held a launcher — Settings (backup restore, playlist import) and the local playlist ⋯ sheet (cover picker) — before anything was clicked. It failed three ways: Smart App Control refused the unsigned `calf_filepicker_native.dll` that Calf extracted to `%USERPROFILE%\.cache\calf-filepicker` on Windows (its loader tries `System.loadLibrary` first, but `conveyor.conf` keeps `extract-native-libraries` off, so the package held no copy); its Linux `.so` needed GLIBC 2.39; and ProGuard renamed `jodd.net.MimeTypes`, whose `getResourceAsStream(simpleName + ".properties")` then missed ("Mime types file missing") on every OS — reproduced by loading `f.b.a` straight from the shipped 2.2.0 jar. The dependency is gone. Desktop opens files with AWT's `FileDialog` (`showOpenFileDialog` in `FilePicker.jvm.kt`) — `GetOpenFileName` on Windows, `NSOpenPanel` on macOS, the GTK chooser on Linux (AWT draws its own where GTK is missing) — and Android with SAF `OpenDocument`, which is what restore used before Calf (`bb1e7156`). `filePickerResult(onResultUri)` hands back a content Uri on Android and a plain path on Desktop: exactly what `restoreNative` opens, and what the new `readPickedFile` (playlist import, beside `restoreNative` in the SettingsViewModel files) opens.
|
||||
- **`FilenameFilter` does work in the Windows `FileDialog`, whatever the javadoc says**: OpenJDK's dialog hook handles `CDN_INCLUDEITEM` and asks `WFileDialogPeer.checkFilenameFilter` about each item (present in jdk21u). macOS (`queryFilenameFilter`) and GTK (`filenameFilterCallback`) ask too, so the cover picker filters images on all three.
|
||||
- **Windows "Unable to rename settings.preferences_pb.tmp" crash traced to a DataStore race and fixed there (2026-10-04)**: Sentry SIMPMUSIC-DESKTOP-J logged ~26k fatal crashes on Windows from 2025-12 onward, on every release. The race lives inside DataStore 1.2.1. `FileStorageConnection.readScope` only `tryLock`s, and `DataStoreImpl` reads anyway when the lock is busy. A read that arrives mid-save goes to disk, because the cached version is behind between `incrementAndGetVersion()` and the cache update inside that save. That read opens the real file with `FileInputStream`, which the JDK opens without `FILE_SHARE_DELETE` (`io_util_md.c`). On Windows, `Files.move(REPLACE_EXISTING)` begins with `DeleteFile(target)` (`WindowsFileCopy.move`), so the save fails. DataStore swallows the real error and blames "multiple instances". The save had been launched on `SERVICE_SCOPE`, which has no handler, so `CrashDialog` ended the process. `createDataStoreInstance()` on JVM now builds the store as `PreferenceDataStoreFactory.create(storage = ReadsWaitForWritesStorage(FileStorage(PreferencesFileSerializer) {…}))`, which holds one Mutex across `readScope` and `writeScope`. The file and its format are unchanged, so nothing migrates. The race cannot be reproduced off Windows. Confirm the fix by the SIMPMUSIC-DESKTOP-J and -12 rates on the next release: an outside program holding the file would still fail a save, and 1.2.1 hides which one did.
|
||||
- **Bumping DataStore does not fix it.** `650ccfb4d` (in 1.3.0-alpha03 and later) only adds `ATOMIC_MOVE` and stops swallowing the error. The unlocked `FileInputStream` read is still on androidx-main, and b/203087070 carries a report of the crash on 1.3.0-alpha10. What `ATOMIC_MOVE` would buy is diagnosis and safety: it attaches the real Win32 error, and it removes 1.2.1's window in which the delete succeeds, the move fails and the settings file is lost.
|
||||
- **The same race also crashes the reader.** SIMPMUSIC-DESKTOP-12 logged ~300 crashes on 18 machines from 2025-12 onward, still on 2.2.0. A read that opens the file between the save's `DeleteFile` and `MoveFileEx` gets `FileNotFoundException` ("cannot find the file specified"). `FileReadScope.readData` then checks `file.exists()`. If that returns false, still inside the gap, the read silently returns empty preferences. If it returns true and the retry fails, DataStore throws "Inoperable file". The lock closes both.
|
||||
- **Do not switch the read to NIO**, even though NIO shares delete. On Windows without POSIX delete semantics, the delete goes pending and the move fails, and DataStore then deletes the `.tmp`. The settings file is gone.
|
||||
- **The trigger was ours.** `skipSegmentsJob` asked `sponsorBlockEnabled.first()` on every progress tick, against the 5 s position save. That was 20 reads a second once 2.2.0 smoothed the published position (3–4 before), and ten times that with SponsorBlock on, because `getSponsorBlockCategories()` reads each of the nine categories. The check now reads `sponsorBlockCategories`, a `MutableStateFlow` that a collector in `init` keeps current from those settings (null while SponsorBlock is off or not loaded yet), so no tick touches DataStore. Android's `MediaServiceHandlerImpl` had the identical per-tick read and got the identical change. The two handlers' SponsorBlock blocks must stay identical.
|
||||
- **The lock is safe only because of how DataStore enters it.** It enters the connection through `readData()` and `writeScope`, and neither runs inside the other. It also always takes its own coordinator lock before the wrapper's. Re-check both on any DataStore bump. The Desktop backup and restore (`backupNative`, `restoreNative`) still read and write the file directly, outside the lock.
|
||||
- **SimpMusic for vivo, a separate artifact (2026-10-04, issues #2074, #2316)**: vivo's Origin Island and music widget only follow apps whose **package name** is on vivo's own list. They never look at the MediaSession, and a normal app cannot add itself to that list: it is a Settings key that only shell or root can write. The new module `vivoAndroidApp` therefore builds androidApp unchanged under `com.spotify.music`. Spotify is on the list on global OriginOS: r/Vivo users report the island follows Spotify, and they lend its package to apps that are not on the list.
|
||||
- **It is a full Android app module with no code of its own.** `sourceSets["main"]` points `manifest`, `java`, `kotlin` and `res` at androidApp. Both `java` and `kotlin` are set, because androidApp keeps its Kotlin under `src/main/java`; a source set that missed it would build an APK with no app classes and no error. The build is arm64-v8a only with no ABI splits, Full only, and debug has no `.dev` suffix so it can be tried on a vivo phone.
|
||||
- **Its `src/release/res/xml/shortcuts.xml` overrides androidApp's.** androidApp's copy hard-codes `targetPackage="com.maxrave.simpmusic"`, which would open the wrong app, or nothing, from the vivo build.
|
||||
- **It passes the unofficial-build check only because both layers hold.** `com.spotify.music` is in `Config.OFFICIAL_PACKAGE_NAMES`, and the APK is signed with our release key, which the F-Droid key check accepts. A user who renames the app with apktool or ReVanced is still blocked: changing the manifest invalidates the signature, so those tools re-sign with their own key.
|
||||
- **Building it:** run `./build_and_sign_apk.sh --release --full --vivo`. The `--vivo` flag switches the module and output directory and names the file `SimpMusic-full-vivo-….apk`. CI job `build-vivo-release` in `android-release.yml` uploads it as `app-vivo-release`.
|
||||
- **Limits.**
|
||||
- It cannot be installed next to the real Spotify, and its data is separate from the regular app's.
|
||||
- The in-app update link still opens the regular download page.
|
||||
- The Listen Together user agent reports `com.spotify.music`, because `AppIdentity` takes the real `applicationId`.
|
||||
- The rolled-back alternative (a Shizuku switch that wrote vivo's list from the regular app) needed Developer options, which users rejected.
|
||||
|
||||
## 🔄 CLAUDE.md Auto-Update Rule (MANDATORY)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ set -e
|
|||
# Default variables
|
||||
BUILD_TYPE="release"
|
||||
BUILD_VARIANT="full"
|
||||
# --vivo builds vivoAndroidApp: the same app under a package vivo's Origin Island follows
|
||||
MODULE="androidApp"
|
||||
NAME_SUFFIX=""
|
||||
KEYSTORE_PATH="./simpmusic.jks"
|
||||
# Read passwords from environment variables or use default (for backward compatibility)
|
||||
KEYSTORE_PASSWORD="${KEYSTORE_PASSWORD}"
|
||||
|
|
@ -38,6 +41,7 @@ print_usage() {
|
|||
echo " --debug Build in debug mode"
|
||||
echo " --full Build full with Sentry"
|
||||
echo " --foss Build foss, compatibility with F-Droid, no Sentry"
|
||||
echo " --vivo Build the vivo edition (vivoAndroidApp, arm64-v8a only)"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
echo "Environment variables:"
|
||||
|
|
@ -54,6 +58,7 @@ while [[ "$#" -gt 0 ]]; do
|
|||
--foss) BUILD_VARIANT="foss" ;;
|
||||
--release) BUILD_TYPE="release" ;;
|
||||
--debug) BUILD_TYPE="debug" ;;
|
||||
--vivo) MODULE="vivoAndroidApp"; NAME_SUFFIX="-vivo" ;;
|
||||
-h|--help) print_usage ;;
|
||||
*) echo "Unknown parameter: $1"; print_usage ;;
|
||||
esac
|
||||
|
|
@ -61,8 +66,8 @@ while [[ "$#" -gt 0 ]]; do
|
|||
done
|
||||
|
||||
# Set derived variables based on selected options
|
||||
APK_OUTPUT_DIR="./androidApp/build/outputs/apk/$BUILD_TYPE"
|
||||
SIGNED_APK_OUTPUT_DIR="./androidApp/build/outputs/apk/$BUILD_TYPE"
|
||||
APK_OUTPUT_DIR="./$MODULE/build/outputs/apk/$BUILD_TYPE"
|
||||
SIGNED_APK_OUTPUT_DIR="./$MODULE/build/outputs/apk/$BUILD_TYPE"
|
||||
|
||||
# Android build-tools path
|
||||
BUILD_TOOLS_PATH="$ANDROID_HOME/build-tools/$(ls $ANDROID_HOME/build-tools | sort | tail -n 1)"
|
||||
|
|
@ -86,7 +91,7 @@ echo "Project cleaned successfully."
|
|||
|
||||
# Step 2: Build the APK
|
||||
echo "[Step 2] Building APK..."
|
||||
./gradlew androidApp:assemble"$BUILD_TYPE"
|
||||
./gradlew "$MODULE":assemble"$BUILD_TYPE"
|
||||
echo "APK built successfully."
|
||||
|
||||
# Step 3: Locate the built APKs
|
||||
|
|
@ -102,8 +107,8 @@ for APK_PATH in $APK_PATHS; do
|
|||
ALIGNED_APK_PATH="$SIGNED_APK_OUTPUT_DIR/aligned-$(basename "${APK_PATH/-unsigned/}")"
|
||||
RELEASE_NAME=$(basename "${APK_PATH/-unsigned/}")
|
||||
RELEASE_NAME="${RELEASE_NAME/app-/}"
|
||||
RELEASE_NAME="${RELEASE_NAME/androidApp-/}"
|
||||
SIGNED_APK_PATH="$SIGNED_APK_OUTPUT_DIR/SimpMusic-$BUILD_VARIANT-$(basename "$RELEASE_NAME")"
|
||||
RELEASE_NAME="${RELEASE_NAME/$MODULE-/}"
|
||||
SIGNED_APK_PATH="$SIGNED_APK_OUTPUT_DIR/SimpMusic-$BUILD_VARIANT$NAME_SUFFIX-$(basename "$RELEASE_NAME")"
|
||||
|
||||
echo "[Step 4] Aligning the APK: $APK_PATH..."
|
||||
if [ ! -f "$ZIPALIGN" ]; then
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.lazy.LazyListItemInfo
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
|
|
@ -65,6 +66,7 @@ import com.maxrave.simpmusic.getPlatform
|
|||
import com.maxrave.simpmusic.ui.theme.LocalAppColors
|
||||
import dev.chrisbanes.haze.blur.HazeBlurStyle
|
||||
import dev.chrisbanes.haze.blur.HazeColorEffect
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
|
@ -531,8 +533,13 @@ fun Palette?.toImmersiveBackground(): Color {
|
|||
|
||||
/**
|
||||
* The frosted top bar of the immersive screens: their own page colour behind a 24dp blur, washed
|
||||
* with that colour again at [tintAlpha]. Blur is forced on, so Android 8–11 takes haze's
|
||||
* RenderScript path instead of a flat scrim.
|
||||
* with that colour again at [tintAlpha]. Where haze does not blur (Android 11 and below), the bar is
|
||||
* that colour, opaque.
|
||||
*
|
||||
* Blur is left at haze's default (Android 12+ and Desktop) on purpose. Forcing it on sends older
|
||||
* Android through haze's RenderScript blur, which crashes natively (SIGSEGV in
|
||||
* `GrallocConsumer::lockNextBuffer`) while one blurred screen replaces another, Library into the
|
||||
* four tiles screen above all. A native crash leaves haze no chance to fall back.
|
||||
*
|
||||
* A plain function, not remembered: `HazeBlurStyle { }` records its writes into a list and compares
|
||||
* by them, so an unchanged tint recomposes into an equal Style and leaves the node alone.
|
||||
|
|
@ -542,12 +549,19 @@ fun barBlurStyle(
|
|||
tintAlpha: Float,
|
||||
): HazeBlurStyle =
|
||||
HazeBlurStyle {
|
||||
blurEnabled(true)
|
||||
blurRadius(24.dp)
|
||||
backgroundColor(tint)
|
||||
colorEffects(listOf(HazeColorEffect.tint(tint.copy(alpha = tintAlpha))))
|
||||
fallbackColorEffect(HazeColorEffect.tint(tint.copy(alpha = 1f)))
|
||||
}
|
||||
|
||||
/** [HazeMaterials.ultraThin] for the plain top bars, with the same opaque fallback as [barBlurStyle]. */
|
||||
@Composable
|
||||
fun ultraThinBarStyle(): HazeBlurStyle {
|
||||
val surface = MaterialTheme.colorScheme.surface
|
||||
return HazeMaterials.ultraThin(surface).then { fallbackColorEffect(HazeColorEffect.tint(surface)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical scrim from [from] to [to] that fades without showing an edge.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ import com.maxrave.simpmusic.extension.artworkScrimBrush
|
|||
import com.maxrave.simpmusic.extension.getScreenSizeInfo
|
||||
import com.maxrave.simpmusic.extension.isScrollingUp
|
||||
import com.maxrave.simpmusic.extension.rgbFactor
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.getPlatform
|
||||
import com.maxrave.simpmusic.ui.component.BlogPromoDialog
|
||||
import com.maxrave.simpmusic.ui.component.CenterLoadingBox
|
||||
|
|
@ -160,7 +161,6 @@ import com.maxrave.simpmusic.viewModel.ListState
|
|||
import com.maxrave.simpmusic.viewModel.SharedViewModel
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import io.ktor.client.HttpClient
|
||||
|
|
@ -809,7 +809,7 @@ fun HomeScreen(
|
|||
Modifier.background(Color.Transparent)
|
||||
} else {
|
||||
Modifier
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) })
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle())
|
||||
},
|
||||
).onGloballyPositioned { coordinates ->
|
||||
topAppBarHeightPx = coordinates.size.height
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.maxrave.domain.data.entities.SongEntity
|
|||
import com.maxrave.domain.mediaservice.handler.PlaylistType
|
||||
import com.maxrave.domain.mediaservice.handler.QueueData
|
||||
import com.maxrave.domain.utils.toTrack
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.ui.component.AddToPlaylistModalBottomSheet
|
||||
import com.maxrave.simpmusic.ui.component.ArtistFullWidthItems
|
||||
import com.maxrave.simpmusic.ui.component.CenterLoadingBox
|
||||
|
|
@ -56,7 +57,6 @@ import com.maxrave.simpmusic.viewModel.SharedViewModel
|
|||
import com.maxrave.simpmusic.viewModel.SongSelectionViewModel
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -234,7 +234,7 @@ fun RecentlySongsScreen(
|
|||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }),
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()),
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.string.recently_added),
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import com.maxrave.domain.utils.LocalResource
|
|||
import com.maxrave.domain.utils.toArrayListTrack
|
||||
import com.maxrave.domain.utils.toTrack
|
||||
import com.maxrave.simpmusic.extension.getStringBlocking
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.ui.component.SearchBarExit
|
||||
import com.maxrave.simpmusic.ui.component.SearchBarEnter
|
||||
import com.maxrave.simpmusic.ui.component.AddToPlaylistModalBottomSheet
|
||||
|
|
@ -79,7 +80,6 @@ import com.maxrave.simpmusic.viewModel.SongSelectionViewModel
|
|||
import com.maxrave.simpmusic.viewModel.SharedViewModel
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import com.maxrave.simpmusic.ui.screen.home.analytics.formatNumericSpan
|
||||
|
|
@ -636,7 +636,7 @@ fun LibraryDynamicPlaylistScreen(
|
|||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }),
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()),
|
||||
colors =
|
||||
TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ import com.maxrave.logger.Logger
|
|||
import com.maxrave.simpmusic.Platform
|
||||
import com.maxrave.simpmusic.extension.copy
|
||||
import com.maxrave.simpmusic.extension.isScrollingUp
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.getPlatform
|
||||
import com.maxrave.simpmusic.ui.component.AddToPlaylistModalBottomSheet
|
||||
import com.maxrave.simpmusic.ui.component.Chip
|
||||
|
|
@ -102,7 +103,6 @@ import com.maxrave.simpmusic.viewModel.TasteUiState
|
|||
import com.maxrave.simpmusic.viewModel.TasteViewModel
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -504,7 +504,7 @@ fun LibraryScreen(
|
|||
Column(
|
||||
Modifier
|
||||
.background(Color.Transparent)
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }).onGloballyPositioned { coordinates ->
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()).onGloballyPositioned { coordinates ->
|
||||
topAppBarHeight = with(density) { coordinates.size.height.toDp() }
|
||||
},
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.maxrave.logger.Logger
|
|||
import com.maxrave.simpmusic.expect.ui.PlatformWebView
|
||||
import com.maxrave.simpmusic.expect.ui.createWebViewCookieManager
|
||||
import com.maxrave.simpmusic.expect.ui.rememberWebViewState
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.ui.component.DevLogInBottomSheet
|
||||
import com.maxrave.simpmusic.ui.component.DevLogInType
|
||||
import com.maxrave.simpmusic.ui.component.RippleIconButton
|
||||
|
|
@ -44,7 +45,6 @@ import com.maxrave.simpmusic.viewModel.LogInViewModel
|
|||
import com.maxrave.simpmusic.viewModel.SettingsViewModel
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -165,7 +165,7 @@ fun LoginScreen(
|
|||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }),
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()),
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.string.log_in),
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.maxrave.simpmusic.expect.ui.PlatformWebView
|
|||
import com.maxrave.simpmusic.expect.ui.createWebViewCookieManager
|
||||
import com.maxrave.simpmusic.expect.ui.rememberWebViewState
|
||||
import com.maxrave.simpmusic.extension.getStringBlocking
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.ui.component.DevCookieLogInBottomSheet
|
||||
import com.maxrave.simpmusic.ui.component.DevLogInBottomSheet
|
||||
import com.maxrave.simpmusic.ui.component.DevLogInType
|
||||
|
|
@ -46,7 +47,6 @@ import com.maxrave.simpmusic.viewModel.LogInViewModel
|
|||
import com.maxrave.simpmusic.viewModel.SettingsViewModel
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
|
@ -195,7 +195,7 @@ fun SpotifyLoginScreen(
|
|||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }),
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()),
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.string.log_in_to_spotify),
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.navigation.NavController
|
||||
import com.maxrave.domain.extension.now
|
||||
import com.maxrave.simpmusic.expect.openUrl
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.ui.component.RippleIconButton
|
||||
import com.maxrave.simpmusic.ui.icon.ArrowBackIosNew
|
||||
import com.maxrave.simpmusic.ui.icon.SimpIcons
|
||||
|
|
@ -53,7 +54,6 @@ import com.mikepenz.markdown.m3.Markdown
|
|||
import com.mikepenz.markdown.m3.markdownTypography
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
|
@ -252,7 +252,7 @@ fun CreditScreen(
|
|||
TopAppBar(
|
||||
modifier =
|
||||
Modifier
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }),
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()),
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.string.app_name),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.maxrave.logger.Logger
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.ui.component.CenterLoadingBox
|
||||
import com.maxrave.simpmusic.ui.component.EndOfPage
|
||||
import com.maxrave.simpmusic.ui.component.HomeItemContentPlaylist
|
||||
|
|
@ -45,7 +46,6 @@ import com.maxrave.simpmusic.viewModel.MoreAlbumsUIState
|
|||
import com.maxrave.simpmusic.viewModel.MoreAlbumsViewModel
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
|
@ -129,7 +129,7 @@ fun MoreAlbumsScreen(
|
|||
TopAppBar(
|
||||
modifier =
|
||||
Modifier
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }),
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()),
|
||||
title = {
|
||||
Text(
|
||||
text = state.title,
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ import com.maxrave.domain.utils.toTrack
|
|||
import com.maxrave.simpmusic.Platform
|
||||
import com.maxrave.simpmusic.expect.ui.PlatformBackHandler
|
||||
import com.maxrave.simpmusic.extension.getScreenSizeInfo
|
||||
import com.maxrave.simpmusic.extension.ultraThinBarStyle
|
||||
import com.maxrave.simpmusic.getPlatform
|
||||
import com.maxrave.simpmusic.ui.component.AddToPlaylistModalBottomSheet
|
||||
import com.maxrave.simpmusic.ui.component.CenterLoadingBox
|
||||
|
|
@ -144,7 +145,6 @@ import com.maxrave.simpmusic.viewModel.SongSelectionViewModel
|
|||
import com.maxrave.simpmusic.viewModel.toStringRes
|
||||
import dev.chrisbanes.haze.HazeInput
|
||||
import dev.chrisbanes.haze.blur.hazeBlur
|
||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -990,7 +990,7 @@ fun SearchScreen(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.hazeBlur(HazeInput.Sources(hazeState), HazeMaterials.ultraThin().then { blurEnabled(true) }),
|
||||
.hazeBlur(HazeInput.Sources(hazeState), ultraThinBarStyle()),
|
||||
)
|
||||
}
|
||||
Column(
|
||||
|
|
|
|||
2
core
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 8f53f61cd6292779fd0ff8d27144aabbd4068336
|
||||
Subproject commit 2cb77b8dc37d3dc9982a957304225ebc1d97bfdd
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
[versions]
|
||||
# App version
|
||||
version-name = "2.2.0"
|
||||
version-code = "59"
|
||||
version-name = "2.3.0"
|
||||
version-code = "60"
|
||||
|
||||
android = "9.2.1"
|
||||
kotlin = "2.4.20"
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ val mediaDir = File(rootDir, "core/media")
|
|||
rootProject.name = "SimpMusic"
|
||||
include(
|
||||
":androidApp",
|
||||
":vivoAndroidApp",
|
||||
":composeApp",
|
||||
":desktopApp",
|
||||
":common",
|
||||
|
|
|
|||
197
vivoAndroidApp/build.gradle.kts
Normal file
197
vivoAndroidApp/build.gradle.kts
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import java.util.Properties
|
||||
|
||||
// SimpMusic for vivo: androidApp's sources, shipped under a package vivo's Origin Island follows.
|
||||
// vivo's island and music widget only follow apps on vivo's own list of package names, and a normal
|
||||
// app cannot add itself to that list. This module builds androidApp unchanged under one of the listed
|
||||
// names. That name must stay in Config.OFFICIAL_PACKAGE_NAMES, and the APK must be signed with our key,
|
||||
// or the unofficial-build check blocks it. Built with: ./build_and_sign_apk.sh --release --full --vivo
|
||||
// Everything except the package and the ABI mirrors androidApp/build.gradle.kts: keep the two in step.
|
||||
|
||||
val isFullBuild: Boolean =
|
||||
try {
|
||||
extra["isFullBuild"] == "true"
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.sentry.gradle)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
}
|
||||
|
||||
val androidAppDir = rootProject.file("androidApp")
|
||||
|
||||
android {
|
||||
namespace = "com.maxrave.simpmusic"
|
||||
compileSdk = 37
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.spotify.music"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode =
|
||||
libs.versions.version.code
|
||||
.get()
|
||||
.toInt()
|
||||
versionName =
|
||||
libs.versions.version.name
|
||||
.get()
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
androidResources {
|
||||
localeFilters +=
|
||||
listOf(
|
||||
"en",
|
||||
"vi",
|
||||
"it",
|
||||
"de",
|
||||
"ru",
|
||||
"tr",
|
||||
"fi",
|
||||
"pl",
|
||||
"pt",
|
||||
"fr",
|
||||
"es",
|
||||
"zh-rCN",
|
||||
"id",
|
||||
"in",
|
||||
"ar",
|
||||
"ja",
|
||||
"zh-rTW",
|
||||
"uk",
|
||||
"iw",
|
||||
"az",
|
||||
"hi",
|
||||
"th",
|
||||
"nl",
|
||||
"ko",
|
||||
"ca",
|
||||
"fa",
|
||||
"bg",
|
||||
"sv",
|
||||
"hr",
|
||||
)
|
||||
}
|
||||
|
||||
// vivo phones with Origin Island are all arm64, so one APK and no ABI splits.
|
||||
ndk {
|
||||
abiFilters.add("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
manifest.srcFile(File(androidAppDir, "src/main/AndroidManifest.xml"))
|
||||
// androidApp keeps its Kotlin under src/main/java; both sets point there so built-in Kotlin
|
||||
// compiles it whichever directory set it reads.
|
||||
java.srcDirs(File(androidAppDir, "src/main/java"))
|
||||
kotlin.srcDirs(File(androidAppDir, "src/main/java"))
|
||||
res.srcDirs(File(androidAppDir, "src/main/res"))
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
File(androidAppDir, "proguard-rules.pro"),
|
||||
)
|
||||
}
|
||||
debug {
|
||||
// No ".dev" suffix: vivo matches the exact package, and a debug build is how this is tried on a vivo phone.
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
packaging {
|
||||
jniLibs.useLegacyPackaging = true
|
||||
jniLibs.excludes +=
|
||||
listOf(
|
||||
"META-INF/META-INF/DEPENDENCIES",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/license.txt",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/NOTICE.txt",
|
||||
"META-INF/notice.txt",
|
||||
"META-INF/ASL2.0",
|
||||
"META-INF/asm-license.txt",
|
||||
"META-INF/notice",
|
||||
"META-INF/*.kotlin_module",
|
||||
)
|
||||
resources {
|
||||
excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
|
||||
// Same reasons as androidApp: kuromoji's duplicate META-INF docs, and its dictionary is
|
||||
// downloaded on demand instead of shipped.
|
||||
excludes += "META-INF/*.md"
|
||||
excludes += "com/atilika/kuromoji/ipadic/*.bin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.desugaring)
|
||||
val debugImplementation = "debugImplementation"
|
||||
debugImplementation(libs.ui.tooling)
|
||||
implementation(libs.activity.compose)
|
||||
implementation(libs.customactivityoncrash)
|
||||
implementation(libs.easypermissions)
|
||||
implementation(libs.legacy.support.v4)
|
||||
implementation(libs.coroutines.android)
|
||||
implementation(libs.glance)
|
||||
implementation(libs.glance.appwidget)
|
||||
implementation(libs.glance.material3)
|
||||
|
||||
implementation(projects.composeApp)
|
||||
implementation(projects.data)
|
||||
|
||||
if (isFullBuild) {
|
||||
implementation(projects.crashlytics)
|
||||
} else {
|
||||
implementation(projects.crashlyticsEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
// Only the Full build is shipped for vivo, so androidApp's FOSS-only Sentry meta cleanup is not repeated.
|
||||
sentry {
|
||||
org.set("simpmusic")
|
||||
projectName.set("android")
|
||||
ignoredBuildTypes.set(setOf("debug"))
|
||||
autoInstallation.enabled = false
|
||||
if (isFullBuild) {
|
||||
val token =
|
||||
try {
|
||||
val properties = Properties()
|
||||
properties.load(rootProject.file("local.properties").inputStream())
|
||||
properties.getProperty("SENTRY_AUTH_TOKEN")
|
||||
} catch (e: Exception) {
|
||||
println("Failed to load SENTRY_AUTH_TOKEN from local.properties: ${e.message}")
|
||||
null
|
||||
}
|
||||
authToken.set(token ?: "")
|
||||
includeProguardMapping.set(true)
|
||||
autoUploadProguardMapping.set(true)
|
||||
} else {
|
||||
includeProguardMapping.set(false)
|
||||
autoUploadProguardMapping.set(false)
|
||||
uploadNativeSymbols.set(false)
|
||||
includeDependenciesReport.set(false)
|
||||
includeSourceContext.set(false)
|
||||
includeNativeSources.set(false)
|
||||
}
|
||||
telemetry.set(false)
|
||||
}
|
||||
34
vivoAndroidApp/src/release/res/xml/shortcuts.xml
Normal file
34
vivoAndroidApp/src/release/res/xml/shortcuts.xml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- vivo build: the same shortcuts, pointed at this build's own package (androidApp's copy targets com.maxrave.simpmusic). -->
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:shortcutId="home"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/round_home_24"
|
||||
android:shortcutShortLabel="@string/home">
|
||||
<intent
|
||||
android:targetClass="com.maxrave.simpmusic.MainActivity"
|
||||
android:targetPackage="com.spotify.music"
|
||||
android:action="com.maxrave.simpmusic.action.HOME" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:shortcutId="search"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/round_search_24"
|
||||
android:shortcutShortLabel="@string/search">
|
||||
<intent
|
||||
android:targetClass="com.maxrave.simpmusic.MainActivity"
|
||||
android:targetPackage="com.spotify.music"
|
||||
android:action="com.maxrave.simpmusic.action.SEARCH" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:shortcutId="library"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/round_library_music_24"
|
||||
android:shortcutShortLabel="@string/library">
|
||||
<intent
|
||||
android:targetClass="com.maxrave.simpmusic.MainActivity"
|
||||
android:targetPackage="com.spotify.music"
|
||||
android:action="com.maxrave.simpmusic.action.LIBRARY" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
Loading…
Reference in a new issue