diff --git a/.dockerignore b/.dockerignore
index 2653c48b..938a46c7 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -9,7 +9,6 @@ web/build
# Flutter mobile client — built separately on developer machines / Flutter CI.
# Including it in the Go build context wastes ~70 files and invalidates the
# `COPY . .` layer cache on every Flutter-only change.
-flutter_client/
# Docs and IDE noise
docs/
diff --git a/.gitignore b/.gitignore
index b2fcc709..f1619185 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,20 +52,6 @@ GEMINI.md
.windsurfrules
.aider.conf.yml
-# Flutter
-flutter_client/.dart_tool/
-flutter_client/.flutter-plugins
-flutter_client/.flutter-plugins-dependencies
-flutter_client/build/
-flutter_client/.idea/
-flutter_client/ios/Podfile.lock
-flutter_client/ios/Pods/
-flutter_client/android/.gradle/
-flutter_client/android/app/build/
-flutter_client/android/local.properties
-flutter_client/android/key.properties
-flutter_client/*.iml
-
# Native Android (Kotlin/Compose) — M8 rewrite
android/.gradle/
android/.kotlin/
diff --git a/android/app/src/main/java/com/fabledsword/minstrel/theme/FabledSwordTokens.kt b/android/app/src/main/java/com/fabledsword/minstrel/theme/FabledSwordTokens.kt
index 7309a465..5a52e73c 100644
--- a/android/app/src/main/java/com/fabledsword/minstrel/theme/FabledSwordTokens.kt
+++ b/android/app/src/main/java/com/fabledsword/minstrel/theme/FabledSwordTokens.kt
@@ -5,7 +5,8 @@ import androidx.compose.ui.unit.dp
/**
* Raw color values for the dark surface cohort. Source of truth:
- * `flutter_client/shared/fabledsword.tokens.json` (dark block).
+ * `shared/fabledsword.tokens.json` (dark block) — moved to the repo root
+ * when the Flutter client was deleted; the tokens were never its property.
*
* Token names are **semantic roles**, not literal colors. In dark
* mode, `parchment` is light text on dark surfaces (#E8E4D8); in
diff --git a/ci-requirements.md b/ci-requirements.md
index cbf3592d..52bf57ca 100644
--- a/ci-requirements.md
+++ b/ci-requirements.md
@@ -15,11 +15,16 @@ git.fabledsword.com/bvandeusen/ci-android:36
- `ci-go:1.26` — Go server tests (`.gitea/workflows/test-go.yml`), web SPA tests (`.gitea/workflows/test-web.yml`), and the release container build (`release.yml`'s `image-release` job).
- `ci-android:36` — native Kotlin/Compose client: ktlint + detekt + unit tests + debug APK (`.gitea/workflows/android.yml`), and the signed release APK (`release.yml`'s `android-release` job).
-**`ci-flutter` is no longer consumed.** The M8 rewrite replaced the Flutter
-client with the native Android app and `flutter.yml` was removed; `ci-android`
-took its place. `flutter_client/` is still in the tree but nothing builds it.
-CI-Runner still publishes `ci-flutter` and will retire it once that directory
-goes — so if the Flutter client is ever revived, say so there first.
+**`ci-flutter` is no longer consumed, and `ci-flutter` can now be retired.**
+The M8 rewrite replaced the Flutter client with the native Android app and
+`flutter.yml` was removed; `ci-android` took its place. `flutter_client/`
+itself was deleted on 2026-08-16, which was the condition CI-Runner was
+waiting on before dropping the image — nothing in this repo needs a Flutter
+toolchain any more.
+
+Note this does **not** mean the `flutter-ci` runner *label* goes: the Android
+jobs still schedule on it while pulling `ci-android:36`, per the label/image
+split below. The label is a scheduling handle, not a toolchain assertion.
## Image deps used
diff --git a/flutter_client/.gitignore b/flutter_client/.gitignore
deleted file mode 100644
index 7545eaf9..00000000
--- a/flutter_client/.gitignore
+++ /dev/null
@@ -1,49 +0,0 @@
-# Miscellaneous
-*.class
-*.log
-*.pyc
-*.swp
-.DS_Store
-.atom/
-.build/
-.buildlog/
-.history
-.svn/
-.swiftpm/
-migrate_working_dir/
-
-# IntelliJ related
-*.iml
-*.ipr
-*.iws
-.idea/
-
-# The .vscode folder contains launch configuration and tasks you configure in
-# VS Code which you may wish to be included in version control, so this line
-# is commented out by default.
-#.vscode/
-
-# Flutter/Dart/Pub related
-**/doc/api/
-**/ios/Flutter/.last_build_id
-.dart_tool/
-.flutter-plugins-dependencies
-.pub-cache/
-.pub/
-/build/
-/coverage/
-
-# Symbolication related
-app.*.symbols
-
-# Obfuscation related
-app.*.map.json
-
-# Android Studio will place build artifacts here
-/android/app/debug
-/android/app/profile
-/android/app/release
-
-# drift codegen output (regenerated by build_runner; CI runs build_runner)
-*.g.dart
-
diff --git a/flutter_client/.metadata b/flutter_client/.metadata
deleted file mode 100644
index 9e9a413a..00000000
--- a/flutter_client/.metadata
+++ /dev/null
@@ -1,33 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: "02085feb3f5d8a8156e5e28512b9d99351d510c0"
- channel: "stable"
-
-project_type: app
-
-# Tracks metadata for the flutter migrate command
-migration:
- platforms:
- - platform: root
- create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
- base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
- - platform: android
- create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
- base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
- - platform: ios
- create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
- base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
-
- # User provided section
-
- # List of Local paths (relative to this file) that should be
- # ignored by the migrate tool.
- #
- # Files that are not part of the templates will be ignored by default.
- unmanaged_files:
- - 'lib/main.dart'
- - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/flutter_client/README.md b/flutter_client/README.md
deleted file mode 100644
index 19c5e0d6..00000000
--- a/flutter_client/README.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# Minstrel mobile client
-
-Flutter (iOS + Android) sibling of the SvelteKit web SPA. v1 first
-slice ships: scaffold, auth, library browse (home / artist / album),
-likes, player with background audio + lock-screen controls.
-
-## Setup
-
-```bash
-cd flutter_client
-flutter pub get
-./tool/sync_shared.sh # copy tokens / error-copy / placeholders from ../web
-dart run tool/gen_tokens.dart # regen lib/theme/tokens.dart from shared/fabledsword.tokens.json
-flutter run
-```
-
-`sync_shared.sh` is idempotent. CI runs it on every build; rerun locally
-whenever `web/src/lib/styles/tokens.json`,
-`web/src/lib/styles/error-copy.json`, or
-`web/static/placeholders/album-fallback.svg` changes.
-
-## Project layout
-
-See `docs/superpowers/specs/2026-05-02-flutter-mobile-foundation-design.md`
-section 3. Briefly:
-
-- `lib/api/` — dio client, ApiError, error-copy loader, per-surface endpoints.
-- `lib/auth/` — server URL screen, login screen, AuthController + secure storage.
-- `lib/library/` — home, artist detail, album detail, providers, widgets.
-- `lib/likes/` — LikeButton + optimistic toggle controller.
-- `lib/player/` — audio handler, player provider, mini PlayerBar, NowPlaying.
-- `lib/theme/` — FabledSword token Dart class (generated), ThemeExtension, ThemeData.
-- `lib/shared/` — routing (go_router shell), version gate, connection error banner.
-
-`shared/fabledsword.tokens.json` and `assets/error-copy.json` are
-synced from web/. Don't edit them directly — edit `web/src/lib/styles/`
-and re-run `./tool/sync_shared.sh`.
-
-## Architecture
-
-- **State:** Riverpod 2 (`AsyncValue`, `AsyncNotifier`, family providers).
-- **HTTP:** dio 5 with a Bearer-auth interceptor; 401 clears the session
- and the router redirects to login.
-- **Audio:** just_audio wrapped by audio_service for background
- playback + lock-screen / notification controls.
-- **Auth:** Bearer tokens in flutter_secure_storage. Server already
- supports both cookie (web SPA) and Bearer (`internal/auth/session.go`).
-- **Routing:** go_router with a ShellRoute so the PlayerBar persists
- across navigation. Cold launch flow: no server-url → `/server-url`,
- url set / no token → `/login`, token present → `/home`.
-
-## CI
-
-`.gitea/workflows/flutter.yml` runs on push to dev/main, tag pushes,
-PR to main, and manual dispatch — path-filtered to `flutter_client/**`
-plus the shared web inputs. Steps: sync, analyze, test, build APK.
-Debug APKs upload as artifacts; release APKs attach to the Gitea
-release on tag.
-
-## Versioning
-
-`pubspec.yaml` `version` mirrors the server tag (e.g. server `v0.1.0`
-→ Flutter `0.1.0+1`). The server's `/healthz` returns
-`min_client_version`; old clients show an Update Required modal and
-refuse to operate. Bump `internal/server/version.go` `MinClientVersion`
-when a server-side change requires a paired client update.
-
-## Distribution
-
-- **Android:** APK on the Gitea release page. No Play Store for v1.
-- **iOS:** TestFlight on tag (manual upload v1, automate later).
-
-## Out-of-scope for slice 1
-
-Search, discover, requests, settings beyond server URL, admin,
-listening history, playlists, offline cache (#357). Those land in
-subsequent slices with their own brainstorm/spec/plan cycles. Per the
-operator's M7 ordering decision (2026-05-02), slice 2+ pauses to ship
-the missing web features first (#352 playlists, #362 theme toggle,
-#363 queue UI, #364 listening history, etc.) before mirroring on
-Flutter.
diff --git a/flutter_client/analysis_options.yaml b/flutter_client/analysis_options.yaml
deleted file mode 100644
index b91a43a1..00000000
--- a/flutter_client/analysis_options.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-analyzer:
- exclude:
- - build/**
- - lib/theme/tokens.dart # generated; allow magic numbers
-
-linter:
- rules:
- avoid_print: true
- prefer_const_constructors: true
- sort_pub_dependencies: false
diff --git a/flutter_client/android/.gitignore b/flutter_client/android/.gitignore
deleted file mode 100644
index be3943c9..00000000
--- a/flutter_client/android/.gitignore
+++ /dev/null
@@ -1,14 +0,0 @@
-gradle-wrapper.jar
-/.gradle
-/captures/
-/gradlew
-/gradlew.bat
-/local.properties
-GeneratedPluginRegistrant.java
-.cxx/
-
-# Remember to never publicly share your keystore.
-# See https://flutter.dev/to/reference-keystore
-key.properties
-**/*.keystore
-**/*.jks
diff --git a/flutter_client/android/app/build.gradle.kts b/flutter_client/android/app/build.gradle.kts
deleted file mode 100644
index 54f8d478..00000000
--- a/flutter_client/android/app/build.gradle.kts
+++ /dev/null
@@ -1,71 +0,0 @@
-plugins {
- id("com.android.application")
- id("kotlin-android")
- // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
- id("dev.flutter.flutter-gradle-plugin")
-}
-
-android {
- namespace = "com.fabledsword.minstrel"
- compileSdk = flutter.compileSdkVersion
- ndkVersion = flutter.ndkVersion
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
- }
-
- defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId = "com.fabledsword.minstrel"
- // You can update the following values to match your application needs.
- // For more information, see: https://flutter.dev/to/review-gradle-config.
- minSdk = flutter.minSdkVersion
- targetSdk = flutter.targetSdkVersion
- versionCode = flutter.versionCode
- versionName = flutter.versionName
- }
-
- // Real release signing config — populated only when CI exports
- // ANDROID_KEYSTORE_PATH (decoded from a base64 secret) plus the
- // matching password/alias env vars. Without these (local
- // `flutter build apk --release` runs), the release build falls
- // through to the debug keystore so local builds still work.
- //
- // Why this matters: Flutter's default `flutter run --release` and
- // CI's earlier setup both signed with the per-machine debug
- // keystore (~/.android/debug.keystore). Every CI runner generated
- // its own debug key, so consecutive release APKs had different
- // signatures and Android refused to upgrade an existing install.
- val keystorePath: String? = System.getenv("ANDROID_KEYSTORE_PATH")
- if (keystorePath != null && keystorePath.isNotEmpty()) {
- signingConfigs {
- create("release") {
- storeFile = file(keystorePath)
- storePassword = System.getenv("ANDROID_STORE_PASSWORD")
- keyAlias = System.getenv("ANDROID_KEY_ALIAS")
- keyPassword = System.getenv("ANDROID_KEY_PASSWORD")
- }
- }
- }
-
- buildTypes {
- release {
- signingConfig = if (keystorePath != null && keystorePath.isNotEmpty()) {
- signingConfigs.getByName("release")
- } else {
- // Local-only fallback so `flutter run --release` works
- // without the keystore. CI must export ANDROID_KEYSTORE_PATH.
- signingConfigs.getByName("debug")
- }
- }
- }
-}
-
-flutter {
- source = "../.."
-}
diff --git a/flutter_client/android/app/src/debug/AndroidManifest.xml b/flutter_client/android/app/src/debug/AndroidManifest.xml
deleted file mode 100644
index 399f6981..00000000
--- a/flutter_client/android/app/src/debug/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/flutter_client/android/app/src/main/AndroidManifest.xml b/flutter_client/android/app/src/main/AndroidManifest.xml
deleted file mode 100644
index cfa07960..00000000
--- a/flutter_client/android/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/flutter_client/android/app/src/main/kotlin/com/fabledsword/minstrel/MainActivity.kt b/flutter_client/android/app/src/main/kotlin/com/fabledsword/minstrel/MainActivity.kt
deleted file mode 100644
index 0be662c1..00000000
--- a/flutter_client/android/app/src/main/kotlin/com/fabledsword/minstrel/MainActivity.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.fabledsword.minstrel
-
-import android.content.Intent
-import androidx.core.content.FileProvider
-import com.ryanheise.audioservice.AudioServiceActivity
-import io.flutter.embedding.engine.FlutterEngine
-import io.flutter.plugin.common.MethodChannel
-import java.io.File
-
-// Must extend AudioServiceActivity (not FlutterActivity) so the
-// audio_service plugin can wire its FlutterEngine through. Without this
-// the platform call from AudioService.init() throws PlatformException
-// "The Activity class declared in your AndroidManifest.xml is wrong",
-// which on first start cascades into a flutter_cache_manager sqlite
-// EXCLUSIVE-lock crash because the engine partially re-initialises.
-class MainActivity : AudioServiceActivity() {
-
- companion object {
- private const val INSTALLER_CHANNEL = "com.fabledsword.minstrel/installer"
- }
-
- override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
- super.configureFlutterEngine(flutterEngine)
-
- // In-app update channel (#397). Dart calls install(path) with
- // the cache-dir APK path; we hand it to Android's
- // PackageInstaller via FileProvider + ACTION_VIEW.
- MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL)
- .setMethodCallHandler { call, result ->
- when (call.method) {
- "install" -> {
- val path = call.argument("path")
- if (path == null) {
- result.error("missing_path", "path argument required", null)
- return@setMethodCallHandler
- }
- try {
- installApk(path)
- result.success(null)
- } catch (e: Exception) {
- result.error("install_failed", e.message, null)
- }
- }
- else -> result.notImplemented()
- }
- }
- }
-
- private fun installApk(path: String) {
- val file = File(path)
- if (!file.exists()) {
- throw IllegalStateException("apk not found at $path")
- }
- val uri = FileProvider.getUriForFile(
- this,
- "${packageName}.fileprovider",
- file
- )
- val intent = Intent(Intent.ACTION_VIEW).apply {
- setDataAndType(uri, "application/vnd.android.package-archive")
- // NEW_TASK: PackageInstaller runs in its own task.
- // GRANT_READ_URI_PERMISSION: hands the content:// URI to
- // the installer process, which lacks our app's read perms
- // by default.
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- }
- startActivity(intent)
- }
-}
diff --git a/flutter_client/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter_client/android/app/src/main/res/drawable-v21/launch_background.xml
deleted file mode 100644
index f74085f3..00000000
--- a/flutter_client/android/app/src/main/res/drawable-v21/launch_background.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite.xml b/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite.xml
deleted file mode 100644
index 9c9aca56..00000000
--- a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
diff --git a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite_border.xml b/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite_border.xml
deleted file mode 100644
index 96ecf90f..00000000
--- a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite_border.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
diff --git a/flutter_client/android/app/src/main/res/drawable/launch_background.xml b/flutter_client/android/app/src/main/res/drawable/launch_background.xml
deleted file mode 100644
index 304732f8..00000000
--- a/flutter_client/android/app/src/main/res/drawable/launch_background.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/flutter_client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter_client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index db77bb4b..00000000
Binary files a/flutter_client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/flutter_client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter_client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 17987b79..00000000
Binary files a/flutter_client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/flutter_client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/flutter_client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 09d43914..00000000
Binary files a/flutter_client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/flutter_client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter_client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index d5f1c8d3..00000000
Binary files a/flutter_client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/flutter_client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter_client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 4d6372ee..00000000
Binary files a/flutter_client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/flutter_client/android/app/src/main/res/values-night/styles.xml b/flutter_client/android/app/src/main/res/values-night/styles.xml
deleted file mode 100644
index 06952be7..00000000
--- a/flutter_client/android/app/src/main/res/values-night/styles.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
diff --git a/flutter_client/android/app/src/main/res/values/styles.xml b/flutter_client/android/app/src/main/res/values/styles.xml
deleted file mode 100644
index cb1ef880..00000000
--- a/flutter_client/android/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
diff --git a/flutter_client/android/app/src/main/res/xml/file_paths.xml b/flutter_client/android/app/src/main/res/xml/file_paths.xml
deleted file mode 100644
index 4e45bfed..00000000
--- a/flutter_client/android/app/src/main/res/xml/file_paths.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
diff --git a/flutter_client/android/app/src/main/res/xml/network_security_config.xml b/flutter_client/android/app/src/main/res/xml/network_security_config.xml
deleted file mode 100644
index b600a598..00000000
--- a/flutter_client/android/app/src/main/res/xml/network_security_config.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
- 127.0.0.1
-
-
diff --git a/flutter_client/android/app/src/profile/AndroidManifest.xml b/flutter_client/android/app/src/profile/AndroidManifest.xml
deleted file mode 100644
index 399f6981..00000000
--- a/flutter_client/android/app/src/profile/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/flutter_client/android/build.gradle.kts b/flutter_client/android/build.gradle.kts
deleted file mode 100644
index dbee657b..00000000
--- a/flutter_client/android/build.gradle.kts
+++ /dev/null
@@ -1,24 +0,0 @@
-allprojects {
- repositories {
- google()
- mavenCentral()
- }
-}
-
-val newBuildDir: Directory =
- rootProject.layout.buildDirectory
- .dir("../../build")
- .get()
-rootProject.layout.buildDirectory.value(newBuildDir)
-
-subprojects {
- val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
- project.layout.buildDirectory.value(newSubprojectBuildDir)
-}
-subprojects {
- project.evaluationDependsOn(":app")
-}
-
-tasks.register("clean") {
- delete(rootProject.layout.buildDirectory)
-}
diff --git a/flutter_client/android/gradle.properties b/flutter_client/android/gradle.properties
deleted file mode 100644
index fbee1d8c..00000000
--- a/flutter_client/android/gradle.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
-android.useAndroidX=true
diff --git a/flutter_client/android/gradle/wrapper/gradle-wrapper.properties b/flutter_client/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index e4ef43fb..00000000
--- a/flutter_client/android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
diff --git a/flutter_client/android/settings.gradle.kts b/flutter_client/android/settings.gradle.kts
deleted file mode 100644
index ca7fe065..00000000
--- a/flutter_client/android/settings.gradle.kts
+++ /dev/null
@@ -1,26 +0,0 @@
-pluginManagement {
- val flutterSdkPath =
- run {
- val properties = java.util.Properties()
- file("local.properties").inputStream().use { properties.load(it) }
- val flutterSdkPath = properties.getProperty("flutter.sdk")
- require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
- flutterSdkPath
- }
-
- includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
-
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-plugins {
- id("dev.flutter.flutter-plugin-loader") version "1.0.0"
- id("com.android.application") version "8.11.1" apply false
- id("org.jetbrains.kotlin.android") version "2.2.20" apply false
-}
-
-include(":app")
diff --git a/flutter_client/assets/error-copy.json b/flutter_client/assets/error-copy.json
deleted file mode 100644
index 9a29973f..00000000
--- a/flutter_client/assets/error-copy.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "unknown": "Something went wrong.",
- "unauthenticated": "Your session has ended. Please sign in again.",
- "auth_required": "You need to sign in to do that.",
- "forbidden": "You don't have permission to do that.",
- "not_authorized": "You don't have permission to do that.",
- "invalid_credentials": "Wrong username or password.",
- "wrong_password": "Current password is incorrect.",
- "password_too_short": "Password must be at least 8 characters.",
- "username_invalid": "That username isn't valid.",
- "username_taken": "That username is already taken.",
- "email_invalid": "Enter a valid email address.",
- "email_taken": "That email is already in use.",
- "invalid_token": "That link has expired or already been used. Request a new one.",
- "invite_invalid": "That invite has expired or already been used.",
- "invite_required": "An invite is required to register on this server.",
- "last_admin": "Can't demote or delete the last admin.",
- "no_email_on_file": "No email is associated with that account.",
- "not_configured": "This integration isn't set up yet.",
- "validation": "Some fields aren't valid. Check and try again.",
- "missing_fields": "Required fields are missing.",
- "missing_query": "Search needs at least one keyword.",
- "invalid_id": "Invalid identifier.",
- "invalid_body": "Couldn't read the request.",
- "bad_request": "Invalid request.",
- "bad_body": "Couldn't read the request.",
- "bad_kind": "Invalid request type.",
- "bad_paging": "Invalid page size or offset.",
- "bad_reason": "Invalid quarantine reason.",
- "mbid_required": "An MBID is required for this lookup.",
- "system_playlist_readonly": "System playlists can't be edited directly.",
- "connection_refused": "Couldn't reach the server. Check the URL and try again.",
- "lidarr_unreachable": "Lidarr is unreachable right now. Try again, or check Admin → Integrations.",
- "lidarr_disabled": "Lidarr integration is not enabled.",
- "lidarr_auth_failed": "Lidarr authentication failed.",
- "lidarr_defaults_incomplete": "Lidarr is missing a default quality profile or root folder. Set them in Admin → Integrations.",
- "lidarr_server_error": "Lidarr returned an error. Check Lidarr's logs for the cause.",
- "lidarr_rejected": "Lidarr rejected the request. Check the server logs for the field-level reason.",
- "lidarr_album_lookup_failed": "Lidarr doesn't recognize this album. Try Resolve or Delete file instead.",
- "album_mbid_missing": "This track has no Lidarr album to remove.",
- "request_not_pending": "This request is no longer pending.",
- "request_not_found": "That request no longer exists.",
- "track_not_found": "That track no longer exists.",
- "album_not_found": "That album no longer exists.",
- "artist_not_found": "That artist no longer exists.",
- "playlist_not_found": "That playlist no longer exists.",
- "user_not_found": "That user no longer exists."
-}
diff --git a/flutter_client/assets/svg/album-fallback.svg b/flutter_client/assets/svg/album-fallback.svg
deleted file mode 100644
index 9720c751..00000000
--- a/flutter_client/assets/svg/album-fallback.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
diff --git a/flutter_client/ios/.gitignore b/flutter_client/ios/.gitignore
deleted file mode 100644
index 7a7f9873..00000000
--- a/flutter_client/ios/.gitignore
+++ /dev/null
@@ -1,34 +0,0 @@
-**/dgph
-*.mode1v3
-*.mode2v3
-*.moved-aside
-*.pbxuser
-*.perspectivev3
-**/*sync/
-.sconsign.dblite
-.tags*
-**/.vagrant/
-**/DerivedData/
-Icon?
-**/Pods/
-**/.symlinks/
-profile
-xcuserdata
-**/.generated/
-Flutter/App.framework
-Flutter/Flutter.framework
-Flutter/Flutter.podspec
-Flutter/Generated.xcconfig
-Flutter/ephemeral/
-Flutter/app.flx
-Flutter/app.zip
-Flutter/flutter_assets/
-Flutter/flutter_export_environment.sh
-ServiceDefinitions.json
-Runner/GeneratedPluginRegistrant.*
-
-# Exceptions to above rules.
-!default.mode1v3
-!default.mode2v3
-!default.pbxuser
-!default.perspectivev3
diff --git a/flutter_client/ios/Flutter/AppFrameworkInfo.plist b/flutter_client/ios/Flutter/AppFrameworkInfo.plist
deleted file mode 100644
index 391a902b..00000000
--- a/flutter_client/ios/Flutter/AppFrameworkInfo.plist
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- App
- CFBundleIdentifier
- io.flutter.flutter.app
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- App
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
- 1.0
- CFBundleSignature
- ????
- CFBundleVersion
- 1.0
-
-
diff --git a/flutter_client/ios/Flutter/Debug.xcconfig b/flutter_client/ios/Flutter/Debug.xcconfig
deleted file mode 100644
index 592ceee8..00000000
--- a/flutter_client/ios/Flutter/Debug.xcconfig
+++ /dev/null
@@ -1 +0,0 @@
-#include "Generated.xcconfig"
diff --git a/flutter_client/ios/Flutter/Release.xcconfig b/flutter_client/ios/Flutter/Release.xcconfig
deleted file mode 100644
index 592ceee8..00000000
--- a/flutter_client/ios/Flutter/Release.xcconfig
+++ /dev/null
@@ -1 +0,0 @@
-#include "Generated.xcconfig"
diff --git a/flutter_client/ios/Runner.xcodeproj/project.pbxproj b/flutter_client/ios/Runner.xcodeproj/project.pbxproj
deleted file mode 100644
index e8759b3f..00000000
--- a/flutter_client/ios/Runner.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,620 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 54;
- objects = {
-
-/* Begin PBXBuildFile section */
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 97C146E61CF9000F007C117D /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 97C146ED1CF9000F007C117D;
- remoteInfo = Runner;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
- 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
- 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
- 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 97C146EB1CF9000F007C117D /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 331C8082294A63A400263BE5 /* RunnerTests */ = {
- isa = PBXGroup;
- children = (
- 331C807B294A618700263BE5 /* RunnerTests.swift */,
- );
- path = RunnerTests;
- sourceTree = "";
- };
- 9740EEB11CF90186004384FC /* Flutter */ = {
- isa = PBXGroup;
- children = (
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEB21CF90195004384FC /* Debug.xcconfig */,
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
- 9740EEB31CF90195004384FC /* Generated.xcconfig */,
- );
- name = Flutter;
- sourceTree = "";
- };
- 97C146E51CF9000F007C117D = {
- isa = PBXGroup;
- children = (
- 9740EEB11CF90186004384FC /* Flutter */,
- 97C146F01CF9000F007C117D /* Runner */,
- 97C146EF1CF9000F007C117D /* Products */,
- 331C8082294A63A400263BE5 /* RunnerTests */,
- );
- sourceTree = "";
- };
- 97C146EF1CF9000F007C117D /* Products */ = {
- isa = PBXGroup;
- children = (
- 97C146EE1CF9000F007C117D /* Runner.app */,
- 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 97C146F01CF9000F007C117D /* Runner */ = {
- isa = PBXGroup;
- children = (
- 97C146FA1CF9000F007C117D /* Main.storyboard */,
- 97C146FD1CF9000F007C117D /* Assets.xcassets */,
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
- 97C147021CF9000F007C117D /* Info.plist */,
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
- 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
- 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
- 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
- );
- path = Runner;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 331C8080294A63A400263BE5 /* RunnerTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
- buildPhases = (
- 331C807D294A63A400263BE5 /* Sources */,
- 331C807F294A63A400263BE5 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 331C8086294A63A400263BE5 /* PBXTargetDependency */,
- );
- name = RunnerTests;
- productName = RunnerTests;
- productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
- 97C146ED1CF9000F007C117D /* Runner */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
- buildPhases = (
- 9740EEB61CF901F6004384FC /* Run Script */,
- 97C146EA1CF9000F007C117D /* Sources */,
- 97C146EB1CF9000F007C117D /* Frameworks */,
- 97C146EC1CF9000F007C117D /* Resources */,
- 9705A1C41CF9048500538489 /* Embed Frameworks */,
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = Runner;
- productName = Runner;
- productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 97C146E61CF9000F007C117D /* Project object */ = {
- isa = PBXProject;
- attributes = {
- BuildIndependentTargetsInParallel = YES;
- LastUpgradeCheck = 1510;
- ORGANIZATIONNAME = "";
- TargetAttributes = {
- 331C8080294A63A400263BE5 = {
- CreatedOnToolsVersion = 14.0;
- TestTargetID = 97C146ED1CF9000F007C117D;
- };
- 97C146ED1CF9000F007C117D = {
- CreatedOnToolsVersion = 7.3.1;
- LastSwiftMigration = 1100;
- };
- };
- };
- buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 9.3";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 97C146E51CF9000F007C117D;
- productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 97C146ED1CF9000F007C117D /* Runner */,
- 331C8080294A63A400263BE5 /* RunnerTests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 331C807F294A63A400263BE5 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 97C146EC1CF9000F007C117D /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
- );
- name = "Thin Binary";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
- };
- 9740EEB61CF901F6004384FC /* Run Script */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "Run Script";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
- };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 331C807D294A63A400263BE5 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 97C146EA1CF9000F007C117D /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
- 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 97C146ED1CF9000F007C117D /* Runner */;
- targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C146FB1CF9000F007C117D /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "";
- };
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C147001CF9000F007C117D /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 249021D3217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Profile;
- };
- 249021D4217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.fabledsword.minstrel;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Profile;
- };
- 331C8088294A63A400263BE5 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.fabledsword.minstrel.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Debug;
- };
- 331C8089294A63A400263BE5 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.fabledsword.minstrel.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Release;
- };
- 331C808A294A63A400263BE5 /* Profile */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.fabledsword.minstrel.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Profile;
- };
- 97C147031CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Debug;
- };
- 97C147041CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_OPTIMIZATION_LEVEL = "-O";
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 97C147061CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.fabledsword.minstrel;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Debug;
- };
- 97C147071CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.fabledsword.minstrel;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 331C8088294A63A400263BE5 /* Debug */,
- 331C8089294A63A400263BE5 /* Release */,
- 331C808A294A63A400263BE5 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147031CF9000F007C117D /* Debug */,
- 97C147041CF9000F007C117D /* Release */,
- 249021D3217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147061CF9000F007C117D /* Debug */,
- 97C147071CF9000F007C117D /* Release */,
- 249021D4217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 97C146E61CF9000F007C117D /* Project object */;
-}
diff --git a/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 919434a6..00000000
--- a/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d98100..00000000
--- a/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index f9b0d7c5..00000000
--- a/flutter_client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- PreviewsEnabled
-
-
-
diff --git a/flutter_client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter_client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
deleted file mode 100644
index e3773d42..00000000
--- a/flutter_client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/flutter_client/ios/Runner.xcworkspace/contents.xcworkspacedata b/flutter_client/ios/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 1d526a16..00000000
--- a/flutter_client/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/flutter_client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter_client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d98100..00000000
--- a/flutter_client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/flutter_client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter_client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index f9b0d7c5..00000000
--- a/flutter_client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- PreviewsEnabled
-
-
-
diff --git a/flutter_client/ios/Runner/AppDelegate.swift b/flutter_client/ios/Runner/AppDelegate.swift
deleted file mode 100644
index c30b367e..00000000
--- a/flutter_client/ios/Runner/AppDelegate.swift
+++ /dev/null
@@ -1,16 +0,0 @@
-import Flutter
-import UIKit
-
-@main
-@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
- override func application(
- _ application: UIApplication,
- didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
- ) -> Bool {
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
- }
-
- func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
- GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
- }
-}
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index d36b1fab..00000000
--- a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5x83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024x1024@1x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
deleted file mode 100644
index dc9ada47..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 7353c41e..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 797d452e..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index 6ed2d933..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index 4cd7b009..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index fe730945..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index 321773cd..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 797d452e..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index 502f463a..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index 0ec30343..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index 0ec30343..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index e9f5fea2..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index 84ac32ae..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 8953cba0..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
deleted file mode 100644
index 0467bf12..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
deleted file mode 100644
index 0bedcf2f..00000000
--- a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "filename" : "LaunchImage.png",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "LaunchImage@2x.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "filename" : "LaunchImage@3x.png",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
deleted file mode 100644
index 9da19eac..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
deleted file mode 100644
index 9da19eac..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
deleted file mode 100644
index 9da19eac..00000000
Binary files a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ
diff --git a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
deleted file mode 100644
index 89c2725b..00000000
--- a/flutter_client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Launch Screen Assets
-
-You can customize the launch screen with your own desired assets by replacing the image files in this directory.
-
-You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/flutter_client/ios/Runner/Base.lproj/LaunchScreen.storyboard b/flutter_client/ios/Runner/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index f2e259c7..00000000
--- a/flutter_client/ios/Runner/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/flutter_client/ios/Runner/Base.lproj/Main.storyboard b/flutter_client/ios/Runner/Base.lproj/Main.storyboard
deleted file mode 100644
index f3c28516..00000000
--- a/flutter_client/ios/Runner/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/flutter_client/ios/Runner/Info.plist b/flutter_client/ios/Runner/Info.plist
deleted file mode 100644
index ca00e585..00000000
--- a/flutter_client/ios/Runner/Info.plist
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
- CADisableMinimumFrameDurationOnPhone
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- Minstrel
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- minstrel
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UIApplicationSceneManifest
-
- UIApplicationSupportsMultipleScenes
-
- UISceneConfigurations
-
- UIWindowSceneSessionRoleApplication
-
-
- UISceneClassName
- UIWindowScene
- UISceneConfigurationName
- flutter
- UISceneDelegateClassName
- $(PRODUCT_MODULE_NAME).SceneDelegate
- UISceneStoryboardFile
- Main
-
-
-
-
- UIApplicationSupportsIndirectInputEvents
-
- UIBackgroundModes
-
- audio
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
diff --git a/flutter_client/ios/Runner/Runner-Bridging-Header.h b/flutter_client/ios/Runner/Runner-Bridging-Header.h
deleted file mode 100644
index 308a2a56..00000000
--- a/flutter_client/ios/Runner/Runner-Bridging-Header.h
+++ /dev/null
@@ -1 +0,0 @@
-#import "GeneratedPluginRegistrant.h"
diff --git a/flutter_client/ios/Runner/SceneDelegate.swift b/flutter_client/ios/Runner/SceneDelegate.swift
deleted file mode 100644
index b9ce8ea2..00000000
--- a/flutter_client/ios/Runner/SceneDelegate.swift
+++ /dev/null
@@ -1,6 +0,0 @@
-import Flutter
-import UIKit
-
-class SceneDelegate: FlutterSceneDelegate {
-
-}
diff --git a/flutter_client/ios/RunnerTests/RunnerTests.swift b/flutter_client/ios/RunnerTests/RunnerTests.swift
deleted file mode 100644
index 86a7c3b1..00000000
--- a/flutter_client/ios/RunnerTests/RunnerTests.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-import Flutter
-import UIKit
-import XCTest
-
-class RunnerTests: XCTestCase {
-
- func testExample() {
- // If you add code to the Runner application, consider adding tests here.
- // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
- }
-
-}
diff --git a/flutter_client/lib/admin/admin_landing_screen.dart b/flutter_client/lib/admin/admin_landing_screen.dart
deleted file mode 100644
index 79d0a03c..00000000
--- a/flutter_client/lib/admin/admin_landing_screen.dart
+++ /dev/null
@@ -1,67 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_lucide/flutter_lucide.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
-
-import '../shared/widgets/main_app_bar_actions.dart';
-import '../theme/theme_extension.dart';
-import 'admin_providers.dart';
-import 'widgets/admin_section_card.dart';
-
-class AdminLandingScreen extends ConsumerWidget {
- const AdminLandingScreen({super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final fs = Theme.of(context).extension()!;
- final counts = ref.watch(adminCountsProvider);
- return Scaffold(
- backgroundColor: fs.obsidian,
- appBar: AppBar(
- backgroundColor: fs.obsidian,
- elevation: 0,
- title: Text('Admin', style: TextStyle(color: fs.parchment)),
- actions: const [MainAppBarActions(currentRoute: '/admin')],
- ),
- body: SafeArea(
- child: counts.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (e, _) => Center(
- child: Text('$e', style: TextStyle(color: fs.error)),
- ),
- data: (c) => RefreshIndicator(
- onRefresh: () async => ref.refresh(adminCountsProvider.future),
- child: ListView(
- children: [
- AdminSectionCard(
- key: const Key('admin_card_requests'),
- icon: LucideIcons.inbox,
- title: 'Requests',
- subtitle: 'Approve or reject Lidarr requests',
- count: c.requests,
- onTap: () => context.push('/admin/requests'),
- ),
- AdminSectionCard(
- key: const Key('admin_card_quarantine'),
- icon: LucideIcons.triangle_alert,
- title: 'Quarantine',
- subtitle: 'Resolve scan failures',
- count: c.quarantine,
- onTap: () => context.push('/admin/quarantine'),
- ),
- AdminSectionCard(
- key: const Key('admin_card_users'),
- icon: LucideIcons.users,
- title: 'Users',
- subtitle: 'Manage accounts and invites',
- count: c.users,
- onTap: () => context.push('/admin/users'),
- ),
- ],
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/admin_providers.dart b/flutter_client/lib/admin/admin_providers.dart
deleted file mode 100644
index dcc220e6..00000000
--- a/flutter_client/lib/admin/admin_providers.dart
+++ /dev/null
@@ -1,237 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-import '../api/endpoints/admin_invites.dart';
-import '../api/endpoints/admin_quarantine.dart';
-import '../api/endpoints/admin_requests.dart';
-import '../api/endpoints/admin_users.dart';
-import '../library/library_providers.dart' show dioProvider;
-import '../models/admin_quarantine_item.dart';
-import '../models/admin_request.dart';
-import '../models/admin_user.dart';
-import '../models/invite.dart';
-
-final adminRequestsApiProvider = FutureProvider((ref) async {
- return AdminRequestsApi(await ref.watch(dioProvider.future));
-});
-
-final adminQuarantineApiProvider =
- FutureProvider((ref) async {
- return AdminQuarantineApi(await ref.watch(dioProvider.future));
-});
-
-final adminUsersApiProvider = FutureProvider((ref) async {
- return AdminUsersApi(await ref.watch(dioProvider.future));
-});
-
-final adminInvitesApiProvider = FutureProvider((ref) async {
- return AdminInvitesApi(await ref.watch(dioProvider.future));
-});
-
-class AdminCounts {
- const AdminCounts({
- required this.requests,
- required this.quarantine,
- required this.users,
- });
- final int requests;
- final int quarantine;
- final int users;
-}
-
-/// Fans out to the three list endpoints in parallel and rolls them up
-/// into `{requests, quarantine, users}` for the landing screen badges.
-final adminCountsProvider = FutureProvider((ref) async {
- final requestsApi = await ref.watch(adminRequestsApiProvider.future);
- final quarantineApi = await ref.watch(adminQuarantineApiProvider.future);
- final usersApi = await ref.watch(adminUsersApiProvider.future);
- final results = await Future.wait([
- requestsApi.list(),
- quarantineApi.list(),
- usersApi.list(),
- ]);
- return AdminCounts(
- requests: (results[0] as List).length,
- quarantine: (results[1] as List).length,
- users: (results[2] as List).length,
- );
-});
-
-class AdminRequestsController extends AsyncNotifier> {
- @override
- Future> build() async {
- final api = await ref.watch(adminRequestsApiProvider.future);
- return api.list();
- }
-
- Future approve(String id) async {
- await _decide(id, (api) => api.approve(id));
- }
-
- Future reject(String id) async {
- await _decide(id, (api) => api.reject(id));
- }
-
- Future _decide(
- String id,
- Future Function(AdminRequestsApi api) action,
- ) async {
- final api = await ref.read(adminRequestsApiProvider.future);
- final current = state.value ?? const [];
- state = AsyncData(current.where((r) => r.id != id).toList());
- try {
- await action(api);
- // Refresh landing badge.
- ref.invalidate(adminCountsProvider);
- } catch (e, st) {
- state = AsyncData(current);
- Error.throwWithStackTrace(e, st);
- }
- }
-}
-
-final adminRequestsProvider =
- AsyncNotifierProvider>(
- AdminRequestsController.new);
-
-/// Read side originally defined for the Requests screen's
-/// requester-UUID → username join. Mutation methods (setAdmin,
-/// setAutoApprove, resetPassword, delete) added alongside the Users
-/// screen in Slice 3. resetPassword is admin-supplies; the server
-/// has no auto-generation mode.
-class AdminUsersController extends AsyncNotifier> {
- @override
- Future> build() async {
- final api = await ref.watch(adminUsersApiProvider.future);
- return api.list();
- }
-
- /// Optimistic flip of `is_admin` on the user row; rolls back on
- /// server error (including the last-admin guard 4xx).
- Future setAdmin(String id, bool isAdmin) =>
- _patch(id, (u) => _copy(u, isAdmin: isAdmin),
- (api) => api.setAdmin(id, isAdmin));
-
- Future setAutoApprove(String id, bool autoApprove) =>
- _patch(id, (u) => _copy(u, autoApproveRequests: autoApprove),
- (api) => api.setAutoApprove(id, autoApprove));
-
- Future delete(String id) async {
- final api = await ref.read(adminUsersApiProvider.future);
- final current = state.value ?? const [];
- state = AsyncData(current.where((u) => u.id != id).toList());
- try {
- await api.delete(id);
- ref.invalidate(adminCountsProvider);
- } catch (e, st) {
- state = AsyncData(current);
- Error.throwWithStackTrace(e, st);
- }
- }
-
- Future resetPassword(String id, String newPassword) async {
- final api = await ref.read(adminUsersApiProvider.future);
- await api.resetPassword(id, newPassword);
- }
-
- Future _patch(
- String id,
- AdminUser Function(AdminUser) mutate,
- Future Function(AdminUsersApi api) action,
- ) async {
- final api = await ref.read(adminUsersApiProvider.future);
- final current = state.value ?? const [];
- state = AsyncData([
- for (final u in current) u.id == id ? mutate(u) : u,
- ]);
- try {
- await action(api);
- } catch (e, st) {
- state = AsyncData(current);
- Error.throwWithStackTrace(e, st);
- }
- }
-
- AdminUser _copy(AdminUser u, {bool? isAdmin, bool? autoApproveRequests}) =>
- AdminUser(
- id: u.id,
- username: u.username,
- displayName: u.displayName,
- isAdmin: isAdmin ?? u.isAdmin,
- autoApproveRequests: autoApproveRequests ?? u.autoApproveRequests,
- createdAt: u.createdAt,
- );
-}
-
-final adminUsersProvider =
- AsyncNotifierProvider>(
- AdminUsersController.new);
-
-class AdminQuarantineController
- extends AsyncNotifier> {
- @override
- Future> build() async {
- final api = await ref.watch(adminQuarantineApiProvider.future);
- return api.list();
- }
-
- Future resolve(String trackId) =>
- _act(trackId, (api) => api.resolve(trackId));
- Future deleteFile(String trackId) =>
- _act(trackId, (api) => api.deleteFile(trackId));
- Future deleteViaLidarr(String trackId) =>
- _act(trackId, (api) => api.deleteViaLidarr(trackId));
-
- Future _act(
- String trackId,
- Future Function(AdminQuarantineApi api) action,
- ) async {
- final api = await ref.read(adminQuarantineApiProvider.future);
- final current = state.value ?? const [];
- state = AsyncData(current.where((q) => q.trackId != trackId).toList());
- try {
- await action(api);
- ref.invalidate(adminCountsProvider);
- } catch (e, st) {
- state = AsyncData(current);
- Error.throwWithStackTrace(e, st);
- }
- }
-}
-
-final adminQuarantineProvider = AsyncNotifierProvider>(AdminQuarantineController.new);
-
-class AdminInvitesController extends AsyncNotifier> {
- @override
- Future> build() async {
- final api = await ref.watch(adminInvitesApiProvider.future);
- return api.list();
- }
-
- /// Returns the freshly-minted invite so the screen can show the
- /// token in a copy-once dialog. The new invite is also prepended
- /// to the local list so it shows up without a refresh.
- Future create({String? note}) async {
- final api = await ref.read(adminInvitesApiProvider.future);
- final invite = await api.create(note: note);
- final current = state.value ?? const [];
- state = AsyncData([invite, ...current]);
- return invite;
- }
-
- Future revoke(String token) async {
- final api = await ref.read(adminInvitesApiProvider.future);
- final current = state.value ?? const [];
- state = AsyncData(current.where((i) => i.token != token).toList());
- try {
- await api.revoke(token);
- } catch (e, st) {
- state = AsyncData(current);
- Error.throwWithStackTrace(e, st);
- }
- }
-}
-
-final adminInvitesProvider =
- AsyncNotifierProvider>(
- AdminInvitesController.new);
diff --git a/flutter_client/lib/admin/admin_quarantine_screen.dart b/flutter_client/lib/admin/admin_quarantine_screen.dart
deleted file mode 100644
index be77580a..00000000
--- a/flutter_client/lib/admin/admin_quarantine_screen.dart
+++ /dev/null
@@ -1,67 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-import '../shared/live_events_provider.dart';
-import '../shared/widgets/main_app_bar_actions.dart';
-import '../theme/theme_extension.dart';
-import 'admin_providers.dart';
-import 'widgets/admin_quarantine_row.dart';
-
-class AdminQuarantineScreen extends ConsumerWidget {
- const AdminQuarantineScreen({super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final fs = Theme.of(context).extension()!;
- // #402 wire-up: any quarantine event (flag from a user / admin
- // resolve / file delete / lidarr delete) refreshes the admin queue.
- ref.listen>(liveEventsProvider, (_, next) {
- final e = next.asData?.value;
- if (e == null) return;
- if (e.kind.startsWith('quarantine.')) {
- ref.invalidate(adminQuarantineProvider);
- }
- });
- final items = ref.watch(adminQuarantineProvider);
- return Scaffold(
- backgroundColor: fs.obsidian,
- appBar: AppBar(
- backgroundColor: fs.obsidian,
- elevation: 0,
- title: Text('Quarantine', style: TextStyle(color: fs.parchment)),
- actions: const [MainAppBarActions(currentRoute: '/admin/quarantine')],
- ),
- body: SafeArea(
- child: items.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (e, _) =>
- Center(child: Text('$e', style: TextStyle(color: fs.error))),
- data: (rows) {
- if (rows.isEmpty) {
- return Center(
- child: Text('No quarantined tracks.',
- style: TextStyle(color: fs.ash)),
- );
- }
- final notifier = ref.read(adminQuarantineProvider.notifier);
- return RefreshIndicator(
- onRefresh: () async =>
- ref.refresh(adminQuarantineProvider.future),
- child: ListView.builder(
- itemCount: rows.length,
- itemBuilder: (_, i) => AdminQuarantineRow(
- key: Key('admin_quarantine_row_${rows[i].trackId}'),
- item: rows[i],
- onResolve: () => notifier.resolve(rows[i].trackId),
- onDeleteFile: () => notifier.deleteFile(rows[i].trackId),
- onDeleteViaLidarr: () =>
- notifier.deleteViaLidarr(rows[i].trackId),
- ),
- ),
- );
- },
- ),
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/admin_requests_screen.dart b/flutter_client/lib/admin/admin_requests_screen.dart
deleted file mode 100644
index 5063265e..00000000
--- a/flutter_client/lib/admin/admin_requests_screen.dart
+++ /dev/null
@@ -1,81 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-import '../models/admin_user.dart';
-import '../shared/live_events_provider.dart';
-import '../shared/widgets/main_app_bar_actions.dart';
-import '../theme/theme_extension.dart';
-import 'admin_providers.dart';
-import 'widgets/admin_request_row.dart';
-
-class AdminRequestsScreen extends ConsumerWidget {
- const AdminRequestsScreen({super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final fs = Theme.of(context).extension()!;
- // #402 wire-up: invalidate the admin requests list when a user
- // creates/cancels/admin approves/rejects/reconciler completes —
- // request.status_changed covers all of them.
- ref.listen>(liveEventsProvider, (_, next) {
- final e = next.asData?.value;
- if (e?.kind == 'request.status_changed') {
- ref.invalidate(adminRequestsProvider);
- }
- });
- final requests = ref.watch(adminRequestsProvider);
- // Best-effort lookup for requester usernames. If the users provider
- // hasn't loaded yet, valueOrNull is null and rows fall back to the
- // UUID prefix; no blocking spinner.
- final users = ref.watch(adminUsersProvider).value ?? const [];
- final usersById = {for (final u in users) u.id: u};
-
- return Scaffold(
- backgroundColor: fs.obsidian,
- appBar: AppBar(
- backgroundColor: fs.obsidian,
- elevation: 0,
- title: Text('Requests', style: TextStyle(color: fs.parchment)),
- actions: const [MainAppBarActions(currentRoute: '/admin/requests')],
- ),
- body: SafeArea(
- child: requests.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (e, _) =>
- Center(child: Text('$e', style: TextStyle(color: fs.error))),
- data: (rows) {
- if (rows.isEmpty) {
- return Center(
- child: Text('No pending requests.',
- style: TextStyle(color: fs.ash)),
- );
- }
- final notifier = ref.read(adminRequestsProvider.notifier);
- return RefreshIndicator(
- onRefresh: () async =>
- ref.refresh(adminRequestsProvider.future),
- child: ListView.builder(
- itemCount: rows.length,
- itemBuilder: (_, i) {
- final req = rows[i];
- final user = usersById[req.userId];
- final display = user?.username ??
- (req.userId.length >= 8
- ? req.userId.substring(0, 8)
- : req.userId);
- return AdminRequestRow(
- key: Key('admin_request_row_${req.id}'),
- request: req,
- requesterDisplay: display,
- onApprove: () => notifier.approve(req.id),
- onReject: () => notifier.reject(req.id),
- );
- },
- ),
- );
- },
- ),
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/admin_users_screen.dart b/flutter_client/lib/admin/admin_users_screen.dart
deleted file mode 100644
index 122a5891..00000000
--- a/flutter_client/lib/admin/admin_users_screen.dart
+++ /dev/null
@@ -1,201 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_lucide/flutter_lucide.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-import '../shared/widgets/main_app_bar_actions.dart';
-import '../theme/theme_extension.dart';
-import 'admin_providers.dart';
-import 'widgets/admin_user_edit_sheet.dart';
-import 'widgets/admin_user_row.dart';
-import 'widgets/invite_row.dart';
-
-class AdminUsersScreen extends ConsumerWidget {
- const AdminUsersScreen({super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final fs = Theme.of(context).extension()!;
- final users = ref.watch(adminUsersProvider);
- final invites = ref.watch(adminInvitesProvider);
- return Scaffold(
- backgroundColor: fs.obsidian,
- appBar: AppBar(
- backgroundColor: fs.obsidian,
- elevation: 0,
- title: Text('Users', style: TextStyle(color: fs.parchment)),
- actions: const [MainAppBarActions(currentRoute: '/admin/users')],
- ),
- body: SafeArea(
- child: RefreshIndicator(
- onRefresh: () async {
- ref.invalidate(adminUsersProvider);
- ref.invalidate(adminInvitesProvider);
- },
- child: ListView(
- children: [
- const _SectionHeader(text: 'Users'),
- users.when(
- loading: () => const Padding(
- padding: EdgeInsets.all(16),
- child: Center(child: CircularProgressIndicator()),
- ),
- error: (e, _) => Padding(
- padding: const EdgeInsets.all(16),
- child: Text('$e', style: TextStyle(color: fs.error)),
- ),
- data: (rows) => Column(
- children: rows
- .map((u) => AdminUserRow(
- key: Key('admin_user_row_${u.id}'),
- user: u,
- onTap: () => AdminUserEditSheet.show(context, u),
- ))
- .toList(),
- ),
- ),
- const Divider(),
- _SectionHeader(
- text: 'Invites',
- trailing: TextButton.icon(
- key: const Key('invite_generate_button'),
- onPressed: () => _showGenerateInvite(context, ref),
- icon: Icon(LucideIcons.plus, color: fs.parchment),
- label: Text('Generate',
- style: TextStyle(color: fs.parchment)),
- ),
- ),
- invites.when(
- loading: () => const Padding(
- padding: EdgeInsets.all(16),
- child: Center(child: CircularProgressIndicator()),
- ),
- error: (e, _) => Padding(
- padding: const EdgeInsets.all(16),
- child: Text('$e', style: TextStyle(color: fs.error)),
- ),
- data: (rows) => Column(
- children: rows
- .map((i) => InviteRow(
- key: Key('invite_row_${i.token}'),
- invite: i,
- onRevoke: () => ref
- .read(adminInvitesProvider.notifier)
- .revoke(i.token),
- ))
- .toList(),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-
- Future _showGenerateInvite(BuildContext context, WidgetRef ref) async {
- final controller = TextEditingController();
- final note = await showDialog(
- context: context,
- builder: (ctx) => AlertDialog(
- title: const Text('Generate invite'),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Text('Token expires in 24 hours.'),
- const SizedBox(height: 12),
- TextField(
- controller: controller,
- decoration: const InputDecoration(
- labelText: 'Note (optional)',
- helperText: 'e.g. "for alice"',
- ),
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(ctx),
- child: const Text('Cancel'),
- ),
- TextButton(
- onPressed: () => Navigator.pop(ctx, controller.text),
- child: const Text('Generate'),
- ),
- ],
- ),
- );
- if (note == null || !context.mounted) return;
- try {
- final invite = await ref
- .read(adminInvitesProvider.notifier)
- .create(note: note.isEmpty ? null : note);
- if (!context.mounted) return;
- await showDialog(
- context: context,
- builder: (ctx) => AlertDialog(
- title: const Text('Invite created'),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Text('Share this token with the new user:'),
- const SizedBox(height: 8),
- SelectableText(
- invite.token,
- style: const TextStyle(fontFamily: 'JetBrainsMono'),
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: () {
- Clipboard.setData(ClipboardData(text: invite.token));
- Navigator.pop(ctx);
- },
- child: const Text('Copy'),
- ),
- TextButton(
- onPressed: () => Navigator.pop(ctx),
- child: const Text('Close'),
- ),
- ],
- ),
- );
- } catch (e) {
- if (context.mounted) {
- ScaffoldMessenger.of(context)
- .showSnackBar(SnackBar(content: Text('$e')));
- }
- }
- }
-}
-
-class _SectionHeader extends StatelessWidget {
- const _SectionHeader({required this.text, this.trailing});
- final String text;
- final Widget? trailing;
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- return Padding(
- padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
- child: Row(
- children: [
- Text(
- text,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'Fraunces',
- fontSize: 18,
- ),
- ),
- const Spacer(),
- if (trailing != null) trailing!,
- ],
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/widgets/admin_quarantine_row.dart b/flutter_client/lib/admin/widgets/admin_quarantine_row.dart
deleted file mode 100644
index 8221b82b..00000000
--- a/flutter_client/lib/admin/widgets/admin_quarantine_row.dart
+++ /dev/null
@@ -1,102 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_lucide/flutter_lucide.dart';
-
-import '../../models/admin_quarantine_item.dart';
-import '../../theme/theme_extension.dart';
-import 'typed_confirm_sheet.dart';
-
-class AdminQuarantineRow extends StatelessWidget {
- const AdminQuarantineRow({
- super.key,
- required this.item,
- required this.onResolve,
- required this.onDeleteFile,
- required this.onDeleteViaLidarr,
- });
-
- final AdminQuarantineItem item;
- final VoidCallback onResolve;
- final VoidCallback onDeleteFile;
- final VoidCallback onDeleteViaLidarr;
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- return ExpansionTile(
- key: Key('admin_quarantine_tile_${item.trackId}'),
- title: Text(
- item.trackTitle,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'Fraunces',
- fontSize: 16,
- ),
- ),
- subtitle: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('${item.artistName} · ${item.albumTitle}',
- style: TextStyle(color: fs.ash)),
- Text(
- '${item.reportCount} '
- '${item.reportCount == 1 ? "report" : "reports"} · ${item.topReasonSummary}',
- style: TextStyle(color: fs.error, fontSize: 12),
- ),
- ],
- ),
- trailing: PopupMenuButton(
- key: Key('admin_quarantine_menu_${item.trackId}'),
- icon: Icon(LucideIcons.ellipsis_vertical, color: fs.parchment),
- onSelected: (action) async {
- switch (action) {
- case 'resolve':
- onResolve();
- break;
- case 'delete_file':
- if (await TypedConfirmSheet.show(
- context,
- title: 'Delete file?',
- message:
- 'Permanently delete the local file for "${item.trackTitle}". '
- 'Cannot be undone.',
- )) {
- onDeleteFile();
- }
- break;
- case 'delete_lidarr':
- if (await TypedConfirmSheet.show(
- context,
- title: 'Delete via Lidarr?',
- message:
- 'Ask Lidarr to delete the file for "${item.trackTitle}".',
- )) {
- onDeleteViaLidarr();
- }
- break;
- }
- },
- itemBuilder: (_) => const [
- PopupMenuItem(value: 'resolve', child: Text('Resolve')),
- PopupMenuItem(value: 'delete_file', child: Text('Delete file')),
- PopupMenuItem(
- value: 'delete_lidarr', child: Text('Delete via Lidarr')),
- ],
- ),
- childrenPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 8),
- children: [
- for (final report in item.reports)
- ListTile(
- dense: true,
- title: Text(
- '${report.username} — ${report.reason}',
- style: TextStyle(color: fs.parchment, fontSize: 13),
- ),
- subtitle: report.notes != null
- ? Text(report.notes!,
- style: TextStyle(color: fs.ash, fontSize: 12))
- : null,
- ),
- ],
- );
- }
-}
diff --git a/flutter_client/lib/admin/widgets/admin_request_row.dart b/flutter_client/lib/admin/widgets/admin_request_row.dart
deleted file mode 100644
index f826556c..00000000
--- a/flutter_client/lib/admin/widgets/admin_request_row.dart
+++ /dev/null
@@ -1,75 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../models/admin_request.dart';
-import '../../theme/theme_extension.dart';
-
-class AdminRequestRow extends StatelessWidget {
- const AdminRequestRow({
- super.key,
- required this.request,
- required this.requesterDisplay,
- required this.onApprove,
- required this.onReject,
- });
-
- final AdminRequest request;
-
- /// Pre-resolved username (or UUID-prefix fallback) for the requester.
- final String requesterDisplay;
-
- final VoidCallback onApprove;
- final VoidCallback onReject;
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- final subtitle =
- '${request.kind} · ${request.artistName} · requested by $requesterDisplay';
- return ListTile(
- title: Text(
- request.displayName,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'Fraunces',
- fontSize: 16,
- ),
- ),
- subtitle: Text(subtitle, style: TextStyle(color: fs.ash)),
- trailing: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- TextButton(
- onPressed: onApprove,
- style: TextButton.styleFrom(foregroundColor: fs.moss),
- child: const Text('Approve'),
- ),
- TextButton(
- onPressed: () async {
- final ok = await showDialog(
- context: context,
- builder: (ctx) => AlertDialog(
- title: const Text('Reject request?'),
- content: Text('Reject "${request.displayName}"?'),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(ctx, false),
- child: const Text('Cancel'),
- ),
- TextButton(
- onPressed: () => Navigator.pop(ctx, true),
- style: TextButton.styleFrom(foregroundColor: fs.oxblood),
- child: const Text('Reject'),
- ),
- ],
- ),
- );
- if (ok == true) onReject();
- },
- style: TextButton.styleFrom(foregroundColor: fs.oxblood),
- child: const Text('Reject'),
- ),
- ],
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/widgets/admin_section_card.dart b/flutter_client/lib/admin/widgets/admin_section_card.dart
deleted file mode 100644
index 919441de..00000000
--- a/flutter_client/lib/admin/widgets/admin_section_card.dart
+++ /dev/null
@@ -1,53 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../theme/theme_extension.dart';
-
-class AdminSectionCard extends StatelessWidget {
- const AdminSectionCard({
- super.key,
- required this.icon,
- required this.title,
- required this.subtitle,
- required this.count,
- required this.onTap,
- });
-
- final IconData icon;
- final String title;
- final String subtitle;
- final int count;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- return Card(
- color: fs.iron,
- margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
- child: ListTile(
- leading: Icon(icon, color: fs.parchment),
- title: Text(
- title,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'Fraunces',
- fontSize: 18,
- ),
- ),
- subtitle: Text(subtitle, style: TextStyle(color: fs.ash)),
- trailing: Container(
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
- decoration: BoxDecoration(
- color: fs.bronze,
- borderRadius: BorderRadius.circular(12),
- ),
- child: Text(
- '$count',
- style: TextStyle(color: fs.obsidian, fontWeight: FontWeight.w500),
- ),
- ),
- onTap: onTap,
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/widgets/admin_user_edit_sheet.dart b/flutter_client/lib/admin/widgets/admin_user_edit_sheet.dart
deleted file mode 100644
index 870d4af4..00000000
--- a/flutter_client/lib/admin/widgets/admin_user_edit_sheet.dart
+++ /dev/null
@@ -1,167 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-import '../../models/admin_user.dart';
-import '../../theme/theme_extension.dart';
-import '../admin_providers.dart';
-import 'typed_confirm_sheet.dart';
-
-class AdminUserEditSheet extends ConsumerWidget {
- const AdminUserEditSheet({super.key, required this.user});
-
- final AdminUser user;
-
- static Future show(BuildContext context, AdminUser user) =>
- showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- builder: (_) => AdminUserEditSheet(user: user),
- );
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final fs = Theme.of(context).extension()!;
- final users = ref.read(adminUsersProvider.notifier);
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).viewInsets.bottom,
- ),
- child: Container(
- color: fs.iron,
- padding: const EdgeInsets.all(20),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- user.username,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'Fraunces',
- fontSize: 20,
- ),
- ),
- const SizedBox(height: 12),
- SwitchListTile(
- key: const Key('user_edit_is_admin'),
- title: Text('Admin', style: TextStyle(color: fs.parchment)),
- value: user.isAdmin,
- onChanged: (v) async {
- try {
- await users.setAdmin(user.id, v);
- } catch (e) {
- if (context.mounted) {
- ScaffoldMessenger.of(context)
- .showSnackBar(SnackBar(content: Text('$e')));
- }
- }
- },
- ),
- SwitchListTile(
- key: const Key('user_edit_auto_approve'),
- title: Text('Auto-approve requests',
- style: TextStyle(color: fs.parchment)),
- value: user.autoApproveRequests,
- onChanged: (v) async {
- try {
- await users.setAutoApprove(user.id, v);
- } catch (e) {
- if (context.mounted) {
- ScaffoldMessenger.of(context)
- .showSnackBar(SnackBar(content: Text('$e')));
- }
- }
- },
- ),
- const SizedBox(height: 12),
- Row(
- children: [
- TextButton(
- key: const Key('user_edit_reset_password'),
- onPressed: () => _resetPassword(context, users),
- child: const Text('Reset password'),
- ),
- const Spacer(),
- TextButton(
- key: const Key('user_edit_delete'),
- style: TextButton.styleFrom(foregroundColor: fs.oxblood),
- onPressed: () => _deleteUser(context, users),
- child: const Text('Delete'),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }
-
- Future _resetPassword(
- BuildContext context, AdminUsersController users) async {
- final controller = TextEditingController();
- final newPw = await showDialog(
- context: context,
- builder: (ctx) => AlertDialog(
- title: const Text('Reset password'),
- content: TextField(
- controller: controller,
- obscureText: true,
- decoration: const InputDecoration(
- labelText: 'New password',
- helperText: 'Minimum 8 characters',
- ),
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(ctx),
- child: const Text('Cancel'),
- ),
- TextButton(
- onPressed: () {
- if (controller.text.length >= 8) {
- Navigator.pop(ctx, controller.text);
- }
- },
- child: const Text('Reset'),
- ),
- ],
- ),
- );
- if (newPw == null || !context.mounted) return;
- try {
- await users.resetPassword(user.id, newPw);
- if (context.mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(content: Text('Password reset.')),
- );
- }
- } catch (e) {
- if (context.mounted) {
- ScaffoldMessenger.of(context)
- .showSnackBar(SnackBar(content: Text('$e')));
- }
- }
- }
-
- Future _deleteUser(
- BuildContext context, AdminUsersController users) async {
- if (!await TypedConfirmSheet.show(
- context,
- title: 'Delete user?',
- message:
- 'Permanently delete user "${user.username}" and all their data. '
- 'Cannot be undone.',
- )) {
- return;
- }
- try {
- await users.delete(user.id);
- if (context.mounted) Navigator.pop(context);
- } catch (e) {
- if (context.mounted) {
- ScaffoldMessenger.of(context)
- .showSnackBar(SnackBar(content: Text('$e')));
- }
- }
- }
-}
diff --git a/flutter_client/lib/admin/widgets/admin_user_row.dart b/flutter_client/lib/admin/widgets/admin_user_row.dart
deleted file mode 100644
index e4deee49..00000000
--- a/flutter_client/lib/admin/widgets/admin_user_row.dart
+++ /dev/null
@@ -1,59 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_lucide/flutter_lucide.dart';
-
-import '../../models/admin_user.dart';
-import '../../theme/theme_extension.dart';
-
-class AdminUserRow extends StatelessWidget {
- const AdminUserRow({super.key, required this.user, required this.onTap});
-
- final AdminUser user;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- return ListTile(
- title: Text(
- user.username,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'Fraunces',
- fontSize: 16,
- ),
- ),
- subtitle: Wrap(
- spacing: 6,
- children: [
- if (user.isAdmin) _Badge(label: 'admin', color: fs.bronze),
- if (user.autoApproveRequests)
- _Badge(label: 'auto-approve', color: fs.moss),
- ],
- ),
- trailing: Icon(LucideIcons.chevron_right, color: fs.ash),
- onTap: onTap,
- );
- }
-}
-
-class _Badge extends StatelessWidget {
- const _Badge({required this.label, required this.color});
- final String label;
- final Color color;
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
- decoration: BoxDecoration(
- color: color,
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(
- label,
- style: TextStyle(color: fs.obsidian, fontSize: 11),
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/widgets/invite_row.dart b/flutter_client/lib/admin/widgets/invite_row.dart
deleted file mode 100644
index 5a9987dd..00000000
--- a/flutter_client/lib/admin/widgets/invite_row.dart
+++ /dev/null
@@ -1,73 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_lucide/flutter_lucide.dart';
-import 'package:flutter/services.dart';
-
-import '../../models/invite.dart';
-import '../../theme/theme_extension.dart';
-
-class InviteRow extends StatelessWidget {
- const InviteRow({
- super.key,
- required this.invite,
- required this.onRevoke,
- });
-
- final Invite invite;
- final VoidCallback onRevoke;
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- return ListTile(
- title: Row(
- children: [
- Expanded(
- child: SelectableText(
- invite.token,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'JetBrainsMono',
- fontSize: 13,
- ),
- ),
- ),
- IconButton(
- icon: Icon(LucideIcons.copy, color: fs.ash, size: 18),
- tooltip: 'Copy',
- onPressed: () =>
- Clipboard.setData(ClipboardData(text: invite.token)),
- ),
- ],
- ),
- subtitle: Wrap(
- spacing: 8,
- crossAxisAlignment: WrapCrossAlignment.center,
- children: [
- Text('expires ${invite.expiresAt}',
- style: TextStyle(color: fs.ash, fontSize: 12)),
- if (invite.note != null)
- Text('· ${invite.note}',
- style: TextStyle(color: fs.ash, fontSize: 12)),
- if (invite.isRedeemed)
- Container(
- padding:
- const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
- decoration: BoxDecoration(
- color: fs.ash,
- borderRadius: BorderRadius.circular(6),
- ),
- child: Text(
- 'redeemed',
- style: TextStyle(color: fs.obsidian, fontSize: 10),
- ),
- ),
- ],
- ),
- trailing: IconButton(
- icon: Icon(LucideIcons.trash_2, color: fs.oxblood),
- tooltip: 'Revoke',
- onPressed: onRevoke,
- ),
- );
- }
-}
diff --git a/flutter_client/lib/admin/widgets/typed_confirm_sheet.dart b/flutter_client/lib/admin/widgets/typed_confirm_sheet.dart
deleted file mode 100644
index 2904e7ae..00000000
--- a/flutter_client/lib/admin/widgets/typed_confirm_sheet.dart
+++ /dev/null
@@ -1,124 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../theme/theme_extension.dart';
-
-/// Modal bottom sheet that requires the user to type [confirmWord]
-/// (default "DELETE") before the confirm button enables. Used for
-/// destructive actions like quarantine delete-file / delete-via-Lidarr
-/// and user delete.
-///
-/// Returns true if the user confirmed, false (or null → false) otherwise.
-class TypedConfirmSheet extends StatefulWidget {
- const TypedConfirmSheet({
- super.key,
- required this.title,
- required this.message,
- this.confirmWord = 'DELETE',
- this.confirmLabel = 'Delete',
- });
-
- final String title;
- final String message;
- final String confirmWord;
- final String confirmLabel;
-
- static Future show(
- BuildContext context, {
- required String title,
- required String message,
- String confirmWord = 'DELETE',
- String confirmLabel = 'Delete',
- }) async {
- final ok = await showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- builder: (_) => TypedConfirmSheet(
- title: title,
- message: message,
- confirmWord: confirmWord,
- confirmLabel: confirmLabel,
- ),
- );
- return ok ?? false;
- }
-
- @override
- State createState() => _TypedConfirmSheetState();
-}
-
-class _TypedConfirmSheetState extends State {
- final _controller = TextEditingController();
- bool _enabled = false;
-
- @override
- void dispose() {
- _controller.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- final fs = Theme.of(context).extension()!;
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).viewInsets.bottom,
- ),
- child: Container(
- color: fs.iron,
- padding: const EdgeInsets.all(20),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- widget.title,
- style: TextStyle(
- color: fs.parchment,
- fontFamily: 'Fraunces',
- fontSize: 20,
- ),
- ),
- const SizedBox(height: 12),
- Text(widget.message, style: TextStyle(color: fs.ash)),
- const SizedBox(height: 16),
- Text(
- 'Type ${widget.confirmWord} to confirm:',
- style: TextStyle(color: fs.ash),
- ),
- const SizedBox(height: 8),
- TextField(
- key: const Key('typed_confirm_input'),
- controller: _controller,
- autofocus: true,
- style: TextStyle(color: fs.parchment, fontFamily: 'JetBrainsMono'),
- decoration: const InputDecoration(border: OutlineInputBorder()),
- onChanged: (v) =>
- setState(() => _enabled = v == widget.confirmWord),
- ),
- const SizedBox(height: 16),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- TextButton(
- onPressed: () => Navigator.pop(context, false),
- child: const Text('Cancel'),
- ),
- const SizedBox(width: 8),
- ElevatedButton(
- key: const Key('typed_confirm_button'),
- onPressed:
- _enabled ? () => Navigator.pop(context, true) : null,
- style: ElevatedButton.styleFrom(
- backgroundColor: fs.oxblood,
- foregroundColor: fs.parchment,
- ),
- child: Text(widget.confirmLabel),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/flutter_client/lib/api/client.dart b/flutter_client/lib/api/client.dart
deleted file mode 100644
index dd6363fa..00000000
--- a/flutter_client/lib/api/client.dart
+++ /dev/null
@@ -1,41 +0,0 @@
-import 'package:dio/dio.dart';
-
-typedef TokenResolver = Future Function();
-typedef OnUnauthenticated = Future Function();
-
-class ApiClient {
- /// Builds a dio instance pinned to [baseUrl] with a Bearer-auth
- /// interceptor that pulls the current token from [tokenResolver]
- /// on every request. Server already supports cookie OR bearer
- /// (internal/auth/session.go); we use bearer to skip cookie-jar.
- ///
- /// [on401] fires when any response comes back 401. Use it to clear
- /// stored credentials so the router redirect can route back to login.
- static Dio buildDio({
- required String baseUrl,
- required TokenResolver tokenResolver,
- OnUnauthenticated? on401,
- }) {
- final d = Dio(BaseOptions(
- baseUrl: baseUrl,
- connectTimeout: const Duration(seconds: 8),
- receiveTimeout: const Duration(seconds: 30),
- contentType: Headers.jsonContentType,
- responseType: ResponseType.json,
- ));
- d.interceptors.add(InterceptorsWrapper(
- onRequest: (opts, h) async {
- final t = await tokenResolver();
- if (t != null && t.isNotEmpty) opts.headers['Authorization'] = 'Bearer $t';
- h.next(opts);
- },
- onError: (e, h) async {
- if (e.response?.statusCode == 401 && on401 != null) {
- await on401();
- }
- h.next(e);
- },
- ));
- return d;
- }
-}
diff --git a/flutter_client/lib/api/endpoints/admin_invites.dart b/flutter_client/lib/api/endpoints/admin_invites.dart
deleted file mode 100644
index e5ab6142..00000000
--- a/flutter_client/lib/api/endpoints/admin_invites.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'package:dio/dio.dart';
-
-import '../../models/invite.dart';
-
-class AdminInvitesApi {
- AdminInvitesApi(this._dio);
- final Dio _dio;
-
- /// GET /api/admin/invites → `{"invites": [...]}`. Envelope unwrapped.
- Future> list() async {
- final r = await _dio.get