diff --git a/.dockerignore b/.dockerignore
index f4a18b9..bd35756 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,5 +2,5 @@
*
!server/stemd.py
!server/stemd-requirements.txt
-!stemroller/*.py
-!stemroller/worker/*.py
+!stemify/*.py
+!stemify/worker/*.py
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9e55105
--- /dev/null
+++ b/README.md
@@ -0,0 +1,562 @@
+# Stemify
+
+**Your whole music library, taken apart and put back together however you like.**
+
+Ever wanted to hear "Carry On Wayward Son" without the vocals so you can belt it out yourself? Or
+mute the drums on a song to figure out what the bass player is actually doing back there? Stemify
+splits every song in your library into its separate parts (vocals, drums, bass, guitar, piano and
+"other"), keeps them organised next to your music, and gives you players that let you mix them
+live, on your phone, in your car, and on your desktop.
+
+It's a home-grown, slightly over-engineered love letter to karaoke, music nerdery and the joy of
+pressing the "mute vocals" button at exactly the wrong moment.
+
+---
+
+## Contents
+
+- [The big picture](#the-big-picture)
+- [What you get](#what-you-get)
+- [Setting it up](#setting-it-up)
+ - [1. The server: stemd and stemserve](#1-the-server-stemd-and-stemserve)
+ - [2. The Android app](#2-the-android-app)
+ - [3. The desktop apps (optional)](#3-the-desktop-apps-optional)
+- [Using the Android app](#using-the-android-app)
+- [Stem Now: skipping the line](#stem-now-skipping-the-line)
+- [The Equalizer](#the-equalizer)
+- [Android Auto](#android-auto)
+- [The desktop apps](#the-desktop-apps)
+- [Separation models](#separation-models)
+- [How the pieces find each other](#how-the-pieces-find-each-other)
+- [stemserve API](#stemserve-api)
+- [Building and testing](#building-and-testing)
+- [Repo tour](#repo-tour)
+- [Troubleshooting and FAQ](#troubleshooting-and-faq)
+- [Credits](#credits)
+- [License](#license)
+
+---
+
+## The big picture
+
+There are three moving parts, and they all agree on one simple idea: **the stems folder mirrors
+the music folder.** If your song lives at `Kansas/Leftoverture/01 Carry On.flac`, its stems live at
+`Stems/Kansas/Leftoverture/01 Carry On/`. That's it. That's the whole trick.
+
+```
+ ┌──────────────────────────── your server (unraid, in our case) ───────────────────────────┐
+ │ │
+ │ Music/ ──read──▶ stemd (GPU) ──writes──▶ Stems/ ──read──▶ stemserve (:8765) ─────┼──▶ phone, car, desktop
+ │ ▲ ▲ │ │
+ │ │ └──────── "Stem Now" requests ◀──────────────┘ │
+ │ Plex Media Server (:32400) ──────────────────────────────────────────────────────────────┼──▶ library, artwork, playlists
+ └───────────────────────────────────────────────────────────────────────────────────────────┘
+```
+
+- **stemd** sits on the server and chews through your library on the GPU, one song at a time,
+ forever. New album? It notices within half an hour and gets to work.
+- **stemserve** hands the finished stems to your devices over HTTP, with seeking, waveforms and
+ optional Opus transcodes so your phone storage doesn't cry.
+- **Plex** stays in charge of what it's good at: your library, metadata, artwork and playlists.
+- **The apps** (Android, Android Auto, and two desktop apps) play everything, and quietly fall back
+ to the original file for any song that hasn't been separated yet. Nothing is ever unplayable.
+
+---
+
+## What you get
+
+**On your phone (Android)**
+- A Plex music player with per-stem faders, mute and solo, and one-tap mixes: Full, Karaoke,
+ A Cappella, No Drums, Rhythm, No Guitar, No Bass.
+- A Plexamp-style waveform seek bar that doubles as a volume control (drag sideways to seek, up and
+ down for volume). The waveform is scaled to each song, so you can actually see the quiet verse
+ and the big chorus.
+- Live level meters for every stem, updated every frame and in sync with what you hear.
+- A 10-band graphic EQ **plus** Equalizer APO-style parametric filters, with per-device profiles
+ that switch automatically when you plug in headphones or connect to Bluetooth.
+- Paste in an AutoEQ correction for your headphones and you're done.
+- Downloads for offline listening, stems and all.
+- Gapless playback, a proper queue, playlists (create, reorder, rename), search, and "On This
+ Day" album birthdays on the home screen.
+- "Stem Now": jump any song, album, artist or playlist to the front of the separation queue.
+- An iOS-flavoured look in Black, Dark or White, at whatever refresh rate your panel can manage.
+
+**In your car (Android Auto)**
+- A browse tree built for a rotary knob and no touchscreen, with voice search, a Vocals on/off
+ button and a Mix button right next to play/pause.
+
+**On your desktop (Linux)**
+- **Stemify (the separator)**: drag a song onto the window, get stems out the other side, preview them in sync,
+ export them. Material layout meets Windows 7 Aero glass, running at 240 Hz because why not.
+- **Stemify Player**: your Plex library and its stems, with full mixing... dressed up as
+ Microsoft Excel 2016. Yes, really. Your boss will think you're working on the quarterly numbers.
+
+**On the server**
+- **stemd** separates everything automatically with BS-RoFormer SW (6 stems) on an NVIDIA GPU,
+ politely, at low priority, sharing the card with Plex transcodes.
+- **stemserve** is one Python file using only the standard library. ffmpeg is optional.
+
+---
+
+## Setting it up
+
+The recommended order is server first, then phone, then (if you like) desktop. The examples below
+use unraid paths and the addresses from the original setup (`192.168.86.2`). Swap in your own.
+
+### 1. The server: stemd and stemserve
+
+You need Docker, and for stemd an NVIDIA GPU (on unraid, install the **Nvidia-Driver** plugin from
+the Apps tab). A CPU-only build exists for testing, but separating a whole library on a CPU is a
+good way to celebrate several birthdays while you wait.
+
+**Folders first** (owned by `99:100`, unraid's nobody:users):
+
+```sh
+mkdir -p /mnt/user/appdata/stemserve/cache \
+ /mnt/user/appdata/stemd/models \
+ /mnt/user/appdata/stemd/state/queue
+chown -R 99:100 /mnt/user/appdata/stemserve /mnt/user/appdata/stemd
+```
+
+**stemd** builds from the repo root because it borrows the `stemify` package. Copy the repo (or
+at least `stemify/`, `server/` and `.dockerignore`) to `/mnt/user/appdata/stemd/src`, then:
+
+```sh
+docker build -f /mnt/user/appdata/stemd/src/server/stemd.Dockerfile -t stemd /mnt/user/appdata/stemd/src
+docker run -d --name stemd --restart unless-stopped --stop-timeout 60 \
+ --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all -e NVIDIA_DRIVER_CAPABILITIES=compute,utility \
+ -v "/mnt/user/Music/Actual music/Flacs":/music:ro \
+ -v /mnt/user/Music/Stems:/stems \
+ -v /mnt/user/appdata/stemd/models:/models \
+ -v /mnt/user/appdata/stemd/state:/state \
+ stemd
+docker logs -f stemd
+```
+
+The first song downloads the model (about 700 MB). After that the log reads like a very calm
+sports commentary: how many tracks are left, which one it's on, and how many times faster than
+realtime it went.
+
+**stemserve** is tiny. Copy the `server/` folder to `/mnt/user/appdata/stemserve` and either use
+the included compose file:
+
+```sh
+cd /mnt/user/appdata/stemserve && docker compose up -d --build
+```
+
+or run it by hand:
+
+```sh
+docker build -t stemserve /mnt/user/appdata/stemserve
+docker run -d --name stemserve --restart unless-stopped -p 8765:8765 \
+ -v /mnt/user/Music/Stems:/stems:ro \
+ -v /mnt/user/appdata/stemserve/cache:/cache \
+ -v /mnt/user/appdata/stemd/state/queue:/queue \
+ stemserve
+```
+
+Check it from any machine on your network:
+
+```sh
+curl http://192.168.86.2:8765/api/info
+```
+
+If you see a happy little JSON blob with a track count, you're in business.
+
+
+Server settings (environment variables)
+
+**stemd**
+
+| Variable | Default | What it does |
+|---|---|---|
+| `MUSIC_DIR` | `/music` | Your music library (read-only is fine) |
+| `STEMS_DIR` | `/stems` | Where stems go |
+| `STATE_DIR` | `/state` | The failed-track list lives here |
+| `QUEUE_DIR` | `STATE_DIR/queue` | Where "Stem Now" requests arrive |
+| `MODELS_DIR` | `/models` | Model weights, downloaded on first use |
+| `MODEL` | `roformer_bs_sw` | Any model id from [the table below](#separation-models) |
+| `DEVICE` | `cuda` | `cuda`, `cuda:N` or `cpu` |
+| `RESCAN_MIN` | `30` | Minutes between library scans once it's caught up |
+| `FP16` | `1` | Half precision on the GPU (`0` for full fp32) |
+| `OVERLAP`, `SEGMENT`, `SHIFTS` | `0.25`, `0`, `0` | Separation quality knobs, same as the desktop app |
+
+`docker build --build-arg TORCH_INDEX=cpu ...` makes a small GPU-less image for testing.
+
+**stemserve**
+
+| Variable | Default | What it does |
+|---|---|---|
+| `STEMS_DIR` | `/stems` | The stems folder |
+| `CACHE_DIR` | `/cache` | Opus transcodes and waveforms (safe to delete any time) |
+| `QUEUE_DIR` | `/queue` | stemd's "Stem Now" folder, shared with it |
+| `PORT` | `8765` | Listen port |
+| `TOKEN` | empty | If set, every request needs `X-Stem-Token` or `?token=` |
+| `INDEX_TTL` | `30` | Seconds between rescans of the stems folder |
+
+
+
+**Updating later:** copy the new files over, rebuild the image, `docker rm -f` the container, and
+run it again. Your stems, models and settings live in the mounted folders, so nothing is lost.
+Update stemd before stemserve: if you separated songs before the project was called Stemify,
+stemd renames their old done-markers on its first start so nothing gets separated twice.
+
+### 2. The Android app
+
+Built for (and lovingly tested on) a OnePlus 15, but any recent Android phone should be happy.
+
+1. Grab `Stemify.apk` from the Releases page, or [build it yourself](#building-and-testing),
+ then sideload it:
+ ```sh
+ adb install -r Stemify.apk
+ ```
+2. Open it and go to **Library → Connect** (or Settings → Plex). Enter the 4-character code at
+ [plex.tv/link](https://plex.tv/link) and pick your music library.
+3. Visit **Settings → Stem Server** and point it at your stemserve. It should say "Online" with a
+ song count. (The default address is `http://192.168.86.2:8765`, which is almost certainly not
+ yours.)
+
+The app checks stemserve for freshly separated songs every minute, so your library slowly "lights
+up" as stemd works through it.
+
+### 3. The desktop apps (optional)
+
+Linux, Python 3.12 and [uv](https://docs.astral.sh/uv/). The default setup uses PyTorch for ROCm
+(AMD GPUs, developed on an RX 9070 XT). NVIDIA and CPU work too if you point the torch index
+elsewhere in `pyproject.toml`.
+
+```sh
+uv sync
+uv run stemify # the drag-and-drop separator
+uv run stemify-player # the Excel-shaped music player
+./scripts/install-desktop.sh # adds both to your app grid ("--uninstall" removes them)
+```
+
+---
+
+## Using the Android app
+
+**Tabs:** Library, Playlists, Search, Downloads and Settings.
+
+**Stems are the norm, so the exceptions are what's marked.** Songs without stems yet say
+**NO STEMS**, and albums and artists say **NO STEMS** or something like **7/10 STEMS** until
+they're done. Those songs still play, just from the original file.
+
+**Hold anything** (a song, album, artist or playlist) for:
+
+- **Add to Front of Queue** / **Add to End of Queue**
+- **Download** (or **Remove Download**)
+- **Add to Playlist** (playlists can even go into other playlists)
+- **Stem Now**, while any of it lacks stems
+
+**The Now Playing screen** has the waveform seek bar, the mixer (faders, mute, solo, mix presets,
+meters, master volume) and the queue. Your mix can carry over from song to song or reset each
+time, your choice.
+
+**Downloads** are proper offline copies: every stem fully fetched (or the original file for songs
+without stems). Sort by download order, artist or release date.
+
+**Stem Quality** decides how stems travel from the server: lossless FLAC, or Opus at a bitrate of
+your choosing, which takes roughly an eighth of the space and is transcoded and cached on the
+server.
+
+**Streaming** needs your home network (Plex and stemserve are plain HTTP on the LAN). Anything
+you've downloaded or already streamed plays anywhere.
+
+---
+
+## Stem Now: skipping the line
+
+stemd works through your library on its own, but a big library takes days. If you're desperate to
+karaoke your way through an album tonight, hold it and choose **Stem Now**.
+
+Here's what happens:
+
+1. The app sends the songs that still lack stems to stemserve.
+2. stemserve drops a little request file into stemd's queue folder.
+3. stemd finishes the song it's on, then does yours next, ahead of everything else. If it was
+ idle, it starts within about 5 seconds.
+4. About a minute after each song finishes, the app notices and plays it with stems from then on.
+
+Requests are handled oldest first and survive restarts. stemd matches the paths Plex reports to
+files in its own music folder by their artist/album/file tail, so it doesn't matter that Plex and
+stemd see the library under different mount points. Songs that failed before (corrupt files, or
+anything over 40 minutes) are skipped; delete `state/failed.json` to give them all another shot.
+
+---
+
+## The Equalizer
+
+Find it in **Settings → Equalizer**. It's two equalizers wearing one trench coat.
+
+**The graphic EQ** has ten octave bands from 31 Hz to 16 kHz, a response curve, and a pile of
+presets borrowed in spirit from iOS (Bass Booster, Late Night, Vocal Booster and friends).
+
+**Parametric Filters** are modelled on [Equalizer APO](https://sourceforge.net/projects/equalizerapo/):
+
+| Type | What it's for |
+|---|---|
+| Peak | Boost or cut around one frequency |
+| Low Shelf / High Shelf | Tilt everything below or above a frequency |
+| Low Pass / High Pass | Roll off the top or bottom, at 6, 12, 24, 36 or 48 dB per octave |
+| Band Pass | Keep only a band |
+| Notch | Surgically remove one frequency (hello, 60 Hz hum) |
+| All Pass | Shift phase without changing level, for the true connoisseur |
+
+Every filter has frequency, gain and Q, can apply to **both channels, or just left or right**,
+and can be switched off on its own to compare. Drag a filter's handle on its curve (sideways for
+frequency, up and down for gain), use the sliders, or tap any value to type it in.
+
+**Headphone corrections in two taps:** copy a `ParametricEQ.txt` from
+[AutoEQ](https://github.com/jaakkopasanen/AutoEq) for your headphones, then on the Filters page
+choose **••• → Paste APO Config**. You can also **Copy as APO Config** to take your settings to a
+PC.
+
+**Profiles** link to output devices: the phone speaker, wired headphones, each Bluetooth headset,
+USB DACs. Plug something in and its profile switches on automatically, or pick one by hand.
+
+**Preamp and anti-clipping:** each profile has its own manual preamp, and the anti-clipping switch
+adds just enough extra cut that a full-scale signal at the curve's loudest point still peaks at
+0 dBFS. A limiter on the master bus catches anything else, so cranking the bass won't make things
+crackle.
+
+---
+
+## Android Auto
+
+Designed for a 2016 Mazda MX-5 with the Mazda Connect retrofit: an 800×480 screen with touch
+disabled, driven entirely by the commander knob and the steering wheel buttons. If it works there,
+it'll work in your car.
+
+- **Four tabs:** Playlists, Albums, Artists, Downloads (Downloads comes first when you're away
+ from home).
+- **Shallow and short:** every song list starts with **Shuffle all**, and anything longer than 40
+ items splits into A to Z folders, so you never have to twiddle the knob through 400 songs at a
+ red light.
+- **Now playing:** next to the transport controls you get **Vocals** on/off (instant karaoke),
+ **Mix** (cycles through the presets), shuffle and repeat. The current mix shows on the artist
+ line, e.g. "Black Sabbath · Karaoke".
+- **Voice:** hold the talk button and say "play Paranoid on Stemify". Playlists, artists,
+ albums and songs all work.
+- **Offline:** away from home, the app quickly notices the server is unreachable and lists only
+ songs that will actually play (downloaded or fully cached).
+
+**Setting up the car (once):** Android Auto hides sideloaded apps until you allow them. On the
+phone, search Settings for "Android Auto", tap **Version** ten times, then in the ⋮ menu go to
+**Developer settings** and tick **Unknown sources**. Stemify then shows up in Auto's app
+launcher.
+
+**Testing without a car:** Google's Desktop Head Unit can impersonate the Miata. On the phone,
+start the head unit server from Auto's developer settings, then:
+
+```sh
+adb forward tcp:5277 tcp:5277
+~/Android/Sdk/extras/google/auto/desktop-head-unit -c ~/Android/Sdk/extras/google/auto/config/rotary.ini
+```
+
+---
+
+## The desktop apps
+
+### Stemify (the separator)
+
+Drop files onto the window. Each becomes a queue item, separation starts right away with your
+last-used model and device, and when it's done you get a lane per stem with a waveform, solo, mute,
+gain and export. Export everything, one stem, or your current mix, as WAV, FLAC or MP3.
+
+Other niceties:
+
+- Separation runs in its own worker process, so the UI never stutters, even mid-separation.
+- Works on AMD (ROCm), NVIDIA and CPU, and shows free VRAM.
+- Results are cached, so flipping back to an old song is instant (with a size limit you can set).
+- A dev frame-time overlay, for anyone who wants proof of the 240 fps claims.
+
+### Stemify Player (the spreadsheet)
+
+The desktop sibling of the phone app, disguised as Microsoft Excel 2016. Some highlights from the
+translation guide:
+
+| In Excel | In Stemify Player |
+|---|---|
+| Sheets (Songs, Artists, Albums, Playlists, Stems, Queue) | Your library |
+| "Tell me what you want to do" | Search |
+| Zoom slider | Master volume |
+| Cell Styles | Mix presets (Karaoke is filed under "Bad", A Cappella under "Good") |
+| AutoSum | AutoShuffle |
+| Undo / Redo | Previous / next song |
+| Format task pane | Now playing, with the stems as a column chart you can drag |
+| `#GETTING_DATA` | A playlist loading from Plex |
+
+It reports plays to Plex, supports media keys and desktop media controls (MPRIS), and remembers
+your queue, position and mix between launches. The full tour is in
+[`stemify/player/README.md`](stemify/player/README.md).
+
+---
+
+## Separation models
+
+| Model id | Name | Stems | Notes |
+|---|---|---|---|
+| `htdemucs` | HT Demucs | 4 | Fast |
+| `htdemucs_ft` | HT Demucs FT | 4 | Fine-tuned, about 4x slower |
+| `htdemucs_6s` | HT Demucs 6-stem | 6 | Adds guitar and piano |
+| `roformer_bs_vocals` | BS-RoFormer Vocals | 2 | Vocals / instrumental (Viperx 1297) |
+| `roformer_mel_kim_ft` | Mel-RoFormer Vocals FT | 2 | Cleanest vocals (Kim FT by unwa) |
+| `roformer_bs_sw` | BS-RoFormer SW 6-stem | 6 | stemd's default (by jarredou) |
+
+The 4-stem models give vocals, drums, bass and other. The 6-stem ones add guitar and piano.
+
+---
+
+## How the pieces find each other
+
+The stems folder mirrors the music folder, with one folder per song:
+
+```
+Music/Kansas/Leftoverture/01 Carry On Wayward Son.flac
+Stems/Kansas/Leftoverture/01 Carry On Wayward Son/
+ .stemify.json ← the "this one's done" marker
+ 01 Carry On Wayward Son - vocals.flac
+ 01 Carry On Wayward Son - drums.flac
+ 01 Carry On Wayward Son - bass.flac
+ ...
+```
+
+- Stems are written as 24-bit FLAC into a hidden `.partial` folder first, and the marker goes in
+ last, so a separation interrupted by a reboot is simply redone next time. No half-baked songs.
+- When a folder holds the same song in several formats, the best one wins (FLAC/WAV, then M4A,
+ Opus/Ogg, MP3, WMA).
+- Plex reports file paths as *its* container sees them, so the apps match them to stems by suffix,
+ checking durations along the way, and learn the path prefix from the first confident match. A
+ song called "Intro.flac" on one album will never steal another album's intro.
+
+---
+
+## stemserve API
+
+| Request | Returns |
+|---|---|
+| `GET /api/info` | Track count, whether ffmpeg is available, whether Stem Now is set up |
+| `GET /api/index` | Every finished song (with ETag, so unchanged indexes cost one tiny 304) |
+| `GET /api/peaks?key=K&n=256` | Per-stem loudness envelopes for waveforms, 0 to 1000 |
+| `POST /api/prepare?key=K&fmt=opus&br=256` | Starts Opus transcodes in the background |
+| `POST /api/stem` with `{"paths": [...]}` | Stem Now: stemd separates these Plex files next |
+| `GET /file/K/.flac[?fmt=opus&br=256]` | The stem itself, with HTTP range support for seeking |
+
+`K` is the song's folder under the stems root, e.g. `Kansas/Leftoverture/01 Carry On Wayward Son`.
+Want to run it outside Docker? It's one file:
+
+```sh
+STEMS_DIR=/path/to/Stems CACHE_DIR=~/.cache/stemserve python3 server/stemserve.py
+```
+
+---
+
+## Building and testing
+
+**Android** needs JDK 21 and the Android SDK (`android/local.properties` points at it):
+
+```sh
+cd android
+JAVA_HOME=/path/to/jdk-21 ./gradlew assembleRelease # app/build/outputs/apk/release/
+JAVA_HOME=/path/to/jdk-21 ./gradlew testDebugUnitTest # stem matching, EQ and filter maths, limiter, cache, car tree
+```
+
+The cache and index tests stream real stems from a stemserve on `localhost:8765` and skip
+themselves when there isn't one. Debug builds include a hook for screenshots and back-gesture
+testing (`app/src/debug`); release builds don't.
+
+**Desktop** apps can be checked without opening a window:
+
+```sh
+QT_QPA_PLATFORM=offscreen uv run stemify-player --no-mpris --screenshot /tmp/p.png@5 --quit-after 6
+```
+
+---
+
+## Repo tour
+
+```
+android/ the Android and Android Auto app (Kotlin, Jetpack Compose, Media3)
+ app/src/main/java/com/stemify/mobile/
+ plex/ Plex library, search, playlists, artwork, play reporting
+ stems/ stemserve client, path matching, which albums have stems
+ cache/ sparse block cache for streaming, and downloads
+ engine/ decoders, the sample-locked mixer, EQ, limiter, meters
+ eq/ EQ profiles, device switching, APO config import/export
+ playback/ queue, audio focus, mix presets, the Media3 session
+ auto/ Android Auto browse tree, voice search, offline mode
+ ui/ every screen, hand-built iOS-style components
+server/
+ stemd.py the GPU separation daemon
+ stemserve.py the stem file server (standard library only)
+ *.Dockerfile, docker-compose.yml
+stemify/ the desktop separator (PySide6 + QML) and shared separation code
+ worker/ separation backends (Demucs, RoFormer) in a separate process
+ player/ Stemify Player, the Excel-shaped one
+ library_core.py the folder layout rules shared with stemd
+scripts/install-desktop.sh
+specs.txt the original design doc for the desktop separator
+```
+
+---
+
+## Troubleshooting and FAQ
+
+**A song says NO STEMS and I'm impatient.**
+Hold it and choose **Stem Now**. It'll be next in line.
+
+**Stem Now says "stemserve needs updating" or "queue folder isn't mounted".**
+stemserve is older than the app, or it's missing the `/queue` mount. See
+[the server setup](#1-the-server-stemd-and-stemserve). The queue folder needs to be owned by
+`99:100` *before* the container first starts, or Docker creates it as root and nobody can write
+to it.
+
+**stemd says it's separating on the CPU.**
+The container can't see the GPU. Make sure it's running with `--runtime=nvidia` and the NVIDIA
+environment variables shown above.
+
+**stemd keeps saying "out of GPU memory".**
+Something else (Plex transcoding, a game server, your ambitions) is hogging VRAM. stemd shrugs and
+retries that song on the next pass. Nothing is marked failed.
+
+**The Stem Server page says Offline.**
+Check the address in Settings → Stem Server, check `curl http://:8765/api/info` from
+another machine, and if you set a `TOKEN` on the server, set the same one in the app.
+
+**The app doesn't show up in Android Auto.**
+Sideloaded apps need **Unknown sources** turned on in Auto's developer settings. See
+[setting up the car](#android-auto).
+
+**Some songs are marked failed in `state/failed.json`.**
+Usually corrupt files, or "songs" over 40 minutes (those are almost always DJ mixes or radio rips).
+Failed tracks retry automatically when the file changes; delete the file to retry everything.
+
+**Why is the desktop player an Excel spreadsheet?**
+We don't talk about it.
+
+---
+
+## Credits
+
+Standing on the shoulders of some very clever people:
+
+- [Demucs](https://github.com/adefossez/demucs) by Alexandre Défossez
+- BS-RoFormer and Mel-RoFormer models, including the SW 6-stem model by jarredou, Viperx's 1297,
+ and Kim's vocal model fine-tuned by unwa, via
+ [python-audio-separator](https://github.com/nomadkaraoke/python-audio-separator)
+- [PyTorch](https://pytorch.org/), [PySide6 / Qt](https://www.qt.io/qt-for-python),
+ [Jetpack Compose](https://developer.android.com/compose) and
+ [Media3](https://developer.android.com/media/media3)
+- [Equalizer APO](https://sourceforge.net/projects/equalizerapo/) and
+ [AutoEQ](https://github.com/jaakkopasanen/AutoEq), whose formats the EQ speaks
+- The Inter, Selawik and Carlito fonts (licences included alongside them)
+- [Plex](https://www.plex.tv/), for keeping the library tidy so we didn't have to
+
+Stemify isn't affiliated with Plex, Google, Microsoft or Mazda. It just likes them.
+
+---
+
+## License
+
+[GPL-3.0](LICENSE). Take it apart and put it back together however you like, just like the songs.
diff --git a/android/README.md b/android/README.md
index 30cb5e5..0473d6c 100644
--- a/android/README.md
+++ b/android/README.md
@@ -1,4 +1,4 @@
-# StemRoller for Android
+# Stemify for Android
A Plex music player for the OnePlus 15 that plays the stems stemd separates on your
unraid server: per-stem faders, mute/solo, karaoke-style presets, a master volume drawn
@@ -8,10 +8,10 @@ iOS look in Black, Dark or White (flat colours, no gradients).
## Install
-`../StemRoller.apk` is a release build signed with the debug key, ready to sideload:
+`../Stemify.apk` is a release build signed with the debug key, ready to sideload:
```sh
-adb install -r ../StemRoller.apk
+adb install -r ../Stemify.apk
```
## First run
@@ -87,7 +87,7 @@ that's what is shaped for the knob (`auto/`, `playback/PlaybackService.kt`):
shows on the artist line ("Black Sabbath · Karaoke"), since a button press gets no other
feedback. Songs without stems just get shuffle and repeat, and say "No stems" in the lists. The same buttons appear on the
phone's lock-screen controls.
-- **Voice:** hold the wheel's talk button: "play Paranoid on StemRoller" (a playlist,
+- **Voice:** hold the wheel's talk button: "play Paranoid on Stemify" (a playlist,
artist, album or song; artists shuffle). There's no keyboard while driving, so Auto's
search button is voice too.
- **Offline:** in the car the phone is on mobile data and Plex/stemserve are LAN-only. The
@@ -103,7 +103,7 @@ Auto hides sideloaded apps until you allow them:
1. Phone Settings → search "Android Auto" → scroll to the bottom and tap **Version** ten
times → OK.
2. ⋮ menu → **Developer settings** → tick **Unknown sources**.
-3. Plug into the Miata. StemRoller is in Auto's app launcher (the grid of icons).
+3. Plug into the Miata. Stemify is in Auto's app launcher (the grid of icons).
Download what you want for the drive while on home Wi-Fi.
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index d9a530a..8929f27 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -8,11 +8,11 @@ plugins {
}
android {
- namespace = "com.stemroller.mobile"
+ namespace = "com.stemify.mobile"
compileSdk = 36
defaultConfig {
- applicationId = "com.stemroller.mobile"
+ applicationId = "com.stemify.mobile"
minSdk = 33
targetSdk = 36
versionCode = 1
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
index 7c714e8..0d5acdc 100644
--- a/android/app/proguard-rules.pro
+++ b/android/app/proguard-rules.pro
@@ -1,10 +1,10 @@
# kotlinx.serialization keeps its generated serializers via its own consumer rules.
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.**
--keep,includedescriptorclasses class com.stemroller.mobile.**$$serializer { *; }
--keepclassmembers class com.stemroller.mobile.** {
+-keep,includedescriptorclasses class com.stemify.mobile.**$$serializer { *; }
+-keepclassmembers class com.stemify.mobile.** {
*** Companion;
}
--keepclasseswithmembers class com.stemroller.mobile.** {
+-keepclasseswithmembers class com.stemify.mobile.** {
kotlinx.serialization.KSerializer serializer(...);
}
diff --git a/android/app/src/debug/java/com/stemroller/mobile/DebugCapture.kt b/android/app/src/debug/java/com/stemify/mobile/DebugCapture.kt
similarity index 89%
rename from android/app/src/debug/java/com/stemroller/mobile/DebugCapture.kt
rename to android/app/src/debug/java/com/stemify/mobile/DebugCapture.kt
index 71a09f0..63dbcdc 100644
--- a/android/app/src/debug/java/com/stemroller/mobile/DebugCapture.kt
+++ b/android/app/src/debug/java/com/stemify/mobile/DebugCapture.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile
+package com.stemify.mobile
import android.app.Activity
import android.app.Application
@@ -17,10 +17,10 @@ import java.io.File
import java.lang.ref.WeakReference
/**
- * `adb shell am broadcast -a com.stemroller.mobile.CAPTURE -p com.stemroller.mobile`
+ * `adb shell am broadcast -a com.stemify.mobile.CAPTURE -p com.stemify.mobile`
* writes files/capture.png: the current activity drawn in software.
*
- * `... -a com.stemroller.mobile.BACK --es phase start|progress|commit|cancel --ef p 0.4`
+ * `... -a com.stemify.mobile.BACK --es phase start|progress|commit|cancel --ef p 0.4`
* feeds the predictive back callbacks the way a system swipe would.
*/
class DebugCapture : ContentProvider() {
@@ -45,7 +45,7 @@ class DebugCapture : ContentProvider() {
v.draw(Canvas(bmp))
File(a.filesDir, "capture.png").outputStream().use { bmp.compress(Bitmap.CompressFormat.PNG, 100, it) }
}
- }, IntentFilter("com.stemroller.mobile.CAPTURE"), Context.RECEIVER_EXPORTED)
+ }, IntentFilter("com.stemify.mobile.CAPTURE"), Context.RECEIVER_EXPORTED)
app.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(c: Context, i: Intent) {
val a = current.get() as? ComponentActivity ?: return
@@ -59,7 +59,7 @@ class DebugCapture : ContentProvider() {
"cancel" -> d.dispatchOnBackCancelled()
}
}
- }, IntentFilter("com.stemroller.mobile.BACK"), Context.RECEIVER_EXPORTED)
+ }, IntentFilter("com.stemify.mobile.BACK"), Context.RECEIVER_EXPORTED)
return true
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0a98b57..2a83e12 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -19,7 +19,7 @@
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
- android:theme="@style/Theme.StemRoller"
+ android:theme="@style/Theme.Stemify"
tools:targetApi="36">
diff --git a/android/app/src/main/java/com/stemroller/mobile/App.kt b/android/app/src/main/java/com/stemify/mobile/App.kt
similarity index 90%
rename from android/app/src/main/java/com/stemroller/mobile/App.kt
rename to android/app/src/main/java/com/stemify/mobile/App.kt
index a73a093..a291898 100644
--- a/android/app/src/main/java/com/stemroller/mobile/App.kt
+++ b/android/app/src/main/java/com/stemify/mobile/App.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile
+package com.stemify.mobile
import android.app.Application
import android.content.pm.ApplicationInfo
@@ -11,16 +11,16 @@ import coil3.memory.MemoryCache
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.allowHardware
import coil3.request.crossfade
-import com.stemroller.mobile.auto.HomeReach
-import com.stemroller.mobile.cache.Downloads
-import com.stemroller.mobile.cache.MediaCache
-import com.stemroller.mobile.core.Connectivity
-import com.stemroller.mobile.core.Settings
-import com.stemroller.mobile.eq.EqController
-import com.stemroller.mobile.playback.PlaybackManager
-import com.stemroller.mobile.plex.PlexClient
-import com.stemroller.mobile.stems.StemCoverage
-import com.stemroller.mobile.stems.StemServer
+import com.stemify.mobile.auto.HomeReach
+import com.stemify.mobile.cache.Downloads
+import com.stemify.mobile.cache.MediaCache
+import com.stemify.mobile.core.Connectivity
+import com.stemify.mobile.core.Settings
+import com.stemify.mobile.eq.EqController
+import com.stemify.mobile.playback.PlaybackManager
+import com.stemify.mobile.plex.PlexClient
+import com.stemify.mobile.stems.StemCoverage
+import com.stemify.mobile.stems.StemServer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
diff --git a/android/app/src/main/java/com/stemroller/mobile/MainActivity.kt b/android/app/src/main/java/com/stemify/mobile/MainActivity.kt
similarity index 96%
rename from android/app/src/main/java/com/stemroller/mobile/MainActivity.kt
rename to android/app/src/main/java/com/stemify/mobile/MainActivity.kt
index 8c4f579..6c1378a 100644
--- a/android/app/src/main/java/com/stemroller/mobile/MainActivity.kt
+++ b/android/app/src/main/java/com/stemify/mobile/MainActivity.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile
+package com.stemify.mobile
import android.Manifest
import android.content.ComponentName
@@ -19,9 +19,9 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.ListenableFuture
-import com.stemroller.mobile.core.ThemeMode
-import com.stemroller.mobile.playback.PlaybackService
-import com.stemroller.mobile.ui.StemApp
+import com.stemify.mobile.core.ThemeMode
+import com.stemify.mobile.playback.PlaybackService
+import com.stemify.mobile.ui.StemApp
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
diff --git a/android/app/src/main/java/com/stemroller/mobile/auto/CarArtProvider.kt b/android/app/src/main/java/com/stemify/mobile/auto/CarArtProvider.kt
similarity index 97%
rename from android/app/src/main/java/com/stemroller/mobile/auto/CarArtProvider.kt
rename to android/app/src/main/java/com/stemify/mobile/auto/CarArtProvider.kt
index 1de0d9c..a08551a 100644
--- a/android/app/src/main/java/com/stemroller/mobile/auto/CarArtProvider.kt
+++ b/android/app/src/main/java/com/stemify/mobile/auto/CarArtProvider.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.auto
+package com.stemify.mobile.auto
import android.content.ContentProvider
import android.content.ContentValues
@@ -10,7 +10,7 @@ import android.graphics.Typeface
import android.net.Uri
import android.os.ParcelFileDescriptor
import coil3.SingletonImageLoader
-import com.stemroller.mobile.App
+import com.stemify.mobile.App
import okhttp3.Request
import java.io.File
import java.io.FileNotFoundException
@@ -102,7 +102,7 @@ class CarArtProvider : ContentProvider() {
override fun update(uri: Uri, v: ContentValues?, s: String?, a: Array?) = 0
companion object {
- const val AUTHORITY = "com.stemroller.mobile.carart"
+ const val AUTHORITY = "com.stemify.mobile.carart"
private const val THUMB = "thumb"
private const val LETTER = "letter"
private const val LETTER_PX = 256
diff --git a/android/app/src/main/java/com/stemroller/mobile/auto/CarLibrary.kt b/android/app/src/main/java/com/stemify/mobile/auto/CarLibrary.kt
similarity index 97%
rename from android/app/src/main/java/com/stemroller/mobile/auto/CarLibrary.kt
rename to android/app/src/main/java/com/stemify/mobile/auto/CarLibrary.kt
index 6221a53..c285623 100644
--- a/android/app/src/main/java/com/stemroller/mobile/auto/CarLibrary.kt
+++ b/android/app/src/main/java/com/stemify/mobile/auto/CarLibrary.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.auto
+package com.stemify.mobile.auto
import android.net.Uri
import android.os.Bundle
@@ -7,11 +7,11 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaConstants
-import com.stemroller.mobile.Graph
-import com.stemroller.mobile.plex.Album
-import com.stemroller.mobile.plex.Artist
-import com.stemroller.mobile.plex.Playlist
-import com.stemroller.mobile.plex.Track
+import com.stemify.mobile.Graph
+import com.stemify.mobile.plex.Album
+import com.stemify.mobile.plex.Artist
+import com.stemify.mobile.plex.Playlist
+import com.stemify.mobile.plex.Track
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -166,7 +166,7 @@ class CarLibrary(private val g: Graph) {
}
/**
- * "Hey Google, play … on StemRoller". Prefers a playlist, then an artist (shuffled), then an
+ * "Hey Google, play … on Stemify". Prefers a playlist, then an artist (shuffled), then an
* album whose name matches; otherwise plays the song hits. Away from home it searches downloads.
*/
suspend fun resolveSearch(query: String): Resolved? = withContext(Dispatchers.IO) {
diff --git a/android/app/src/main/java/com/stemroller/mobile/auto/CarTree.kt b/android/app/src/main/java/com/stemify/mobile/auto/CarTree.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/auto/CarTree.kt
rename to android/app/src/main/java/com/stemify/mobile/auto/CarTree.kt
index 7ec30a0..bd63b5b 100644
--- a/android/app/src/main/java/com/stemroller/mobile/auto/CarTree.kt
+++ b/android/app/src/main/java/com/stemify/mobile/auto/CarTree.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.auto
+package com.stemify.mobile.auto
import java.text.Normalizer
diff --git a/android/app/src/main/java/com/stemroller/mobile/auto/HomeReach.kt b/android/app/src/main/java/com/stemify/mobile/auto/HomeReach.kt
similarity index 94%
rename from android/app/src/main/java/com/stemroller/mobile/auto/HomeReach.kt
rename to android/app/src/main/java/com/stemify/mobile/auto/HomeReach.kt
index 8febada..3b7a72d 100644
--- a/android/app/src/main/java/com/stemroller/mobile/auto/HomeReach.kt
+++ b/android/app/src/main/java/com/stemify/mobile/auto/HomeReach.kt
@@ -1,8 +1,8 @@
-package com.stemroller.mobile.auto
+package com.stemify.mobile.auto
-import com.stemroller.mobile.core.Connectivity
-import com.stemroller.mobile.core.Settings
-import com.stemroller.mobile.core.await
+import com.stemify.mobile.core.Connectivity
+import com.stemify.mobile.core.Settings
+import com.stemify.mobile.core.await
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
diff --git a/android/app/src/main/java/com/stemroller/mobile/cache/Downloads.kt b/android/app/src/main/java/com/stemify/mobile/cache/Downloads.kt
similarity index 95%
rename from android/app/src/main/java/com/stemroller/mobile/cache/Downloads.kt
rename to android/app/src/main/java/com/stemify/mobile/cache/Downloads.kt
index 6ce64f2..b76c0e3 100644
--- a/android/app/src/main/java/com/stemroller/mobile/cache/Downloads.kt
+++ b/android/app/src/main/java/com/stemify/mobile/cache/Downloads.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.cache
+package com.stemify.mobile.cache
import android.app.Notification
import android.app.NotificationChannel
@@ -11,16 +11,16 @@ import android.content.pm.ServiceInfo
import android.os.IBinder
import coil3.SingletonImageLoader
import coil3.request.ImageRequest
-import com.stemroller.mobile.App
-import com.stemroller.mobile.MainActivity
-import com.stemroller.mobile.R
-import com.stemroller.mobile.core.AppJson
-import com.stemroller.mobile.core.Connectivity
-import com.stemroller.mobile.core.Settings
-import com.stemroller.mobile.playback.PlaybackManager
-import com.stemroller.mobile.plex.PlexClient
-import com.stemroller.mobile.plex.Track
-import com.stemroller.mobile.stems.StemServer
+import com.stemify.mobile.App
+import com.stemify.mobile.MainActivity
+import com.stemify.mobile.R
+import com.stemify.mobile.core.AppJson
+import com.stemify.mobile.core.Connectivity
+import com.stemify.mobile.core.Settings
+import com.stemify.mobile.playback.PlaybackManager
+import com.stemify.mobile.plex.PlexClient
+import com.stemify.mobile.plex.Track
+import com.stemify.mobile.stems.StemServer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
diff --git a/android/app/src/main/java/com/stemroller/mobile/cache/MediaCache.kt b/android/app/src/main/java/com/stemify/mobile/cache/MediaCache.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/cache/MediaCache.kt
rename to android/app/src/main/java/com/stemify/mobile/cache/MediaCache.kt
index 98a94f1..00b76c7 100644
--- a/android/app/src/main/java/com/stemroller/mobile/cache/MediaCache.kt
+++ b/android/app/src/main/java/com/stemify/mobile/cache/MediaCache.kt
@@ -1,6 +1,6 @@
-package com.stemroller.mobile.cache
+package com.stemify.mobile.cache
-import com.stemroller.mobile.core.AppJson
+import com.stemify.mobile.core.AppJson
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
diff --git a/android/app/src/main/java/com/stemroller/mobile/core/Net.kt b/android/app/src/main/java/com/stemify/mobile/core/Net.kt
similarity index 98%
rename from android/app/src/main/java/com/stemroller/mobile/core/Net.kt
rename to android/app/src/main/java/com/stemify/mobile/core/Net.kt
index d92e006..86e0afe 100644
--- a/android/app/src/main/java/com/stemroller/mobile/core/Net.kt
+++ b/android/app/src/main/java/com/stemify/mobile/core/Net.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.core
+package com.stemify.mobile.core
import android.content.Context
import android.net.ConnectivityManager
diff --git a/android/app/src/main/java/com/stemroller/mobile/core/Settings.kt b/android/app/src/main/java/com/stemify/mobile/core/Settings.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/core/Settings.kt
rename to android/app/src/main/java/com/stemify/mobile/core/Settings.kt
index bf0db38..a5a4b7d 100644
--- a/android/app/src/main/java/com/stemroller/mobile/core/Settings.kt
+++ b/android/app/src/main/java/com/stemify/mobile/core/Settings.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.core
+package com.stemify.mobile.core
import android.content.Context
import android.content.SharedPreferences
diff --git a/android/app/src/main/java/com/stemroller/mobile/engine/Dsp.kt b/android/app/src/main/java/com/stemify/mobile/engine/Dsp.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/engine/Dsp.kt
rename to android/app/src/main/java/com/stemify/mobile/engine/Dsp.kt
index 6cb6ea4..acb2e87 100644
--- a/android/app/src/main/java/com/stemroller/mobile/engine/Dsp.kt
+++ b/android/app/src/main/java/com/stemify/mobile/engine/Dsp.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.engine
+package com.stemify.mobile.engine
import kotlin.math.PI
import kotlin.math.abs
diff --git a/android/app/src/main/java/com/stemroller/mobile/engine/StemDecoder.kt b/android/app/src/main/java/com/stemify/mobile/engine/StemDecoder.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/engine/StemDecoder.kt
rename to android/app/src/main/java/com/stemify/mobile/engine/StemDecoder.kt
index 04347e4..aaf4000 100644
--- a/android/app/src/main/java/com/stemroller/mobile/engine/StemDecoder.kt
+++ b/android/app/src/main/java/com/stemify/mobile/engine/StemDecoder.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.engine
+package com.stemify.mobile.engine
import android.media.AudioFormat
import android.media.MediaCodec
@@ -6,7 +6,7 @@ import android.media.MediaDataSource
import android.media.MediaExtractor
import android.media.MediaFormat
import android.util.Log
-import com.stemroller.mobile.cache.MediaCache
+import com.stemify.mobile.cache.MediaCache
import java.io.IOException
import java.nio.ByteOrder
import java.util.concurrent.CountDownLatch
diff --git a/android/app/src/main/java/com/stemroller/mobile/engine/StemEngine.kt b/android/app/src/main/java/com/stemify/mobile/engine/StemEngine.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/engine/StemEngine.kt
rename to android/app/src/main/java/com/stemify/mobile/engine/StemEngine.kt
index 1aa372c..29a1ed6 100644
--- a/android/app/src/main/java/com/stemroller/mobile/engine/StemEngine.kt
+++ b/android/app/src/main/java/com/stemify/mobile/engine/StemEngine.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.engine
+package com.stemify.mobile.engine
import android.media.AudioAttributes
import android.media.AudioDeviceInfo
diff --git a/android/app/src/main/java/com/stemroller/mobile/eq/ApoConfig.kt b/android/app/src/main/java/com/stemify/mobile/eq/ApoConfig.kt
similarity index 95%
rename from android/app/src/main/java/com/stemroller/mobile/eq/ApoConfig.kt
rename to android/app/src/main/java/com/stemify/mobile/eq/ApoConfig.kt
index ac64d22..01868c6 100644
--- a/android/app/src/main/java/com/stemroller/mobile/eq/ApoConfig.kt
+++ b/android/app/src/main/java/com/stemify/mobile/eq/ApoConfig.kt
@@ -1,9 +1,9 @@
-package com.stemroller.mobile.eq
+package com.stemify.mobile.eq
-import com.stemroller.mobile.engine.EQ_Q
-import com.stemroller.mobile.engine.EqChannel
-import com.stemroller.mobile.engine.EqFilter
-import com.stemroller.mobile.engine.FilterType
+import com.stemify.mobile.engine.EQ_Q
+import com.stemify.mobile.engine.EqChannel
+import com.stemify.mobile.engine.EqFilter
+import com.stemify.mobile.engine.FilterType
import java.util.Locale
import kotlin.math.pow
import kotlin.math.sqrt
@@ -107,7 +107,7 @@ object ApoConfig {
/** The profile as an Equalizer APO config: preamp, graphic bands as peaks, then the filters. */
fun format(p: EqProfile): String = buildString {
fun n(v: Float) = String.format(Locale.ROOT, "%.2f", v).trimEnd('0').trimEnd('.')
- appendLine("# ${p.name}, from StemRoller")
+ appendLine("# ${p.name}, from Stemify")
appendLine("Preamp: ${n(p.preamp)} dB")
var channel = EqChannel.Both
var k = 1
diff --git a/android/app/src/main/java/com/stemroller/mobile/eq/EqController.kt b/android/app/src/main/java/com/stemify/mobile/eq/EqController.kt
similarity index 97%
rename from android/app/src/main/java/com/stemroller/mobile/eq/EqController.kt
rename to android/app/src/main/java/com/stemify/mobile/eq/EqController.kt
index bed24fa..ad7612c 100644
--- a/android/app/src/main/java/com/stemroller/mobile/eq/EqController.kt
+++ b/android/app/src/main/java/com/stemify/mobile/eq/EqController.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.eq
+package com.stemify.mobile.eq
import android.content.Context
import android.media.AudioAttributes
@@ -7,12 +7,12 @@ import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.os.Handler
import android.os.Looper
-import com.stemroller.mobile.core.AppJson
-import com.stemroller.mobile.engine.EQ_BANDS
-import com.stemroller.mobile.engine.EQ_MAX_DB
-import com.stemroller.mobile.engine.EqFilter
-import com.stemroller.mobile.engine.EqMath
-import com.stemroller.mobile.engine.EqParams
+import com.stemify.mobile.core.AppJson
+import com.stemify.mobile.engine.EQ_BANDS
+import com.stemify.mobile.engine.EQ_MAX_DB
+import com.stemify.mobile.engine.EqFilter
+import com.stemify.mobile.engine.EqMath
+import com.stemify.mobile.engine.EqParams
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
diff --git a/android/app/src/main/java/com/stemroller/mobile/playback/MixPresets.kt b/android/app/src/main/java/com/stemify/mobile/playback/MixPresets.kt
similarity index 98%
rename from android/app/src/main/java/com/stemroller/mobile/playback/MixPresets.kt
rename to android/app/src/main/java/com/stemify/mobile/playback/MixPresets.kt
index 55ac7d5..d12242b 100644
--- a/android/app/src/main/java/com/stemroller/mobile/playback/MixPresets.kt
+++ b/android/app/src/main/java/com/stemify/mobile/playback/MixPresets.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.playback
+package com.stemify.mobile.playback
/** A one-tap stem mix. [build] returns null when the track lacks the stems it needs. */
class MixPreset(val label: String, val build: (List) -> Map?)
diff --git a/android/app/src/main/java/com/stemroller/mobile/playback/PlaybackManager.kt b/android/app/src/main/java/com/stemify/mobile/playback/PlaybackManager.kt
similarity index 96%
rename from android/app/src/main/java/com/stemroller/mobile/playback/PlaybackManager.kt
rename to android/app/src/main/java/com/stemify/mobile/playback/PlaybackManager.kt
index 0b6cab0..6e76e85 100644
--- a/android/app/src/main/java/com/stemroller/mobile/playback/PlaybackManager.kt
+++ b/android/app/src/main/java/com/stemify/mobile/playback/PlaybackManager.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.playback
+package com.stemify.mobile.playback
import android.content.BroadcastReceiver
import android.content.Context
@@ -12,22 +12,22 @@ import android.net.wifi.WifiManager
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
-import com.stemroller.mobile.cache.MediaCache
-import com.stemroller.mobile.core.AppJson
-import com.stemroller.mobile.core.Settings
-import com.stemroller.mobile.core.StemQuality
-import com.stemroller.mobile.engine.EqParams
-import com.stemroller.mobile.engine.ResourceDataSource
-import com.stemroller.mobile.engine.StemDecoder
-import com.stemroller.mobile.engine.StemEngine
-import com.stemroller.mobile.engine.TrackSession
-import com.stemroller.mobile.engine.masterTaper
-import com.stemroller.mobile.engine.stemTaper
-import com.stemroller.mobile.eq.EqController
-import com.stemroller.mobile.plex.PlexClient
-import com.stemroller.mobile.plex.Track
-import com.stemroller.mobile.stems.StemEntry
-import com.stemroller.mobile.stems.StemServer
+import com.stemify.mobile.cache.MediaCache
+import com.stemify.mobile.core.AppJson
+import com.stemify.mobile.core.Settings
+import com.stemify.mobile.core.StemQuality
+import com.stemify.mobile.engine.EqParams
+import com.stemify.mobile.engine.ResourceDataSource
+import com.stemify.mobile.engine.StemDecoder
+import com.stemify.mobile.engine.StemEngine
+import com.stemify.mobile.engine.TrackSession
+import com.stemify.mobile.engine.masterTaper
+import com.stemify.mobile.engine.stemTaper
+import com.stemify.mobile.eq.EqController
+import com.stemify.mobile.plex.PlexClient
+import com.stemify.mobile.plex.Track
+import com.stemify.mobile.stems.StemEntry
+import com.stemify.mobile.stems.StemServer
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -697,10 +697,10 @@ class PlaybackManager(
}
private val noisyRegistered = AtomicBoolean(false)
private val wakeLock = context.getSystemService(PowerManager::class.java)
- .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StemRoller:playback").apply { setReferenceCounted(false) }
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Stemify:playback").apply { setReferenceCounted(false) }
@Suppress("DEPRECATION")
private val wifiLock = context.applicationContext.getSystemService(WifiManager::class.java)
- .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "StemRoller:stream").apply { setReferenceCounted(false) }
+ .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Stemify:stream").apply { setReferenceCounted(false) }
private fun holdLocks(on: Boolean) {
if (on) {
diff --git a/android/app/src/main/java/com/stemroller/mobile/playback/PlaybackService.kt b/android/app/src/main/java/com/stemify/mobile/playback/PlaybackService.kt
similarity index 96%
rename from android/app/src/main/java/com/stemroller/mobile/playback/PlaybackService.kt
rename to android/app/src/main/java/com/stemify/mobile/playback/PlaybackService.kt
index 71b7e6a..610d500 100644
--- a/android/app/src/main/java/com/stemroller/mobile/playback/PlaybackService.kt
+++ b/android/app/src/main/java/com/stemify/mobile/playback/PlaybackService.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.playback
+package com.stemify.mobile.playback
import android.app.PendingIntent
import android.content.Intent
@@ -25,15 +25,15 @@ import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
-import com.stemroller.mobile.App
-import com.stemroller.mobile.MainActivity
-import com.stemroller.mobile.R
-import com.stemroller.mobile.auto.CarArtProvider
-import com.stemroller.mobile.auto.CarIds
-import com.stemroller.mobile.auto.CarLibrary
-import com.stemroller.mobile.auto.Resolved
-import com.stemroller.mobile.plex.NotSignedInException
-import com.stemroller.mobile.plex.Track
+import com.stemify.mobile.App
+import com.stemify.mobile.MainActivity
+import com.stemify.mobile.R
+import com.stemify.mobile.auto.CarArtProvider
+import com.stemify.mobile.auto.CarIds
+import com.stemify.mobile.auto.CarLibrary
+import com.stemify.mobile.auto.Resolved
+import com.stemify.mobile.plex.NotSignedInException
+import com.stemify.mobile.plex.Track
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
@@ -399,7 +399,7 @@ class PlaybackService : MediaLibraryService() {
// Auto says how many tabs it can show (4 on the Miata's screen).
params?.extras?.getInt(MediaConstants.EXTRAS_KEY_ROOT_CHILDREN_LIMIT, 0)?.takeIf { it > 0 }?.let { rootLimit = it }
val root = MediaItem.Builder().setMediaId(CarIds.ROOT).setMediaMetadata(
- MediaMetadata.Builder().setTitle("StemRoller").setIsBrowsable(true).setIsPlayable(false)
+ MediaMetadata.Builder().setTitle("Stemify").setIsBrowsable(true).setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED).build(),
).build()
val extras = Bundle().apply {
@@ -477,14 +477,14 @@ class PlaybackService : MediaLibraryService() {
override fun onPlaybackResumption(mediaSession: MediaSession, controller: MediaSession.ControllerInfo): ListenableFuture {
val q = pm.queue.value
if (q.isEmpty()) return Futures.immediateFailedFuture(UnsupportedOperationException("no saved queue"))
- val items = q.map { MediaItem.Builder().setMediaId("${it.uid}:${it.track.ratingKey}").setUri("stemroller://resume").setTag(ResumeTag).build() }
+ val items = q.map { MediaItem.Builder().setMediaId("${it.uid}:${it.track.ratingKey}").setUri("stemify://resume").setTag(ResumeTag).build() }
return Futures.immediateFuture(MediaSession.MediaItemsWithStartPosition(items, pm.index.value.coerceIn(0, q.lastIndex), pm.positionMs()))
}
}
private fun tagged(t: Track, shuffle: Boolean) = MediaItem.Builder()
.setMediaId("track:${t.ratingKey}")
- .setUri("stemroller://track/${t.ratingKey}")
+ .setUri("stemify://track/${t.ratingKey}")
.setTag(PlayTag(t, shuffle))
.setMediaMetadata(MediaMetadata.Builder().setTitle(t.title).setArtist(t.artist).setArtworkUri(CarArtProvider.uri(t.thumb)).build())
.build()
@@ -506,10 +506,10 @@ class PlaybackService : MediaLibraryService() {
companion object {
private const val TAG = "StemCar"
- private const val ACTION_VOCALS = "stemroller.VOCALS"
- private const val ACTION_MIX = "stemroller.MIX"
- private const val ACTION_SHUFFLE = "stemroller.SHUFFLE"
- private const val ACTION_REPEAT = "stemroller.REPEAT"
+ private const val ACTION_VOCALS = "stemify.VOCALS"
+ private const val ACTION_MIX = "stemify.MIX"
+ private const val ACTION_SHUFFLE = "stemify.SHUFFLE"
+ private const val ACTION_REPEAT = "stemify.REPEAT"
private val ACTIONS = listOf(ACTION_VOCALS, ACTION_MIX, ACTION_SHUFFLE, ACTION_REPEAT)
/** Tells Auto to show its (voice) search button. */
diff --git a/android/app/src/main/java/com/stemroller/mobile/plex/PlexClient.kt b/android/app/src/main/java/com/stemify/mobile/plex/PlexClient.kt
similarity index 97%
rename from android/app/src/main/java/com/stemroller/mobile/plex/PlexClient.kt
rename to android/app/src/main/java/com/stemify/mobile/plex/PlexClient.kt
index 43b263b..93bea53 100644
--- a/android/app/src/main/java/com/stemroller/mobile/plex/PlexClient.kt
+++ b/android/app/src/main/java/com/stemify/mobile/plex/PlexClient.kt
@@ -1,12 +1,12 @@
-package com.stemroller.mobile.plex
+package com.stemify.mobile.plex
import android.net.Uri
import android.os.Build
-import com.stemroller.mobile.core.AppJson
-import com.stemroller.mobile.core.HttpException
-import com.stemroller.mobile.core.Settings
-import com.stemroller.mobile.core.await
-import com.stemroller.mobile.core.awaitBody
+import com.stemify.mobile.core.AppJson
+import com.stemify.mobile.core.HttpException
+import com.stemify.mobile.core.Settings
+import com.stemify.mobile.core.await
+import com.stemify.mobile.core.awaitBody
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
@@ -247,7 +247,7 @@ class PlexClient(private val http: OkHttpClient, private val settings: Settings)
}.getOrDefault(false)
companion object {
- const val PRODUCT = "StemRoller"
+ const val PRODUCT = "Stemify"
private const val PAGE = 1000
}
}
diff --git a/android/app/src/main/java/com/stemroller/mobile/plex/PlexModels.kt b/android/app/src/main/java/com/stemify/mobile/plex/PlexModels.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/plex/PlexModels.kt
rename to android/app/src/main/java/com/stemify/mobile/plex/PlexModels.kt
index 9c85978..61c29c1 100644
--- a/android/app/src/main/java/com/stemroller/mobile/plex/PlexModels.kt
+++ b/android/app/src/main/java/com/stemify/mobile/plex/PlexModels.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.plex
+package com.stemify.mobile.plex
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/android/app/src/main/java/com/stemroller/mobile/stems/StemCoverage.kt b/android/app/src/main/java/com/stemify/mobile/stems/StemCoverage.kt
similarity index 95%
rename from android/app/src/main/java/com/stemroller/mobile/stems/StemCoverage.kt
rename to android/app/src/main/java/com/stemify/mobile/stems/StemCoverage.kt
index 7c30cf2..29bd8e5 100644
--- a/android/app/src/main/java/com/stemroller/mobile/stems/StemCoverage.kt
+++ b/android/app/src/main/java/com/stemify/mobile/stems/StemCoverage.kt
@@ -1,9 +1,9 @@
-package com.stemroller.mobile.stems
+package com.stemify.mobile.stems
-import com.stemroller.mobile.core.AppJson
-import com.stemroller.mobile.core.Settings
-import com.stemroller.mobile.plex.PlexClient
-import com.stemroller.mobile.plex.Track
+import com.stemify.mobile.core.AppJson
+import com.stemify.mobile.core.Settings
+import com.stemify.mobile.plex.PlexClient
+import com.stemify.mobile.plex.Track
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/android/app/src/main/java/com/stemroller/mobile/stems/StemIndex.kt b/android/app/src/main/java/com/stemify/mobile/stems/StemIndex.kt
similarity index 98%
rename from android/app/src/main/java/com/stemroller/mobile/stems/StemIndex.kt
rename to android/app/src/main/java/com/stemify/mobile/stems/StemIndex.kt
index 6c13aea..9bdb3ad 100644
--- a/android/app/src/main/java/com/stemroller/mobile/stems/StemIndex.kt
+++ b/android/app/src/main/java/com/stemify/mobile/stems/StemIndex.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.stems
+package com.stemify.mobile.stems
import kotlinx.serialization.Serializable
import java.text.Normalizer
diff --git a/android/app/src/main/java/com/stemroller/mobile/stems/StemServer.kt b/android/app/src/main/java/com/stemify/mobile/stems/StemServer.kt
similarity index 96%
rename from android/app/src/main/java/com/stemroller/mobile/stems/StemServer.kt
rename to android/app/src/main/java/com/stemify/mobile/stems/StemServer.kt
index 62c6569..b186da5 100644
--- a/android/app/src/main/java/com/stemroller/mobile/stems/StemServer.kt
+++ b/android/app/src/main/java/com/stemify/mobile/stems/StemServer.kt
@@ -1,13 +1,13 @@
-package com.stemroller.mobile.stems
+package com.stemify.mobile.stems
import android.net.Uri
-import com.stemroller.mobile.core.AppJson
-import com.stemroller.mobile.core.HttpException
-import com.stemroller.mobile.core.Settings
-import com.stemroller.mobile.core.StemQuality
-import com.stemroller.mobile.core.await
-import com.stemroller.mobile.core.awaitBody
-import com.stemroller.mobile.plex.Track
+import com.stemify.mobile.core.AppJson
+import com.stemify.mobile.core.HttpException
+import com.stemify.mobile.core.Settings
+import com.stemify.mobile.core.StemQuality
+import com.stemify.mobile.core.await
+import com.stemify.mobile.core.awaitBody
+import com.stemify.mobile.plex.Track
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/Root.kt b/android/app/src/main/java/com/stemify/mobile/ui/Root.kt
similarity index 85%
rename from android/app/src/main/java/com/stemroller/mobile/ui/Root.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/Root.kt
index f2889ef..2501d57 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/Root.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/Root.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui
+package com.stemify.mobile.ui
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.core.AnimationSpec
@@ -46,46 +46,46 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.stemroller.mobile.App
-import com.stemroller.mobile.ui.components.Hairline
-import com.stemroller.mobile.ui.components.LocalBottomInset
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.OverlayHost
-import com.stemroller.mobile.ui.components.Overlays
-import com.stemroller.mobile.ui.components.tappable
-import com.stemroller.mobile.ui.nav.NavStack
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.ui.nav.StackHost
-import com.stemroller.mobile.ui.screens.AlbumScreen
-import com.stemroller.mobile.ui.screens.EqFilterScreen
-import com.stemroller.mobile.ui.screens.EqFiltersScreen
-import com.stemroller.mobile.ui.screens.AlbumsScreen
-import com.stemroller.mobile.ui.screens.AppActions
-import com.stemroller.mobile.ui.screens.AppearanceScreen
-import com.stemroller.mobile.ui.screens.ArtistScreen
-import com.stemroller.mobile.ui.screens.ArtistsScreen
-import com.stemroller.mobile.ui.screens.DownloadsScreen
-import com.stemroller.mobile.ui.screens.EqScreen
-import com.stemroller.mobile.ui.screens.LibraryHome
-import com.stemroller.mobile.ui.screens.LocalApp
-import com.stemroller.mobile.ui.screens.MiniPlayer
-import com.stemroller.mobile.ui.screens.NowPlayingScreen
-import com.stemroller.mobile.ui.screens.PlaylistScreen
-import com.stemroller.mobile.ui.screens.PlaylistsScreen
-import com.stemroller.mobile.ui.screens.PlexAccountScreen
-import com.stemroller.mobile.ui.screens.QualityScreen
-import com.stemroller.mobile.ui.screens.SearchScreen
-import com.stemroller.mobile.ui.screens.SettingsScreen
-import com.stemroller.mobile.ui.screens.SongsScreen
-import com.stemroller.mobile.ui.screens.StemServerScreen
-import com.stemroller.mobile.ui.screens.StorageScreen
-import com.stemroller.mobile.ui.screens.Tab
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.IosTheme
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.ui.components.Hairline
+import com.stemify.mobile.ui.components.LocalBottomInset
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.OverlayHost
+import com.stemify.mobile.ui.components.Overlays
+import com.stemify.mobile.ui.components.tappable
+import com.stemify.mobile.ui.nav.NavStack
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.ui.nav.StackHost
+import com.stemify.mobile.ui.screens.AlbumScreen
+import com.stemify.mobile.ui.screens.EqFilterScreen
+import com.stemify.mobile.ui.screens.EqFiltersScreen
+import com.stemify.mobile.ui.screens.AlbumsScreen
+import com.stemify.mobile.ui.screens.AppActions
+import com.stemify.mobile.ui.screens.AppearanceScreen
+import com.stemify.mobile.ui.screens.ArtistScreen
+import com.stemify.mobile.ui.screens.ArtistsScreen
+import com.stemify.mobile.ui.screens.DownloadsScreen
+import com.stemify.mobile.ui.screens.EqScreen
+import com.stemify.mobile.ui.screens.LibraryHome
+import com.stemify.mobile.ui.screens.LocalApp
+import com.stemify.mobile.ui.screens.MiniPlayer
+import com.stemify.mobile.ui.screens.NowPlayingScreen
+import com.stemify.mobile.ui.screens.PlaylistScreen
+import com.stemify.mobile.ui.screens.PlaylistsScreen
+import com.stemify.mobile.ui.screens.PlexAccountScreen
+import com.stemify.mobile.ui.screens.QualityScreen
+import com.stemify.mobile.ui.screens.SearchScreen
+import com.stemify.mobile.ui.screens.SettingsScreen
+import com.stemify.mobile.ui.screens.SongsScreen
+import com.stemify.mobile.ui.screens.StemServerScreen
+import com.stemify.mobile.ui.screens.StorageScreen
+import com.stemify.mobile.ui.screens.Tab
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.IosTheme
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/components/Chrome.kt b/android/app/src/main/java/com/stemify/mobile/ui/components/Chrome.kt
similarity index 97%
rename from android/app/src/main/java/com/stemroller/mobile/ui/components/Chrome.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/components/Chrome.kt
index 42a0882..27ccda5 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/components/Chrome.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/components/Chrome.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.components
+package com.stemify.mobile.ui.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@@ -58,14 +58,14 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
-import com.stemroller.mobile.App
-import com.stemroller.mobile.ui.nav.LocalEntry
-import com.stemroller.mobile.ui.nav.LocalNav
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.ui.nav.LocalEntry
+import com.stemify.mobile.ui.nav.LocalNav
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlin.math.ceil
/** Height of the tab bar plus mini player, so lists can scroll clear of them. */
@@ -385,7 +385,7 @@ fun ErrorRow(message: String, onRetry: () -> Unit) {
if (!App.graph.plex.signedIn) {
val nav = LocalNav.current
EmptyState(Sym.server, "Not Connected", "Sign in to your Plex server to see your music here.", "Connect") {
- nav.push(com.stemroller.mobile.ui.nav.Route.PlexAccount)
+ nav.push(com.stemify.mobile.ui.nav.Route.PlexAccount)
}
} else {
EmptyState(Sym.warning, "Couldn't Load", message, "Try Again", onRetry)
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/components/Controls.kt b/android/app/src/main/java/com/stemify/mobile/ui/components/Controls.kt
similarity index 98%
rename from android/app/src/main/java/com/stemroller/mobile/ui/components/Controls.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/components/Controls.kt
index 939a9b9..77385d3 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/components/Controls.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/components/Controls.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.components
+package com.stemify.mobile.ui.components
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.LinearEasing
@@ -56,11 +56,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlin.math.abs
import kotlin.math.roundToInt
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/components/Overlays.kt b/android/app/src/main/java/com/stemify/mobile/ui/components/Overlays.kt
similarity index 97%
rename from android/app/src/main/java/com/stemroller/mobile/ui/components/Overlays.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/components/Overlays.kt
index 8cadcbc..b8bb1f8 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/components/Overlays.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/components/Overlays.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.components
+package com.stemify.mobile.ui.components
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.core.Animatable
@@ -50,11 +50,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/nav/Navigation.kt b/android/app/src/main/java/com/stemify/mobile/ui/nav/Navigation.kt
similarity index 98%
rename from android/app/src/main/java/com/stemroller/mobile/ui/nav/Navigation.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/nav/Navigation.kt
index 71c67bf..e8b14e9 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/nav/Navigation.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/nav/Navigation.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.nav
+package com.stemify.mobile.ui.nav
import androidx.activity.BackEventCompat
import androidx.activity.compose.PredictiveBackHandler
@@ -29,9 +29,9 @@ import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
-import com.stemroller.mobile.plex.Album
-import com.stemroller.mobile.plex.Artist
-import com.stemroller.mobile.plex.Playlist
+import com.stemify.mobile.plex.Album
+import com.stemify.mobile.plex.Artist
+import com.stemify.mobile.plex.Playlist
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/Common.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/Common.kt
similarity index 93%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/Common.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/Common.kt
index 881d954..96718ef 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/Common.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/Common.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -27,31 +27,31 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import com.stemroller.mobile.App
-import com.stemroller.mobile.stems.Coverage
-import com.stemroller.mobile.plex.Album
-import com.stemroller.mobile.plex.Artist
-import com.stemroller.mobile.plex.Playlist
-import com.stemroller.mobile.plex.Track
-import com.stemroller.mobile.ui.components.Artwork
-import com.stemroller.mobile.ui.components.Chevron
-import com.stemroller.mobile.ui.components.DownloadedMark
-import com.stemroller.mobile.ui.components.Hairline
-import com.stemroller.mobile.ui.components.IconButton
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.MARGIN
-import com.stemroller.mobile.ui.components.Overlays
-import com.stemroller.mobile.ui.components.PillButton
-import com.stemroller.mobile.ui.components.PlayingBars
-import com.stemroller.mobile.ui.components.ProgressRing
-import com.stemroller.mobile.ui.components.SheetAction
-import com.stemroller.mobile.ui.components.Tag
-import com.stemroller.mobile.ui.components.tappable
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.stems.Coverage
+import com.stemify.mobile.plex.Album
+import com.stemify.mobile.plex.Artist
+import com.stemify.mobile.plex.Playlist
+import com.stemify.mobile.plex.Track
+import com.stemify.mobile.ui.components.Artwork
+import com.stemify.mobile.ui.components.Chevron
+import com.stemify.mobile.ui.components.DownloadedMark
+import com.stemify.mobile.ui.components.Hairline
+import com.stemify.mobile.ui.components.IconButton
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.MARGIN
+import com.stemify.mobile.ui.components.Overlays
+import com.stemify.mobile.ui.components.PillButton
+import com.stemify.mobile.ui.components.PlayingBars
+import com.stemify.mobile.ui.components.ProgressRing
+import com.stemify.mobile.ui.components.SheetAction
+import com.stemify.mobile.ui.components.Tag
+import com.stemify.mobile.ui.components.tappable
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/EqFilterScreens.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/EqFilterScreens.kt
similarity index 94%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/EqFilterScreens.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/EqFilterScreens.kt
index e109d5f..83db8b9 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/EqFilterScreens.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/EqFilterScreens.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import android.content.ClipData
import android.content.ClipboardManager
@@ -35,30 +35,30 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.drawText
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import com.stemroller.mobile.App
-import com.stemroller.mobile.engine.EQ_BANDS
-import com.stemroller.mobile.engine.EQ_SLOPES
-import com.stemroller.mobile.engine.EqChannel
-import com.stemroller.mobile.engine.EqFilter
-import com.stemroller.mobile.engine.EqMath
-import com.stemroller.mobile.engine.FilterType
-import com.stemroller.mobile.eq.ApoConfig
-import com.stemroller.mobile.eq.EqController
-import com.stemroller.mobile.ui.components.Cell
-import com.stemroller.mobile.ui.components.IconButton
-import com.stemroller.mobile.ui.components.IosSlider
-import com.stemroller.mobile.ui.components.IosSwitch
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.Page
-import com.stemroller.mobile.ui.components.SegmentedControl
-import com.stemroller.mobile.ui.components.SheetAction
-import com.stemroller.mobile.ui.components.section
-import com.stemroller.mobile.ui.nav.LocalNav
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.engine.EQ_BANDS
+import com.stemify.mobile.engine.EQ_SLOPES
+import com.stemify.mobile.engine.EqChannel
+import com.stemify.mobile.engine.EqFilter
+import com.stemify.mobile.engine.EqMath
+import com.stemify.mobile.engine.FilterType
+import com.stemify.mobile.eq.ApoConfig
+import com.stemify.mobile.eq.EqController
+import com.stemify.mobile.ui.components.Cell
+import com.stemify.mobile.ui.components.IconButton
+import com.stemify.mobile.ui.components.IosSlider
+import com.stemify.mobile.ui.components.IosSwitch
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.Page
+import com.stemify.mobile.ui.components.SegmentedControl
+import com.stemify.mobile.ui.components.SheetAction
+import com.stemify.mobile.ui.components.section
+import com.stemify.mobile.ui.nav.LocalNav
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import java.util.Locale
import kotlin.math.abs
import kotlin.math.ln
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/EqScreen.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/EqScreen.kt
similarity index 86%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/EqScreen.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/EqScreen.kt
index c6cf804..e19ab0e 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/EqScreen.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/EqScreen.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
@@ -24,30 +24,30 @@ import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.stemroller.mobile.App
-import com.stemroller.mobile.engine.EQ_BANDS
-import com.stemroller.mobile.engine.EQ_LABELS
-import com.stemroller.mobile.engine.EQ_MAX_DB
-import com.stemroller.mobile.engine.EqMath
-import com.stemroller.mobile.eq.DeviceKind
-import com.stemroller.mobile.eq.EQ_PRESETS
-import com.stemroller.mobile.eq.EqProfile
-import com.stemroller.mobile.eq.OutputDevice
-import com.stemroller.mobile.ui.components.Cell
-import com.stemroller.mobile.ui.components.IosSwitch
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.Page
-import com.stemroller.mobile.ui.components.SegmentedControl
-import com.stemroller.mobile.ui.components.SettingsIcon
-import com.stemroller.mobile.ui.components.SheetAction
-import com.stemroller.mobile.ui.components.VerticalBandSlider
-import com.stemroller.mobile.ui.components.section
-import com.stemroller.mobile.ui.nav.LocalNav
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.engine.EQ_BANDS
+import com.stemify.mobile.engine.EQ_LABELS
+import com.stemify.mobile.engine.EQ_MAX_DB
+import com.stemify.mobile.engine.EqMath
+import com.stemify.mobile.eq.DeviceKind
+import com.stemify.mobile.eq.EQ_PRESETS
+import com.stemify.mobile.eq.EqProfile
+import com.stemify.mobile.eq.OutputDevice
+import com.stemify.mobile.ui.components.Cell
+import com.stemify.mobile.ui.components.IosSwitch
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.Page
+import com.stemify.mobile.ui.components.SegmentedControl
+import com.stemify.mobile.ui.components.SettingsIcon
+import com.stemify.mobile.ui.components.SheetAction
+import com.stemify.mobile.ui.components.VerticalBandSlider
+import com.stemify.mobile.ui.components.section
+import com.stemify.mobile.ui.nav.LocalNav
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlin.math.abs
fun deviceIcon(d: OutputDevice): Pair = when (d.kind) {
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/LibraryScreens.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/LibraryScreens.kt
similarity index 94%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/LibraryScreens.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/LibraryScreens.kt
index 91fbdb9..a754012 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/LibraryScreens.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/LibraryScreens.kt
@@ -1,7 +1,7 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import androidx.compose.foundation.background
-import com.stemroller.mobile.ui.components.ProgressRing
+import com.stemify.mobile.ui.components.ProgressRing
import androidx.compose.ui.draw.clip
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.layout.size
@@ -44,35 +44,35 @@ import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.stemroller.mobile.App
-import com.stemroller.mobile.plex.Album
-import com.stemroller.mobile.plex.Artist
-import com.stemroller.mobile.plex.Track
-import com.stemroller.mobile.cache.DownloadRecord
-import com.stemroller.mobile.ui.components.Artwork
-import com.stemroller.mobile.ui.components.Chevron
-import com.stemroller.mobile.ui.components.EmptyState
-import com.stemroller.mobile.ui.components.ErrorRow
-import com.stemroller.mobile.ui.components.Hairline
-import com.stemroller.mobile.ui.components.IconButton
-import com.stemroller.mobile.ui.components.LoadingRow
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.MARGIN
-import com.stemroller.mobile.ui.components.Page
-import com.stemroller.mobile.ui.components.SectionTitle
-import com.stemroller.mobile.ui.components.SheetAction
-import com.stemroller.mobile.ui.components.TextButton
-import com.stemroller.mobile.ui.components.tappable
-import com.stemroller.mobile.ui.nav.Load
-import com.stemroller.mobile.ui.nav.LocalNav
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.ui.nav.rememberInEntry
-import com.stemroller.mobile.ui.nav.rememberLoader
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.plex.Album
+import com.stemify.mobile.plex.Artist
+import com.stemify.mobile.plex.Track
+import com.stemify.mobile.cache.DownloadRecord
+import com.stemify.mobile.ui.components.Artwork
+import com.stemify.mobile.ui.components.Chevron
+import com.stemify.mobile.ui.components.EmptyState
+import com.stemify.mobile.ui.components.ErrorRow
+import com.stemify.mobile.ui.components.Hairline
+import com.stemify.mobile.ui.components.IconButton
+import com.stemify.mobile.ui.components.LoadingRow
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.MARGIN
+import com.stemify.mobile.ui.components.Page
+import com.stemify.mobile.ui.components.SectionTitle
+import com.stemify.mobile.ui.components.SheetAction
+import com.stemify.mobile.ui.components.TextButton
+import com.stemify.mobile.ui.components.tappable
+import com.stemify.mobile.ui.nav.Load
+import com.stemify.mobile.ui.nav.LocalNav
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.ui.nav.rememberInEntry
+import com.stemify.mobile.ui.nav.rememberLoader
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlinx.coroutines.flow.distinctUntilChanged
import java.time.LocalDate
import java.time.format.DateTimeFormatter
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/NowPlaying.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/NowPlaying.kt
similarity index 94%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/NowPlaying.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/NowPlaying.kt
index 58e04e6..9140dc8 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/NowPlaying.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/NowPlaying.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import android.graphics.Bitmap
import androidx.compose.animation.AnimatedContent
@@ -66,33 +66,33 @@ import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.allowHardware
import coil3.toBitmap
-import com.stemroller.mobile.App
-import com.stemroller.mobile.engine.MeterHistory
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.core.StemQuality
-import com.stemroller.mobile.playback.MixPresets
-import com.stemroller.mobile.playback.QueueItem
-import com.stemroller.mobile.playback.RepeatMode
-import com.stemroller.mobile.playback.SourceKind
-import com.stemroller.mobile.playback.StemSetting
-import com.stemroller.mobile.playback.Stems
-import com.stemroller.mobile.ui.components.Artwork
-import com.stemroller.mobile.ui.components.Chip
-import com.stemroller.mobile.ui.components.Hairline
-import com.stemroller.mobile.ui.components.IconButton
-import com.stemroller.mobile.ui.components.IosSlider
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.SheetAction
-import com.stemroller.mobile.ui.components.Spinner
-import com.stemroller.mobile.ui.components.TextButton
-import com.stemroller.mobile.ui.components.pressFade
-import com.stemroller.mobile.ui.components.tappable
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.LocalPalette
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.engine.MeterHistory
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.core.StemQuality
+import com.stemify.mobile.playback.MixPresets
+import com.stemify.mobile.playback.QueueItem
+import com.stemify.mobile.playback.RepeatMode
+import com.stemify.mobile.playback.SourceKind
+import com.stemify.mobile.playback.StemSetting
+import com.stemify.mobile.playback.Stems
+import com.stemify.mobile.ui.components.Artwork
+import com.stemify.mobile.ui.components.Chip
+import com.stemify.mobile.ui.components.Hairline
+import com.stemify.mobile.ui.components.IconButton
+import com.stemify.mobile.ui.components.IosSlider
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.SheetAction
+import com.stemify.mobile.ui.components.Spinner
+import com.stemify.mobile.ui.components.TextButton
+import com.stemify.mobile.ui.components.pressFade
+import com.stemify.mobile.ui.components.tappable
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.LocalPalette
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.math.exp
@@ -200,7 +200,7 @@ fun NowPlayingScreen(onClose: () -> Unit, dragModifier: Modifier) {
Column(Modifier.weight(1f)) {
Txt(track.title, Type.title3)
Txt(track.artist, Type.title3, color = c.label2, weight = FontWeight.Normal, modifier = Modifier.tappable {
- track.artistKey?.let { k -> app.showInLibrary(com.stemroller.mobile.ui.nav.Route.ArtistPage(com.stemroller.mobile.plex.Artist(k, track.artist, track.artist))) }
+ track.artistKey?.let { k -> app.showInLibrary(com.stemify.mobile.ui.nav.Route.ArtistPage(com.stemify.mobile.plex.Artist(k, track.artist, track.artist))) }
})
}
Box(Modifier.size(32.dp).clip(CircleShape).background(c.fill).pressFade { menu.show(track) }, contentAlignment = Alignment.Center) {
@@ -356,7 +356,7 @@ private fun StemFader(name: String, s: StemSetting, level: Float, enabled: Boole
Box(Modifier.size(10.dp).clip(CircleShape).background(if (s.muted) c.label3 else color))
Spacer(Modifier.width(8.dp))
Txt(Stems.label(name), Type.headline, Modifier.weight(1f), color = if (s.muted) c.label2 else c.label)
- val pct = (com.stemroller.mobile.engine.stemTaper(s.level) * 100).toInt()
+ val pct = (com.stemify.mobile.engine.stemTaper(s.level) * 100).toInt()
Txt("$pct%", Type.footnote.copy(fontFeatureSettings = "tnum"), color = c.label2, modifier = Modifier.padding(end = 10.dp))
if (enabled) {
ToggleBox("M", s.muted, c.red, onMute)
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/PlaylistScreens.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/PlaylistScreens.kt
similarity index 90%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/PlaylistScreens.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/PlaylistScreens.kt
index d2623d8..603e5fc 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/PlaylistScreens.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/PlaylistScreens.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
@@ -35,37 +35,37 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
-import com.stemroller.mobile.App
-import com.stemroller.mobile.plex.Playlist
-import com.stemroller.mobile.plex.SearchResults
-import com.stemroller.mobile.plex.Track
-import com.stemroller.mobile.ui.components.Artwork
-import com.stemroller.mobile.ui.components.Chevron
-import com.stemroller.mobile.ui.components.EmptyState
-import com.stemroller.mobile.ui.components.ErrorRow
-import com.stemroller.mobile.ui.components.Hairline
-import com.stemroller.mobile.ui.components.IconButton
-import com.stemroller.mobile.ui.components.LoadingRow
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.MARGIN
-import com.stemroller.mobile.ui.components.Page
-import com.stemroller.mobile.ui.components.SearchField
-import com.stemroller.mobile.ui.components.SectionTitle
-import com.stemroller.mobile.ui.components.SheetAction
-import com.stemroller.mobile.ui.components.Spinner
-import com.stemroller.mobile.ui.components.Tag
-import com.stemroller.mobile.ui.components.TextButton
-import com.stemroller.mobile.ui.components.tappable
-import com.stemroller.mobile.ui.nav.Load
-import com.stemroller.mobile.ui.nav.LocalNav
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.ui.nav.rememberInEntry
-import com.stemroller.mobile.ui.nav.rememberLoader
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.plex.Playlist
+import com.stemify.mobile.plex.SearchResults
+import com.stemify.mobile.plex.Track
+import com.stemify.mobile.ui.components.Artwork
+import com.stemify.mobile.ui.components.Chevron
+import com.stemify.mobile.ui.components.EmptyState
+import com.stemify.mobile.ui.components.ErrorRow
+import com.stemify.mobile.ui.components.Hairline
+import com.stemify.mobile.ui.components.IconButton
+import com.stemify.mobile.ui.components.LoadingRow
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.MARGIN
+import com.stemify.mobile.ui.components.Page
+import com.stemify.mobile.ui.components.SearchField
+import com.stemify.mobile.ui.components.SectionTitle
+import com.stemify.mobile.ui.components.SheetAction
+import com.stemify.mobile.ui.components.Spinner
+import com.stemify.mobile.ui.components.Tag
+import com.stemify.mobile.ui.components.TextButton
+import com.stemify.mobile.ui.components.tappable
+import com.stemify.mobile.ui.nav.Load
+import com.stemify.mobile.ui.nav.LocalNav
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.ui.nav.rememberInEntry
+import com.stemify.mobile.ui.nav.rememberLoader
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -174,7 +174,7 @@ fun PlaylistScreen(playlist: Playlist) {
sim.add(i, t)
}
}
- }.onFailure { overlays.hud("Couldn't save: ${it.message}", com.stemroller.mobile.ui.theme.Sym.warning) }
+ }.onFailure { overlays.hud("Couldn't save: ${it.message}", com.stemify.mobile.ui.theme.Sym.warning) }
tracks.refresh()
edit.saving = false
edit.editing = false
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/ProgressVolumeBar.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/ProgressVolumeBar.kt
similarity index 95%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/ProgressVolumeBar.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/ProgressVolumeBar.kt
index 2e03bd0..afd7aaa 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/ProgressVolumeBar.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/ProgressVolumeBar.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@@ -39,16 +39,16 @@ import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
-import com.stemroller.mobile.App
-import com.stemroller.mobile.engine.masterTaper
-import com.stemroller.mobile.engine.stemTaper
-import com.stemroller.mobile.playback.StemSetting
-import com.stemroller.mobile.playback.Waveform
-import com.stemroller.mobile.ui.theme.Icon
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
+import com.stemify.mobile.App
+import com.stemify.mobile.engine.masterTaper
+import com.stemify.mobile.engine.stemTaper
+import com.stemify.mobile.playback.StemSetting
+import com.stemify.mobile.playback.Waveform
+import com.stemify.mobile.ui.theme.Icon
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
import kotlinx.coroutines.delay
import kotlin.math.abs
import kotlin.math.log10
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/screens/SettingsScreens.kt b/android/app/src/main/java/com/stemify/mobile/ui/screens/SettingsScreens.kt
similarity index 94%
rename from android/app/src/main/java/com/stemroller/mobile/ui/screens/SettingsScreens.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/screens/SettingsScreens.kt
index 692abf6..8302513 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/screens/SettingsScreens.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/screens/SettingsScreens.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.screens
+package com.stemify.mobile.ui.screens
import android.content.Intent
import android.net.Uri
@@ -40,29 +40,29 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.stemroller.mobile.App
-import com.stemroller.mobile.core.Accent
-import com.stemroller.mobile.core.StemQuality
-import com.stemroller.mobile.core.ThemeMode
-import com.stemroller.mobile.plex.MusicSection
-import com.stemroller.mobile.stems.StemServerStatus
-import com.stemroller.mobile.ui.components.Cell
-import com.stemroller.mobile.ui.components.IosSwitch
-import com.stemroller.mobile.ui.components.LocalOverlays
-import com.stemroller.mobile.ui.components.Page
-import com.stemroller.mobile.ui.components.SegmentedControl
-import com.stemroller.mobile.ui.components.SettingsIcon
-import com.stemroller.mobile.ui.components.Spinner
-import com.stemroller.mobile.ui.components.section
-import com.stemroller.mobile.ui.components.tappable
-import com.stemroller.mobile.ui.nav.LocalNav
-import com.stemroller.mobile.ui.nav.Route
-import com.stemroller.mobile.ui.nav.rememberInEntry
-import com.stemroller.mobile.ui.theme.Ios
-import com.stemroller.mobile.ui.theme.Sym
-import com.stemroller.mobile.ui.theme.Txt
-import com.stemroller.mobile.ui.theme.Type
-import com.stemroller.mobile.ui.theme.palette
+import com.stemify.mobile.App
+import com.stemify.mobile.core.Accent
+import com.stemify.mobile.core.StemQuality
+import com.stemify.mobile.core.ThemeMode
+import com.stemify.mobile.plex.MusicSection
+import com.stemify.mobile.stems.StemServerStatus
+import com.stemify.mobile.ui.components.Cell
+import com.stemify.mobile.ui.components.IosSwitch
+import com.stemify.mobile.ui.components.LocalOverlays
+import com.stemify.mobile.ui.components.Page
+import com.stemify.mobile.ui.components.SegmentedControl
+import com.stemify.mobile.ui.components.SettingsIcon
+import com.stemify.mobile.ui.components.Spinner
+import com.stemify.mobile.ui.components.section
+import com.stemify.mobile.ui.components.tappable
+import com.stemify.mobile.ui.nav.LocalNav
+import com.stemify.mobile.ui.nav.Route
+import com.stemify.mobile.ui.nav.rememberInEntry
+import com.stemify.mobile.ui.theme.Ios
+import com.stemify.mobile.ui.theme.Sym
+import com.stemify.mobile.ui.theme.Txt
+import com.stemify.mobile.ui.theme.Type
+import com.stemify.mobile.ui.theme.palette
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -108,7 +108,7 @@ fun SettingsScreen() {
{ Cell("Download on Wi-Fi Only", icon = { SettingsIcon(Sym.download, Color(0xFF30B0C7)) }, trailing = { IosSwitch(wifi, { s.downloadsWifiOnly.value = it }) }) },
))
section("display", header = "Display", separatorInset = 60.dp,
- footer = "StemRoller 1.1 · stems by stemd on your server" + (mode?.let { " · ${"%.0f".format(it.refreshRate)} Hz" } ?: ""),
+ footer = "Stemify 1.1 · stems by stemd on your server" + (mode?.let { " · ${"%.0f".format(it.refreshRate)} Hz" } ?: ""),
rows = listOf(
{ Cell("Appearance", icon = { SettingsIcon(Sym.paintbrush, Color(0xFF007AFF)) }, value = theme.label, chevron = true) { nav.push(Route.Appearance) } },
{ Cell("High Refresh Rate", icon = { SettingsIcon(Sym.phone, Color(0xFFFF9500)) }, trailing = { IosSwitch(hz, { s.highRefreshRate.value = it }) }) },
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/theme/Icons.kt b/android/app/src/main/java/com/stemify/mobile/ui/theme/Icons.kt
similarity index 99%
rename from android/app/src/main/java/com/stemroller/mobile/ui/theme/Icons.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/theme/Icons.kt
index d3f03e4..9dcd6fe 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/theme/Icons.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/theme/Icons.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.theme
+package com.stemify.mobile.ui.theme
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
diff --git a/android/app/src/main/java/com/stemroller/mobile/ui/theme/Theme.kt b/android/app/src/main/java/com/stemify/mobile/ui/theme/Theme.kt
similarity index 97%
rename from android/app/src/main/java/com/stemroller/mobile/ui/theme/Theme.kt
rename to android/app/src/main/java/com/stemify/mobile/ui/theme/Theme.kt
index 865c24c..57bfbb1 100644
--- a/android/app/src/main/java/com/stemroller/mobile/ui/theme/Theme.kt
+++ b/android/app/src/main/java/com/stemify/mobile/ui/theme/Theme.kt
@@ -1,4 +1,4 @@
-package com.stemroller.mobile.ui.theme
+package com.stemify.mobile.ui.theme
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
@@ -19,9 +19,9 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
-import com.stemroller.mobile.R
-import com.stemroller.mobile.core.Accent
-import com.stemroller.mobile.core.ThemeMode
+import com.stemify.mobile.R
+import com.stemify.mobile.core.Accent
+import com.stemify.mobile.core.ThemeMode
import kotlin.math.exp
/** iOS semantic colours, flattened to solid values for each scheme. */
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 78b8845..1fbd790 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,4 +1,4 @@
- StemRoller
+ Stemify
diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml
index f986874..875a4b9 100644
--- a/android/app/src/main/res/values/themes.xml
+++ b/android/app/src/main/res/values/themes.xml
@@ -1,6 +1,6 @@
-