Initial commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
*.g.dart linguist-generated=true
|
||||||
|
*.freezed.dart linguist-generated=true
|
||||||
|
*.sh text eol=lf
|
||||||
|
|
||||||
|
android/app/libs/*.aar binary -text linguist-generated=true
|
||||||
|
|
||||||
|
linux/flutter/generated_plugin_registrant.cc text eol=lf
|
||||||
|
linux/flutter/generated_plugins.cmake text eol=lf
|
||||||
|
macos/Flutter/GeneratedPluginRegistrant.swift text eol=lf
|
||||||
|
windows/flutter/generated_plugin_registrant.cc text eol=lf
|
||||||
|
windows/flutter/generated_plugins.cmake text eol=lf
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# Local editor/tool state
|
||||||
|
.idea/
|
||||||
|
.cursor/
|
||||||
|
*.iml
|
||||||
|
.ace-tool/
|
||||||
|
.vscode/
|
||||||
|
.claude/index.json
|
||||||
|
.claude/settings.local.json
|
||||||
|
.claude/worktrees/
|
||||||
|
.codex/config.toml
|
||||||
|
.takumi/
|
||||||
|
.workflow/
|
||||||
|
.codestable/
|
||||||
|
/.omc/
|
||||||
|
*.moc
|
||||||
|
node_modules/
|
||||||
|
out/
|
||||||
|
devtools_options.yaml
|
||||||
|
|
||||||
|
# Screenshots & debug captures
|
||||||
|
flutter_*.png
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/.gradle/
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Platform generated artifacts
|
||||||
|
**/generated_plugin_registrant.*
|
||||||
|
**/generated_plugins.cmake
|
||||||
|
linux/flutter/ephemeral/
|
||||||
|
macos/Flutter/ephemeral/
|
||||||
|
macos/Pods/
|
||||||
|
windows/flutter/ephemeral/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Android machine-specific / generated (do not commit)
|
||||||
|
/android/local.properties
|
||||||
|
**/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Large binaries & build artifacts (prevent re-commit)
|
||||||
|
.media3-build/
|
||||||
|
dist/
|
||||||
|
**/target/
|
||||||
|
*.pdb
|
||||||
|
*.rlib
|
||||||
|
*.rmeta
|
||||||
|
*.dll
|
||||||
|
*.dylib
|
||||||
|
*.so
|
||||||
|
*.7z
|
||||||
|
*.wasm
|
||||||
|
test_link/
|
||||||
|
|
||||||
|
# Local git worktrees
|
||||||
|
.worktrees/
|
||||||
|
|
||||||
|
# Superpowers brainstorm visual companion
|
||||||
|
.superpowers/
|
||||||
|
|
||||||
|
# Opencode project config may be committed; dependency/runtime output should not.
|
||||||
|
.opencode/node_modules/
|
||||||
|
.opencode/images/
|
||||||
|
.opencode/.cache/
|
||||||
|
|
||||||
|
# Windows reserved device name (created accidentally)
|
||||||
|
nul
|
||||||
|
|
||||||
|
# 本地签名密钥,勿提交
|
||||||
|
android/release-signing.json
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
# 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'
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# SMPlayer
|
||||||
|
|
||||||
|
一个 Emby 媒体服务器跨平台客户端。
|
||||||
|
|
||||||
|
目前主要面向 Windows / macOS
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
- Emby 媒体库浏览:主页、媒体库、详情、搜索、收藏、播放记录、最近添加。
|
||||||
|
- 播放器:倍速、音轨 / 字幕切换、字幕样式、画面比例、选集、全屏、桌面画中画。
|
||||||
|
- 弹幕:兼容弹弹 play / danmu_api 风格 API,支持多源、自动匹配、手动搜索、过滤和样式调整。
|
||||||
|
- 多服务器:可添加多个 Emby 服务器,支持本地备份 / 导入配置。
|
||||||
|
- 扩展功能:可选 TMDB 详情 / 发现页、Trakt scrobble 和应用内更新。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
Flutter、Riverpod、Dio / Retrofit、fvp / libmdk、media_kit / mpv、media3 / ExoPlayer + FFmpeg(Android)、canvas_danmaku。
|
||||||
|
|
||||||
|
## 播放引擎
|
||||||
|
|
||||||
|
播放器内置三个可切换的解码 / 渲染引擎,可在"设置 → 播放设置 → 播放引擎"中选择:
|
||||||
|
|
||||||
|
| 引擎 | 适用平台 | 解码路径 | 说明 |
|
||||||
|
|------|----------|----------|------|
|
||||||
|
| fvp (libmdk) | Windows / macOS / Android | 平台硬解 + libmdk 软解兜底 | `auto` 模式的主引擎;杜比视界 / ProRes 优先走它 |
|
||||||
|
| mpv (media_kit) | Windows / macOS / Android | mpv / libmpv | 兼容性次引擎;桌面 `auto` 模式备选 |
|
||||||
|
| media3 (ExoPlayer + FFmpeg) | Android only | MediaCodec 硬解视频 + FFmpeg 软解音频 | 专为补 Android 高规格音轨(DTS / TrueHD / EAC3);需自编译 AAR,见 `plugins/media3_engine/README.md` 与 `scripts/build_media3_ffmpeg.sh` |
|
||||||
|
|
||||||
|
`auto` 模式在 Android 上默认 fvp、桌面默认 mpv;遇 DV / ProRes 强制 fvp。media3 仅在用户手动选中时启用,且桌面平台不会展示该选项(运行时也有路由层守卫降级到 mpv)。
|
||||||
|
|
||||||
|
## 下载
|
||||||
|
|
||||||
|
前往 GitHub Releases 页面下载发布版本(需自行配置仓库地址)。
|
||||||
|
|
||||||
|
当前自动发布流程主要提供 Windows x64 安装包。macOS 和 Android 可按下方开发说明自行构建。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
- `lib/core` - 合约、领域用例、仓储端口、存储和 Emby API 适配器。
|
||||||
|
- `lib/features` - 首页、媒体库、详情、播放、搜索、收藏、设置等功能页面。
|
||||||
|
- `lib/providers` - Riverpod 状态和依赖注入。
|
||||||
|
- `lib/shared` - 可复用组件、主题和映射工具。
|
||||||
|
- `macos`, `windows` - Flutter 桌面平台外壳。
|
||||||
|
- `android` - Flutter Android 平台外壳。
|
||||||
|
- `installer/`, `scripts/` - Windows Inno Setup 脚本和开发 / 发布辅助脚本。
|
||||||
|
- `test` - Flutter 测试。
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
### Script entry (`package.json`, npm-style)
|
||||||
|
|
||||||
|
If you prefer `npm run <task>` over remembering each Flutter/PowerShell invocation, a thin
|
||||||
|
`package.json` maps familiar names onto the existing scripts. It is `private` (no publish,
|
||||||
|
no `node_modules` needed) — npm is only used as a task runner; the single source of truth
|
||||||
|
stays in `scripts/*.ps1`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev # -> scripts/dev.ps1 run (Windows smart run)
|
||||||
|
npm run dev:macos # -> flutter run -d macos
|
||||||
|
npm run gen # -> build_runner build --delete-conflicting-outputs
|
||||||
|
npm run watch # -> build_runner watch --delete-conflicting-outputs
|
||||||
|
npm run build:windows # -> flutter build windows --release
|
||||||
|
npm run build:macos # -> flutter build macos --release
|
||||||
|
npm run build:apk # -> flutter build apk --release
|
||||||
|
npm run test # -> flutter test
|
||||||
|
npm run analyze # -> flutter analyze
|
||||||
|
npm run clean # -> flutter clean
|
||||||
|
npm run pub # -> flutter pub get
|
||||||
|
npm run doctor # -> flutter doctor -v
|
||||||
|
npm run installer # -> scripts/dev.ps1 installer (Windows .exe)
|
||||||
|
npm run dmg # -> scripts/build_dmg.sh (macOS .dmg)
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass-through args use the npm `--` separator; everything after it is forwarded to the
|
||||||
|
underlying script. To force `pub get` and run in release mode:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev -- -Force -FlutterArgs --release # -> scripts/dev.ps1 run -Force -FlutterArgs --release
|
||||||
|
```
|
||||||
|
|
||||||
|
`dev`/`installer` shell out to PowerShell (Windows); `dmg` needs bash (macOS). The rest are
|
||||||
|
plain Flutter CLI calls and work on any platform.
|
||||||
|
|
||||||
|
### Windows — unified entry (`scripts/dev.ps1`)
|
||||||
|
|
||||||
|
One-click subcommand dispatcher. Delegates to `dev-run.ps1` / `build-installer.ps1` and wraps common Flutter tasks. On Windows, `run` prepares the project-pinned fvp/libmdk SDK and clears `FVP_DEPS_LATEST`, so local development never silently switches to a nightly dependency.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Smart dev run (skips `pub get` when unchanged)
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 run
|
||||||
|
|
||||||
|
# Force a clean re-download of the project-pinned fvp/mdk-sdk:
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 run -RefreshFvpDeps
|
||||||
|
|
||||||
|
# Build .exe installer (Inno Setup 6 required, see below)
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 installer
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 installer -SkipFlutterBuild
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 installer -Version 1.0.1
|
||||||
|
|
||||||
|
# Other shortcuts
|
||||||
|
scripts\dev.ps1 build # flutter build windows --release
|
||||||
|
scripts\dev.ps1 gen # dart run build_runner build --delete-conflicting-outputs
|
||||||
|
scripts\dev.ps1 clean # flutter clean
|
||||||
|
scripts\dev.ps1 pub # flutter pub get
|
||||||
|
scripts\dev.ps1 analyze # flutter analyze
|
||||||
|
scripts\dev.ps1 doctor # flutter doctor -v
|
||||||
|
scripts\dev.ps1 help # full subcommand list
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass-through args:
|
||||||
|
- `dev.ps1 run -Force -FlutterArgs --release` → forces `pub get` and runs Flutter in release mode.
|
||||||
|
- `dev.ps1 run -Device macos` → choose a different target.
|
||||||
|
- `dev.ps1 run -RefreshFvpDeps` → deletes the cached SDK/archive and re-downloads the project-pinned fvp/MDK version.
|
||||||
|
|
||||||
|
Avoid using this for daily Windows development:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:FVP_DEPS_LATEST=1; flutter run -d windows
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `scripts\dev.ps1 run` instead; it also skips `pub get` when dependencies are unchanged.
|
||||||
|
|
||||||
|
### macOS / Android
|
||||||
|
|
||||||
|
Use the Flutter CLI directly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
flutter pub get
|
||||||
|
flutter run -d macos # macOS
|
||||||
|
npm run dev:android # Android (auto-detect emulator or device)
|
||||||
|
flutter build macos # macOS bundle
|
||||||
|
npm run build:apk # Android APK with project-pinned MDK
|
||||||
|
./scripts/build_dmg.sh # macOS .dmg packaging
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Android 构建前置 — Media3 FFmpeg decoder AAR**:Android 端的 media3 引擎依赖
|
||||||
|
> 自编译的 `media3-decoder-ffmpeg-1.10.1-arm64.aar`。仓库默认不提交该二进制,
|
||||||
|
> 因此首次构建 Android 之前需要运行 `scripts/build_media3_ffmpeg.sh` 生成产物
|
||||||
|
> (NDK r26b、~30–60 分钟)。AAR 缺失时 plugin 的 `preBuild` 会 fail-fast 抛出
|
||||||
|
> 包含恢复指令的错误。仅构建 Windows / macOS 桌面端时无需此步骤。详见
|
||||||
|
> `plugins/media3_engine/README.md`。
|
||||||
|
|
||||||
|
## Windows Installer (`.exe`)
|
||||||
|
|
||||||
|
Produces `build/installer/smplayer-setup-<version>-x64.exe` via Inno Setup 6.
|
||||||
|
|
||||||
|
**Prerequisite (one-time)** — install Inno Setup:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
winget install -e --id JRSoftware.InnoSetup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
scripts\dev.ps1 installer # full pipeline (flutter build + ISCC)
|
||||||
|
scripts\dev.ps1 installer -SkipFlutterBuild # reuse existing build/ artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
Layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
installer/
|
||||||
|
├── sm_emby.iss Inno Setup script (DV/HDR engine DLLs + flutter_assets)
|
||||||
|
└── lang/ChineseSimplified.isl Bundled language file (avoids relying on system ISL)
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer is **unsigned** — first launch triggers Windows SmartScreen ("More info → Run anyway"). Add `SignTool=` to `sm_emby.iss` once a `.pfx` certificate is available.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
flutter analyze
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code generation
|
||||||
|
|
||||||
|
`@freezed` / `@JsonSerializable` / `@RestApi` annotations require regenerating part files after changes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
# or
|
||||||
|
scripts\dev.ps1 gen
|
||||||
|
```
|
||||||
|
|
||||||
|
Generated `*.g.dart` / `*.freezed.dart` files **must be committed** (marked `linguist-generated=true` so GitHub diffs collapse them).
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
prefer_const_constructors: true
|
||||||
|
prefer_const_declarations: true
|
||||||
|
prefer_final_fields: true
|
||||||
|
prefer_final_locals: true
|
||||||
|
avoid_print: true
|
||||||
|
prefer_single_quotes: true
|
||||||
|
sort_child_properties_last: true
|
||||||
|
use_key_in_widget_constructors: true
|
||||||
|
sized_box_for_whitespace: true
|
||||||
|
prefer_is_empty: true
|
||||||
|
prefer_is_not_empty: true
|
||||||
|
unnecessary_lambdas: true
|
||||||
|
always_declare_return_types: true
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
# Generated build output can contain partial package snapshots that are not
|
||||||
|
# valid standalone Dart packages and must not participate in app analysis.
|
||||||
|
- build/**
|
||||||
|
# test_link/ is a third-party Flutter example project (media_kit/fvp), not part of sm-emby.
|
||||||
|
- test_link/**
|
||||||
|
errors:
|
||||||
|
invalid_annotation_target: ignore
|
||||||
|
# Emby DTO fields intentionally mirror upstream JSON casing.
|
||||||
|
non_constant_identifier_names: ignore
|
||||||
|
# Current stable Flutter still accepts these APIs; migrate during the next UI pass.
|
||||||
|
deprecated_member_use: ignore
|
||||||
|
# Keep collection conditionals compatible with the existing code style.
|
||||||
|
use_null_aware_elements: ignore
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import java.util.Properties
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
val localProperties = Properties().apply {
|
||||||
|
val localPropertiesFile = rootProject.file("local.properties")
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.inputStream().use { load(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val developmentVersionCode = System.getenv("SMPLAYER_ANDROID_VERSION_CODE")?.let { rawVersionCode ->
|
||||||
|
rawVersionCode.toIntOrNull()?.takeIf { versionCode -> versionCode > 0 }
|
||||||
|
?: error("SMPLAYER_ANDROID_VERSION_CODE must be a positive integer")
|
||||||
|
}
|
||||||
|
val flutterVersionCode = developmentVersionCode
|
||||||
|
?: localProperties.getProperty("flutter.versionCode")?.toIntOrNull()
|
||||||
|
?: 1
|
||||||
|
val flutterVersionName = localProperties.getProperty("flutter.versionName") ?: "1.0.0"
|
||||||
|
|
||||||
|
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||||
|
val keystoreProperties = Properties().apply {
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystorePropertiesFile.inputStream().use { load(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.smplayer.smplayer"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.smplayer.smplayer"
|
||||||
|
// fvp(libmdk) 与 media_kit(libmpv) 在 Android 上的硬解基线。
|
||||||
|
minSdk = maxOf(flutter.minSdkVersion, 24)
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutterVersionCode
|
||||||
|
versionName = flutterVersionName
|
||||||
|
// ABI 限制策略:见下方 buildTypes。defaultConfig 不收缩,保留多 ABI 让
|
||||||
|
// debug 在 x86_64 emulator 上仍能加载 fvp libmdk.so / media_kit libmpv.so;
|
||||||
|
// release 包仅 arm64-v8a 以匹配 media3 FFmpeg AAR 并控制包体。
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file(keystoreProperties["storeFile"] as String)
|
||||||
|
storePassword = keystoreProperties["storePassword"] as String
|
||||||
|
keyAlias = keystoreProperties["keyAlias"] as String
|
||||||
|
keyPassword = keystoreProperties["keyPassword"] as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地 debug 在没有 key.properties 时使用 Android debug 签名;release 构建
|
||||||
|
// 绝不回退到临时 debug 证书,避免发布无法覆盖安装的 APK。
|
||||||
|
val releaseSigningConfig = if (keystorePropertiesFile.exists()) {
|
||||||
|
signingConfigs.getByName("release")
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix = ".dev"
|
||||||
|
manifestPlaceholders["appLabel"] = "smplayer-dev"
|
||||||
|
signingConfig = releaseSigningConfig ?: signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
manifestPlaceholders["appLabel"] = "smplayer"
|
||||||
|
if (releaseSigningConfig != null) {
|
||||||
|
signingConfig = releaseSigningConfig
|
||||||
|
}
|
||||||
|
ndk {
|
||||||
|
// 仅 release 收缩到 arm64-v8a:与 media3_engine plugin 自编译的
|
||||||
|
// FFmpeg decoder AAR 对齐(其只含 arm64-v8a),包体最小化。
|
||||||
|
// debug 不限制 → x86_64 / arm64 emulator 上 fvp / media_kit 仍可运行。
|
||||||
|
abiFilters += listOf("arm64-v8a")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
|
|
||||||
|
// media3_engine plugin 把 media3-decoder-ffmpeg-*.aar 标为 compileOnly(AGP 8
|
||||||
|
// 禁止 library module 直接 implementation 本地 .aar)。在 app 这一层把所有本地
|
||||||
|
// .aar 实际打入 APK;目前只有 media3 FFmpeg decoder AAR,使用 include 通配避免
|
||||||
|
// 后续新增 AAR 时再改这里。
|
||||||
|
dependencies {
|
||||||
|
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Media3 FFmpeg decoder AAR 校验清单。
|
||||||
|
#
|
||||||
|
# 格式:<SHA-256> <文件名>
|
||||||
|
#
|
||||||
|
# 由 scripts/build_media3_ffmpeg.sh 产出 AAR 后维护此文件(自动覆写对应文件名条目)。
|
||||||
|
# Gradle 任务 `verifyMedia3FfmpegAar` 在 preBuild 前会读取此清单 + 计算 AAR 实际
|
||||||
|
# SHA-256,不一致即 fail-fast(见 plugins/media3_engine/android/build.gradle.kts)。
|
||||||
|
#
|
||||||
|
# 升级版本时同时更新下方文件名(含 media3 tag)与 SHA-256。
|
||||||
|
|
||||||
|
8e4979b0b9f1408314b149a9740310034238cb80f55167f388f5dbc310e8229e media3-decoder-ffmpeg-1.10.1-arm64.aar
|
||||||
Binary file not shown.
@@ -0,0 +1,70 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
<application
|
||||||
|
android:label="${appLabel}"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:supportsPictureInPicture="true"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Trakt OAuth deep link callback: smplayer://oauth-trakt -->
|
||||||
|
<intent-filter android:autoVerify="false">
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<data android:scheme="smplayer" android:host="oauth-trakt"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
|
android:value="true" />
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_provider_paths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.smplayer.smplayer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Base64
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.io.File
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
|
||||||
|
class AndroidTlsBridge(
|
||||||
|
private val context: Context,
|
||||||
|
flutterEngine: FlutterEngine,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val CHANNEL_NAME = "smplayer/android_tls"
|
||||||
|
private const val CA_BUNDLE_FILE_NAME = "android-trusted-ca-bundle.pem"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
private val worker = Executors.newSingleThreadExecutor()
|
||||||
|
private val methodChannel = MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger,
|
||||||
|
CHANNEL_NAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var disposed = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
methodChannel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"exportTrustedCaBundle" -> worker.execute {
|
||||||
|
try {
|
||||||
|
val bundlePath = exportTrustedCaBundle()
|
||||||
|
postResult { result.success(bundlePath) }
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
postResult {
|
||||||
|
result.error(
|
||||||
|
"CA_EXPORT_FAILED",
|
||||||
|
exception.message ?: "Failed to export Android trusted CAs",
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportTrustedCaBundle(): String {
|
||||||
|
val trustStore = KeyStore.getInstance("AndroidCAStore").apply {
|
||||||
|
load(null)
|
||||||
|
}
|
||||||
|
val encodedCertificates = linkedSetOf<String>()
|
||||||
|
val aliases = trustStore.aliases().toList().sorted()
|
||||||
|
|
||||||
|
for (alias in aliases) {
|
||||||
|
val certificate = trustStore.getCertificate(alias) as? X509Certificate
|
||||||
|
?: continue
|
||||||
|
encodedCertificates += Base64.encodeToString(
|
||||||
|
certificate.encoded,
|
||||||
|
Base64.NO_WRAP,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
check(encodedCertificates.isNotEmpty()) {
|
||||||
|
"AndroidCAStore contained no X.509 trust anchors"
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputFile = File(context.cacheDir, CA_BUNDLE_FILE_NAME)
|
||||||
|
val temporaryFile = File(context.cacheDir, "$CA_BUNDLE_FILE_NAME.tmp")
|
||||||
|
temporaryFile.bufferedWriter(Charsets.US_ASCII).use { writer ->
|
||||||
|
for (encodedCertificate in encodedCertificates) {
|
||||||
|
writer.appendLine("-----BEGIN CERTIFICATE-----")
|
||||||
|
encodedCertificate.chunked(64).forEach(writer::appendLine)
|
||||||
|
writer.appendLine("-----END CERTIFICATE-----")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputFile.exists() && !outputFile.delete()) {
|
||||||
|
throw IllegalStateException("Could not replace cached CA bundle")
|
||||||
|
}
|
||||||
|
if (!temporaryFile.renameTo(outputFile)) {
|
||||||
|
temporaryFile.copyTo(outputFile, overwrite = true)
|
||||||
|
temporaryFile.delete()
|
||||||
|
}
|
||||||
|
return outputFile.absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postResult(callback: () -> Unit) {
|
||||||
|
if (disposed) return
|
||||||
|
mainHandler.post {
|
||||||
|
if (!disposed) callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
disposed = true
|
||||||
|
methodChannel.setMethodCallHandler(null)
|
||||||
|
worker.shutdownNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.smplayer.smplayer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
class ApkInstallerBridge(
|
||||||
|
private val context: Context,
|
||||||
|
flutterEngine: FlutterEngine,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val CHANNEL_NAME = "smplayer/apk_installer"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val methodChannel = MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
methodChannel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"installApk" -> {
|
||||||
|
val filePath = call.argument<String>("path")
|
||||||
|
if (filePath.isNullOrEmpty()) {
|
||||||
|
result.error("INVALID_PATH", "APK file path is null or empty", null)
|
||||||
|
return@setMethodCallHandler
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
launchApkInstall(filePath)
|
||||||
|
result.success(true)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
result.error(
|
||||||
|
"INSTALL_FAILED",
|
||||||
|
exception.message ?: "Failed to launch APK installer",
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"canRequestInstall" -> {
|
||||||
|
val canRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.packageManager.canRequestPackageInstalls()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
result.success(canRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchApkInstall(filePath: String) {
|
||||||
|
val apkFile = File(filePath)
|
||||||
|
if (!apkFile.exists()) {
|
||||||
|
throw IllegalArgumentException("APK file does not exist: $filePath")
|
||||||
|
}
|
||||||
|
|
||||||
|
val authority = "${context.packageName}.fileprovider"
|
||||||
|
val contentUri = FileProvider.getUriForFile(context, authority, apkFile)
|
||||||
|
|
||||||
|
val installIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
setDataAndType(contentUri, "application/vnd.android.package-archive")
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivity(installIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
methodChannel.setMethodCallHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.smplayer.smplayer
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity() {
|
||||||
|
private var systemAudio: SystemAudioBridge? = null
|
||||||
|
private var pipBridge: PipBridge? = null
|
||||||
|
private var apkInstaller: ApkInstallerBridge? = null
|
||||||
|
private var androidTls: AndroidTlsBridge? = null
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
systemAudio = SystemAudioBridge(applicationContext, flutterEngine)
|
||||||
|
pipBridge = PipBridge(this, flutterEngine)
|
||||||
|
apkInstaller = ApkInstallerBridge(applicationContext, flutterEngine)
|
||||||
|
androidTls = AndroidTlsBridge(applicationContext, flutterEngine)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
volumeControlStream = AudioManager.STREAM_MUSIC
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
val bridge = systemAudio
|
||||||
|
if (bridge != null && bridge.keyInterceptEnabled) {
|
||||||
|
val isVolumeUp = event.keyCode == KeyEvent.KEYCODE_VOLUME_UP
|
||||||
|
val isVolumeDown = event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|
||||||
|
if (isVolumeUp || isVolumeDown) {
|
||||||
|
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||||
|
bridge.adjustVolume(
|
||||||
|
if (isVolumeUp) 1 else -1,
|
||||||
|
showSystemUi = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.dispatchKeyEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserLeaveHint() {
|
||||||
|
super.onUserLeaveHint()
|
||||||
|
pipBridge?.onUserLeaveHint()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPictureInPictureModeChanged(
|
||||||
|
isInPictureInPictureMode: Boolean,
|
||||||
|
newConfig: Configuration,
|
||||||
|
) {
|
||||||
|
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||||
|
pipBridge?.onPipModeChanged(isInPictureInPictureMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
pipBridge?.dispose()
|
||||||
|
pipBridge = null
|
||||||
|
apkInstaller?.dispose()
|
||||||
|
apkInstaller = null
|
||||||
|
androidTls?.dispose()
|
||||||
|
androidTls = null
|
||||||
|
systemAudio?.dispose()
|
||||||
|
systemAudio = null
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
package com.smplayer.smplayer
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.PictureInPictureParams
|
||||||
|
import android.app.RemoteAction
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Rational
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
class PipBridge(
|
||||||
|
private val activity: Activity,
|
||||||
|
flutterEngine: FlutterEngine,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val METHOD_CHANNEL = "smplayer/pip"
|
||||||
|
private const val EVENT_CHANNEL = "smplayer/pip_events"
|
||||||
|
|
||||||
|
private const val ACTION_PLAY = "com.smplayer.pip.PLAY"
|
||||||
|
private const val ACTION_PAUSE = "com.smplayer.pip.PAUSE"
|
||||||
|
private const val ACTION_NEXT = "com.smplayer.pip.NEXT"
|
||||||
|
private const val ACTION_PREV = "com.smplayer.pip.PREV"
|
||||||
|
private const val ACTION_SEEK_FWD = "com.smplayer.pip.SEEK_FWD"
|
||||||
|
private const val ACTION_SEEK_BWD = "com.smplayer.pip.SEEK_BWD"
|
||||||
|
|
||||||
|
private const val REQUEST_PLAY = 1
|
||||||
|
private const val REQUEST_PAUSE = 2
|
||||||
|
private const val REQUEST_NEXT = 3
|
||||||
|
private const val REQUEST_PREV = 4
|
||||||
|
private const val REQUEST_SEEK_FWD = 5
|
||||||
|
private const val REQUEST_SEEK_BWD = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
private val methodChannel = MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL
|
||||||
|
)
|
||||||
|
private val eventChannel = EventChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL
|
||||||
|
)
|
||||||
|
|
||||||
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
private var autoEnterOnLeave = false
|
||||||
|
private var aspectX = 16
|
||||||
|
private var aspectY = 9
|
||||||
|
private var isPlaying = false
|
||||||
|
private var hasNext = false
|
||||||
|
private var hasPrev = false
|
||||||
|
private var useEpisodeActions = true
|
||||||
|
|
||||||
|
private val actionReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
val action = intent?.action ?: return
|
||||||
|
val dartAction = when (action) {
|
||||||
|
ACTION_PLAY -> "play"
|
||||||
|
ACTION_PAUSE -> "pause"
|
||||||
|
ACTION_NEXT -> "next"
|
||||||
|
ACTION_PREV -> "prev"
|
||||||
|
ACTION_SEEK_FWD -> "seekForward"
|
||||||
|
ACTION_SEEK_BWD -> "seekBackward"
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
mainHandler.post {
|
||||||
|
eventSink?.success(mapOf("type" to "action", "action" to dartAction))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var receiverRegistered = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
methodChannel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"isSupported" -> {
|
||||||
|
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||||
|
}
|
||||||
|
|
||||||
|
"isInPipMode" -> {
|
||||||
|
result.success(
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||||
|
activity.isInPictureInPictureMode
|
||||||
|
else false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"configure" -> {
|
||||||
|
val args = call.arguments as? Map<*, *>
|
||||||
|
if (args != null) {
|
||||||
|
aspectX = (args["aspectX"] as? Number)?.toInt() ?: 16
|
||||||
|
aspectY = (args["aspectY"] as? Number)?.toInt() ?: 9
|
||||||
|
autoEnterOnLeave = args["autoEnterOnLeave"] as? Boolean ?: false
|
||||||
|
isPlaying = args["isPlaying"] as? Boolean ?: false
|
||||||
|
hasNext = args["hasNext"] as? Boolean ?: false
|
||||||
|
hasPrev = args["hasPrev"] as? Boolean ?: false
|
||||||
|
useEpisodeActions = args["useEpisodeActions"] as? Boolean ?: true
|
||||||
|
updatePipParams()
|
||||||
|
}
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"enterPip" -> {
|
||||||
|
val args = call.arguments as? Map<*, *>
|
||||||
|
if (args != null) {
|
||||||
|
aspectX = (args["aspectX"] as? Number)?.toInt() ?: aspectX
|
||||||
|
aspectY = (args["aspectY"] as? Number)?.toInt() ?: aspectY
|
||||||
|
}
|
||||||
|
result.success(enterPip())
|
||||||
|
}
|
||||||
|
|
||||||
|
"updatePlaybackState" -> {
|
||||||
|
val args = call.arguments as? Map<*, *>
|
||||||
|
if (args != null) {
|
||||||
|
isPlaying = args["isPlaying"] as? Boolean ?: isPlaying
|
||||||
|
hasNext = args["hasNext"] as? Boolean ?: hasNext
|
||||||
|
hasPrev = args["hasPrev"] as? Boolean ?: hasPrev
|
||||||
|
useEpisodeActions = args["useEpisodeActions"] as? Boolean ?: useEpisodeActions
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||||
|
activity.isInPictureInPictureMode
|
||||||
|
) {
|
||||||
|
updatePipParams()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) {
|
||||||
|
eventSink = sink
|
||||||
|
registerActionReceiver()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
eventSink = null
|
||||||
|
unregisterActionReceiver()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerActionReceiver() {
|
||||||
|
if (receiverRegistered) return
|
||||||
|
val filter = IntentFilter().apply {
|
||||||
|
addAction(ACTION_PLAY)
|
||||||
|
addAction(ACTION_PAUSE)
|
||||||
|
addAction(ACTION_NEXT)
|
||||||
|
addAction(ACTION_PREV)
|
||||||
|
addAction(ACTION_SEEK_FWD)
|
||||||
|
addAction(ACTION_SEEK_BWD)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
activity.registerReceiver(actionReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
} else {
|
||||||
|
activity.registerReceiver(actionReceiver, filter)
|
||||||
|
}
|
||||||
|
receiverRegistered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterActionReceiver() {
|
||||||
|
if (!receiverRegistered) return
|
||||||
|
try {
|
||||||
|
activity.unregisterReceiver(actionReceiver)
|
||||||
|
} catch (_: IllegalArgumentException) {}
|
||||||
|
receiverRegistered = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRemoteActions(): List<RemoteAction> {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return emptyList()
|
||||||
|
|
||||||
|
val actions = mutableListOf<RemoteAction>()
|
||||||
|
|
||||||
|
if (useEpisodeActions && (hasNext || hasPrev)) {
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
makeAction(
|
||||||
|
android.R.drawable.ic_media_previous,
|
||||||
|
"上一集",
|
||||||
|
ACTION_PREV,
|
||||||
|
REQUEST_PREV,
|
||||||
|
enabled = hasPrev,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actions.add(
|
||||||
|
if (isPlaying) makeAction(
|
||||||
|
android.R.drawable.ic_media_pause,
|
||||||
|
"暂停",
|
||||||
|
ACTION_PAUSE,
|
||||||
|
REQUEST_PAUSE,
|
||||||
|
) else makeAction(
|
||||||
|
android.R.drawable.ic_media_play,
|
||||||
|
"播放",
|
||||||
|
ACTION_PLAY,
|
||||||
|
REQUEST_PLAY,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actions.add(
|
||||||
|
makeAction(
|
||||||
|
android.R.drawable.ic_media_next,
|
||||||
|
"下一集",
|
||||||
|
ACTION_NEXT,
|
||||||
|
REQUEST_NEXT,
|
||||||
|
enabled = hasNext,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
makeAction(
|
||||||
|
android.R.drawable.ic_media_rew,
|
||||||
|
"快退",
|
||||||
|
ACTION_SEEK_BWD,
|
||||||
|
REQUEST_SEEK_BWD,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actions.add(
|
||||||
|
if (isPlaying) makeAction(
|
||||||
|
android.R.drawable.ic_media_pause,
|
||||||
|
"暂停",
|
||||||
|
ACTION_PAUSE,
|
||||||
|
REQUEST_PAUSE,
|
||||||
|
) else makeAction(
|
||||||
|
android.R.drawable.ic_media_play,
|
||||||
|
"播放",
|
||||||
|
ACTION_PLAY,
|
||||||
|
REQUEST_PLAY,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actions.add(
|
||||||
|
makeAction(
|
||||||
|
android.R.drawable.ic_media_ff,
|
||||||
|
"快进",
|
||||||
|
ACTION_SEEK_FWD,
|
||||||
|
REQUEST_SEEK_FWD,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeAction(
|
||||||
|
iconRes: Int,
|
||||||
|
title: String,
|
||||||
|
action: String,
|
||||||
|
requestCode: Int,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
): RemoteAction {
|
||||||
|
val intent = Intent(action).setPackage(activity.packageName)
|
||||||
|
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(activity, requestCode, intent, flags)
|
||||||
|
val icon = Icon.createWithResource(activity, iconRes)
|
||||||
|
return RemoteAction(icon, title, title, pendingIntent).apply {
|
||||||
|
isEnabled = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildPipParams(): PictureInPictureParams? {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null
|
||||||
|
|
||||||
|
val builder = PictureInPictureParams.Builder()
|
||||||
|
.setAspectRatio(Rational(aspectX, aspectY))
|
||||||
|
.setActions(buildRemoteActions())
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
builder.setAutoEnterEnabled(autoEnterOnLeave && isPlaying)
|
||||||
|
builder.setSeamlessResizeEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePipParams() {
|
||||||
|
val params = buildPipParams() ?: return
|
||||||
|
try {
|
||||||
|
activity.setPictureInPictureParams(params)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enterPip(): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
|
||||||
|
val params = buildPipParams() ?: return false
|
||||||
|
return try {
|
||||||
|
activity.enterPictureInPictureMode(params)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun onUserLeaveHint() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) return
|
||||||
|
if (!autoEnterOnLeave || !isPlaying) return
|
||||||
|
enterPip()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun onPipModeChanged(isInPip: Boolean) {
|
||||||
|
mainHandler.post {
|
||||||
|
eventSink?.success(mapOf("type" to "modeChanged", "isInPip" to isInPip))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
unregisterActionReceiver()
|
||||||
|
methodChannel.setMethodCallHandler(null)
|
||||||
|
eventChannel.setStreamHandler(null)
|
||||||
|
eventSink = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.smplayer.smplayer
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
class SystemAudioBridge(
|
||||||
|
private val context: Context,
|
||||||
|
flutterEngine: FlutterEngine,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val METHOD_CHANNEL = "smplayer/system_audio"
|
||||||
|
private const val EVENT_CHANNEL = "smplayer/system_audio_events"
|
||||||
|
private const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"
|
||||||
|
private const val STREAM_TYPE = AudioManager.STREAM_MUSIC
|
||||||
|
}
|
||||||
|
|
||||||
|
private val audioManager: AudioManager =
|
||||||
|
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
|
||||||
|
private val methodChannel = MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL
|
||||||
|
)
|
||||||
|
private val eventChannel = EventChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL
|
||||||
|
)
|
||||||
|
|
||||||
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var keyInterceptEnabled: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val volumeReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||||
|
if (intent?.action == VOLUME_CHANGED_ACTION) {
|
||||||
|
val streamType = intent.getIntExtra(
|
||||||
|
"android.media.EXTRA_VOLUME_STREAM_TYPE", -1
|
||||||
|
)
|
||||||
|
if (streamType == STREAM_TYPE) {
|
||||||
|
postVolumeEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
methodChannel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"getVolume" -> result.success(currentVolumeMap())
|
||||||
|
"adjustVolume" -> {
|
||||||
|
val delta = (call.argument<Int>("delta") ?: 0)
|
||||||
|
adjustVolume(delta, showSystemUi = false)
|
||||||
|
result.success(currentVolumeMap())
|
||||||
|
}
|
||||||
|
"setKeyIntercept" -> {
|
||||||
|
keyInterceptEnabled = call.argument<Boolean>("enabled") ?: false
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) {
|
||||||
|
eventSink = sink
|
||||||
|
context.registerReceiver(
|
||||||
|
volumeReceiver, IntentFilter(VOLUME_CHANGED_ACTION)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
try {
|
||||||
|
context.unregisterReceiver(volumeReceiver)
|
||||||
|
} catch (_: IllegalArgumentException) {
|
||||||
|
}
|
||||||
|
eventSink = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun adjustVolume(delta: Int, showSystemUi: Boolean) {
|
||||||
|
val direction = when {
|
||||||
|
delta > 0 -> AudioManager.ADJUST_RAISE
|
||||||
|
delta < 0 -> AudioManager.ADJUST_LOWER
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
val flag = if (showSystemUi) AudioManager.FLAG_SHOW_UI else 0
|
||||||
|
repeat(kotlin.math.abs(delta).coerceAtMost(20)) {
|
||||||
|
audioManager.adjustStreamVolume(STREAM_TYPE, direction, flag)
|
||||||
|
}
|
||||||
|
postVolumeEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun currentVolumeMap(): Map<String, Int> = mapOf(
|
||||||
|
"current" to audioManager.getStreamVolume(STREAM_TYPE),
|
||||||
|
"max" to audioManager.getStreamMaxVolume(STREAM_TYPE),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun postVolumeEvent() {
|
||||||
|
val payload = currentVolumeMap()
|
||||||
|
mainHandler.post {
|
||||||
|
eventSink?.success(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
try {
|
||||||
|
context.unregisterReceiver(volumeReceiver)
|
||||||
|
} catch (_: IllegalArgumentException) {
|
||||||
|
}
|
||||||
|
methodChannel.setMethodCallHandler(null)
|
||||||
|
eventChannel.setStreamHandler(null)
|
||||||
|
eventSink = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<!-- UpdateService 下载 APK 至 getTemporaryDirectory() 下的 smplayer-update/ -->
|
||||||
|
<cache-path name="update_apk" path="smplayer-update/" />
|
||||||
|
</paths>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:ReservedCodeCacheSize=256m
|
||||||
|
# 让 Gradle 直连、不读 macOS 系统代理,避免 Reqable/Charles 等抓包代理残留导致依赖下载挂起或连接被拒
|
||||||
|
systemProp.java.net.useSystemProxies=false
|
||||||
|
android.useAndroidX=true
|
||||||
|
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||||
|
android.builtInKotlin=false
|
||||||
|
# This newDsl flag was added automatically by Flutter migrator
|
||||||
|
android.newDsl=false
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# 本地 release 签名配置。
|
||||||
|
# 复制为 key.properties 并填入你的 keystore 信息。
|
||||||
|
# key.properties 已被 .gitignore 忽略,不会提交到仓库。
|
||||||
|
#
|
||||||
|
# 生成 keystore:
|
||||||
|
# keytool -genkey -v -keystore release.keystore -alias smplayer \
|
||||||
|
# -keyalg RSA -keysize 2048 -validity 10000
|
||||||
|
#
|
||||||
|
# storeFile 路径相对于 android/app/ 目录解析。
|
||||||
|
|
||||||
|
storeFile=release.keystore
|
||||||
|
storePassword=
|
||||||
|
keyAlias=smplayer
|
||||||
|
keyPassword=
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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")
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.2 MiB |
@@ -0,0 +1,29 @@
|
|||||||
|
# assets/js
|
||||||
|
|
||||||
|
## cheerio.min.js
|
||||||
|
|
||||||
|
打包好的 [cheerio](https://github.com/cheeriojs/cheerio)(含 parse5 解析器),注入到
|
||||||
|
fjs(QuickJS)脚本组件运行环境,作为 `Widget.html.load` / `Widget.dom.*` 的实现。
|
||||||
|
上游 ForwardWidgets 宿主的约定就是 "Widget has built-in cheerio",脚本以同步
|
||||||
|
cheerio API 消费 DOM——fjs 的 Dart 桥(`fjs.bridge_call`)只有异步 Promise 形态,
|
||||||
|
因此 DOM 解析必须完整运行在 JS 侧,不能走宿主桥。
|
||||||
|
|
||||||
|
全局导出名:`__fjsCheerio`(`__fjsCheerio.load(html)`)。bundle 头部带一个 `atob`
|
||||||
|
polyfill(`entities` 包解码实体表需要,QuickJS 没有内置 atob/Buffer)。
|
||||||
|
|
||||||
|
### 重新构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir /tmp/cheerio-bundle && cd /tmp/cheerio-bundle
|
||||||
|
npm install cheerio@1.1.2 esbuild
|
||||||
|
echo "export { load } from 'cheerio';" > entry.js
|
||||||
|
npx esbuild entry.js --bundle --format=iife --global-name=__fjsCheerio \
|
||||||
|
--platform=browser --minify --banner:js="$(cat banner.js)" \
|
||||||
|
--outfile=cheerio.min.js
|
||||||
|
```
|
||||||
|
|
||||||
|
`banner.js` 为 atob polyfill,内容见当前 bundle 文件头部(第 1 段
|
||||||
|
`if (typeof globalThis.atob === 'undefined')`)。`--platform=browser` 会选择
|
||||||
|
cheerio 的 browser 入口(只含 load/parse,不含依赖 Buffer/undici 的
|
||||||
|
loadBuffer/fromURL)。构建后用一个无 Node 全局的裸 `vm` 沙箱冒烟验证
|
||||||
|
`load/text/attr/closest/Array.from` 再提交。
|
||||||
Vendored
+20
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
|
|||||||
|
# fjs(脚本组件 JS 引擎)的原生 Rust 库 libfjs 经 cargokit 构建。
|
||||||
|
# 本机装有 rustup 时 cargokit 默认从源码编译——但 libfjs 的依赖链要求较新的
|
||||||
|
# rustc(1.88 已不满足)。这里显式改用 fjs 官方 GitHub Release 的签名预编译
|
||||||
|
# 产物(公钥见 fjs 包内 libfjs/cargokit.yaml),构建应用无需 Rust 工具链。
|
||||||
|
use_precompiled_binaries: true
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
; *** Inno Setup version 6.5.0+ Chinese Simplified messages ***
|
||||||
|
;
|
||||||
|
; To download user-contributed translations of this file, go to:
|
||||||
|
; https://jrsoftware.org/files/istrans/
|
||||||
|
;
|
||||||
|
; Note: When translating this text, do not add periods (.) to the end of
|
||||||
|
; messages that didn't have them already, because on those messages Inno
|
||||||
|
; Setup adds the periods automatically (appending a period would result in
|
||||||
|
; two periods being displayed).
|
||||||
|
;
|
||||||
|
; Maintained by Zhenghan Yang
|
||||||
|
; Email: 847320916@QQ.com
|
||||||
|
; Translation based on network resource
|
||||||
|
; The latest Translation is on https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
|
||||||
|
;
|
||||||
|
|
||||||
|
[LangOptions]
|
||||||
|
; The following three entries are very important. Be sure to read and
|
||||||
|
; understand the '[LangOptions] section' topic in the help file.
|
||||||
|
LanguageName=简体中文
|
||||||
|
; If Language Name display incorrect, uncomment next line
|
||||||
|
; LanguageName=<7B80><4F53><4E2D><6587>
|
||||||
|
; About LanguageID, to reference link:
|
||||||
|
; https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
|
||||||
|
LanguageID=$0804
|
||||||
|
; About CodePage, to reference link:
|
||||||
|
; https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
|
||||||
|
LanguageCodePage=936
|
||||||
|
; If the language you are translating to requires special font faces or
|
||||||
|
; sizes, uncomment any of the following entries and change them accordingly.
|
||||||
|
;DialogFontName=
|
||||||
|
;DialogFontSize=9
|
||||||
|
;DialogFontBaseScaleWidth=7
|
||||||
|
;DialogFontBaseScaleHeight=15
|
||||||
|
;WelcomeFontName=Segoe UI
|
||||||
|
;WelcomeFontSize=14
|
||||||
|
|
||||||
|
[Messages]
|
||||||
|
|
||||||
|
; *** 应用程序标题
|
||||||
|
SetupAppTitle=安装
|
||||||
|
SetupWindowTitle=安装 - %1
|
||||||
|
UninstallAppTitle=卸载
|
||||||
|
UninstallAppFullTitle=%1 卸载
|
||||||
|
|
||||||
|
; *** Misc. common
|
||||||
|
InformationTitle=信息
|
||||||
|
ConfirmTitle=确认
|
||||||
|
ErrorTitle=错误
|
||||||
|
|
||||||
|
; *** SetupLdr messages
|
||||||
|
SetupLdrStartupMessage=现在将安装 %1。您想要继续吗?
|
||||||
|
LdrCannotCreateTemp=无法创建临时文件。安装程序已中止
|
||||||
|
LdrCannotExecTemp=无法执行临时目录中的文件。安装程序已中止
|
||||||
|
HelpTextNote=
|
||||||
|
|
||||||
|
; *** 启动错误消息
|
||||||
|
LastErrorMessage=%1。%n%n错误 %2: %3
|
||||||
|
SetupFileMissing=安装目录中缺少文件 %1。请修正这个问题或者获取程序的新副本。
|
||||||
|
SetupFileCorrupt=安装文件已损坏。请获取程序的新副本。
|
||||||
|
SetupFileCorruptOrWrongVer=安装文件已损坏,或是与这个安装程序的版本不兼容。请修正这个问题或获取新的程序副本。
|
||||||
|
InvalidParameter=无效的命令行参数:%n%n%1
|
||||||
|
SetupAlreadyRunning=安装程序正在运行。
|
||||||
|
WindowsVersionNotSupported=此程序不支持当前计算机运行的 Windows 版本。
|
||||||
|
WindowsServicePackRequired=此程序需要 %1 服务包 %2 或更高版本。
|
||||||
|
NotOnThisPlatform=此程序不能在 %1 上运行。
|
||||||
|
OnlyOnThisPlatform=此程序只能在 %1 上运行。
|
||||||
|
OnlyOnTheseArchitectures=此程序只能安装到为下列处理器架构设计的 Windows 版本中:%n%n%1
|
||||||
|
WinVersionTooLowError=此程序需要 %1 版本 %2 或更高。
|
||||||
|
WinVersionTooHighError=此程序不能安装于 %1 版本 %2 或更高。
|
||||||
|
AdminPrivilegesRequired=在安装此程序时您必须以管理员身份登录。
|
||||||
|
PowerUserPrivilegesRequired=在安装此程序时您必须以管理员身份或有权限的用户组身份登录。
|
||||||
|
SetupAppRunningError=安装程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。
|
||||||
|
UninstallAppRunningError=卸载程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。
|
||||||
|
|
||||||
|
; *** 启动问题
|
||||||
|
PrivilegesRequiredOverrideTitle=选择安装程序模式
|
||||||
|
PrivilegesRequiredOverrideInstruction=选择安装模式
|
||||||
|
PrivilegesRequiredOverrideText1=%1 可以为所有用户安装(需要管理员权限),或仅为您安装。
|
||||||
|
PrivilegesRequiredOverrideText2=%1 可以仅为您安装,或为所有用户安装(需要管理员权限)。
|
||||||
|
PrivilegesRequiredOverrideAllUsers=为所有用户安装(&A)
|
||||||
|
PrivilegesRequiredOverrideAllUsersRecommended=为所有用户安装(&A) (建议选项)
|
||||||
|
PrivilegesRequiredOverrideCurrentUser=仅为我安装(&M)
|
||||||
|
PrivilegesRequiredOverrideCurrentUserRecommended=仅为我安装(&M) (建议选项)
|
||||||
|
|
||||||
|
; *** 其他错误
|
||||||
|
ErrorCreatingDir=安装程序无法创建目录“%1”
|
||||||
|
ErrorTooManyFilesInDir=无法在目录“%1”中创建文件,因为里面包含太多文件
|
||||||
|
|
||||||
|
; *** 安装程序公共消息
|
||||||
|
ExitSetupTitle=退出安装程序
|
||||||
|
ExitSetupMessage=安装程序尚未完成。如果现在退出,将不会安装该程序。%n%n您之后可以再次运行安装程序完成安装。%n%n现在退出安装程序吗?
|
||||||
|
AboutSetupMenuItem=关于安装程序(&A)...
|
||||||
|
AboutSetupTitle=关于安装程序
|
||||||
|
AboutSetupMessage=%1 版本 %2%n%3%n%n%1 主页:%n%4
|
||||||
|
AboutSetupNote=
|
||||||
|
TranslatorNote=简体中文翻译由Kira(847320916@qq.com)维护。项目地址:https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
|
||||||
|
|
||||||
|
; *** 按钮
|
||||||
|
ButtonBack=< 上一步(&B)
|
||||||
|
ButtonNext=下一步(&N) >
|
||||||
|
ButtonInstall=安装(&I)
|
||||||
|
ButtonOK=确定
|
||||||
|
ButtonCancel=取消
|
||||||
|
ButtonYes=是(&Y)
|
||||||
|
ButtonYesToAll=全是(&A)
|
||||||
|
ButtonNo=否(&N)
|
||||||
|
ButtonNoToAll=全否(&O)
|
||||||
|
ButtonFinish=完成(&F)
|
||||||
|
ButtonBrowse=浏览(&B)...
|
||||||
|
ButtonWizardBrowse=浏览(&R)...
|
||||||
|
ButtonNewFolder=新建文件夹(&M)
|
||||||
|
|
||||||
|
; *** “选择语言”对话框消息
|
||||||
|
SelectLanguageTitle=选择安装语言
|
||||||
|
SelectLanguageLabel=选择安装时使用的语言。
|
||||||
|
|
||||||
|
; *** 公共向导文字
|
||||||
|
ClickNext=点击“下一步”继续,或点击“取消”退出安装程序。
|
||||||
|
BeveledLabel=
|
||||||
|
BrowseDialogTitle=浏览文件夹
|
||||||
|
BrowseDialogLabel=在下面的列表中选择一个文件夹,然后点击“确定”。
|
||||||
|
NewFolderName=新建文件夹
|
||||||
|
|
||||||
|
; *** “欢迎”向导页
|
||||||
|
WelcomeLabel1=欢迎使用 [name] 安装向导
|
||||||
|
WelcomeLabel2=现在将安装 [name/ver] 到您的电脑中。%n%n建议您在继续安装前关闭所有其他应用程序。
|
||||||
|
|
||||||
|
; *** “密码”向导页
|
||||||
|
WizardPassword=密码
|
||||||
|
PasswordLabel1=这个安装程序有密码保护。
|
||||||
|
PasswordLabel3=请输入密码,然后点击“下一步”继续。密码区分大小写。
|
||||||
|
PasswordEditLabel=密码(&P):
|
||||||
|
IncorrectPassword=您输入的密码不正确,请重新输入。
|
||||||
|
|
||||||
|
; *** “许可协议”向导页
|
||||||
|
WizardLicense=许可协议
|
||||||
|
LicenseLabel=请在继续安装前阅读以下重要信息。
|
||||||
|
LicenseLabel3=请仔细阅读下列许可协议。在继续安装前您必须同意这些协议条款。
|
||||||
|
LicenseAccepted=我同意此协议(&A)
|
||||||
|
LicenseNotAccepted=我不同意此协议(&D)
|
||||||
|
|
||||||
|
; *** “信息”向导页
|
||||||
|
WizardInfoBefore=信息
|
||||||
|
InfoBeforeLabel=请在继续安装前阅读以下重要信息。
|
||||||
|
InfoBeforeClickLabel=准备好继续安装后,点击“下一步”。
|
||||||
|
WizardInfoAfter=信息
|
||||||
|
InfoAfterLabel=请在继续安装前阅读以下重要信息。
|
||||||
|
InfoAfterClickLabel=准备好继续安装后,点击“下一步”。
|
||||||
|
|
||||||
|
; *** “用户信息”向导页
|
||||||
|
WizardUserInfo=用户信息
|
||||||
|
UserInfoDesc=请输入您的信息。
|
||||||
|
UserInfoName=用户名(&U):
|
||||||
|
UserInfoOrg=组织(&O):
|
||||||
|
UserInfoSerial=序列号(&S):
|
||||||
|
UserInfoNameRequired=您必须输入用户名。
|
||||||
|
|
||||||
|
; *** “选择目标目录”向导页
|
||||||
|
WizardSelectDir=选择目标位置
|
||||||
|
SelectDirDesc=您想将 [name] 安装在哪里?
|
||||||
|
SelectDirLabel3=安装程序将安装 [name] 到下面的文件夹中。
|
||||||
|
SelectDirBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
|
||||||
|
DiskSpaceGBLabel=至少需要有 [gb] GB 的可用磁盘空间。
|
||||||
|
DiskSpaceMBLabel=至少需要有 [mb] MB 的可用磁盘空间。
|
||||||
|
CannotInstallToNetworkDrive=安装程序无法安装到一个网络驱动器。
|
||||||
|
CannotInstallToUNCPath=安装程序无法安装到一个 UNC 路径。
|
||||||
|
InvalidPath=您必须输入一个带驱动器卷标的完整路径,例如:%n%nC:\APP%n%n或UNC路径:%n%n\\server\share
|
||||||
|
InvalidDrive=您选定的驱动器或 UNC 共享不存在或不能访问。请选择其他位置。
|
||||||
|
DiskSpaceWarningTitle=磁盘空间不足
|
||||||
|
DiskSpaceWarning=安装程序至少需要 %1 KB 的可用空间才能安装,但选定驱动器只有 %2 KB 的可用空间。%n%n您一定要继续吗?
|
||||||
|
DirNameTooLong=文件夹名称或路径太长。
|
||||||
|
InvalidDirName=文件夹名称无效。
|
||||||
|
BadDirName32=文件夹名称不能包含下列任何字符:%n%n%1
|
||||||
|
DirExistsTitle=文件夹已存在
|
||||||
|
DirExists=文件夹:%n%n%1%n%n已经存在。您一定要安装到这个文件夹中吗?
|
||||||
|
DirDoesntExistTitle=文件夹不存在
|
||||||
|
DirDoesntExist=文件夹:%n%n%1%n%n不存在。您想要创建此文件夹吗?
|
||||||
|
|
||||||
|
; *** “选择组件”向导页
|
||||||
|
WizardSelectComponents=选择组件
|
||||||
|
SelectComponentsDesc=您想安装哪些程序组件?
|
||||||
|
SelectComponentsLabel2=选中您想安装的组件;取消您不想安装的组件。然后点击“下一步”继续。
|
||||||
|
FullInstallation=完全安装
|
||||||
|
; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)
|
||||||
|
CompactInstallation=简洁安装
|
||||||
|
CustomInstallation=自定义安装
|
||||||
|
NoUninstallWarningTitle=组件已存在
|
||||||
|
NoUninstallWarning=安装程序检测到下列组件已安装在您的电脑中:%n%n%1%n%n取消选中这些组件不会卸载它们。%n%n确定要继续吗?
|
||||||
|
ComponentSize1=%1 KB
|
||||||
|
ComponentSize2=%1 MB
|
||||||
|
ComponentsDiskSpaceGBLabel=当前选择的组件需要至少 [gb] GB 的磁盘空间。
|
||||||
|
ComponentsDiskSpaceMBLabel=当前选择的组件需要至少 [mb] MB 的磁盘空间。
|
||||||
|
|
||||||
|
; *** “选择附加任务”向导页
|
||||||
|
WizardSelectTasks=选择附加任务
|
||||||
|
SelectTasksDesc=您想要安装程序执行哪些附加任务?
|
||||||
|
SelectTasksLabel2=选择您想要安装程序在安装 [name] 时执行的附加任务,然后点击“下一步”。
|
||||||
|
|
||||||
|
; *** “选择开始菜单文件夹”向导页
|
||||||
|
WizardSelectProgramGroup=选择开始菜单文件夹
|
||||||
|
SelectStartMenuFolderDesc=安装程序应该在哪里放置程序的快捷方式?
|
||||||
|
SelectStartMenuFolderLabel3=安装程序将在下列“开始”菜单文件夹中创建程序的快捷方式。
|
||||||
|
SelectStartMenuFolderBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
|
||||||
|
MustEnterGroupName=您必须输入一个文件夹名。
|
||||||
|
GroupNameTooLong=文件夹名或路径太长。
|
||||||
|
InvalidGroupName=无效的文件夹名字。
|
||||||
|
BadGroupName=文件夹名不能包含下列任何字符:%n%n%1
|
||||||
|
NoProgramGroupCheck2=不创建开始菜单文件夹(&D)
|
||||||
|
|
||||||
|
; *** “准备安装”向导页
|
||||||
|
WizardReady=准备安装
|
||||||
|
ReadyLabel1=安装程序准备就绪,现在可以开始安装 [name] 到您的电脑。
|
||||||
|
ReadyLabel2a=点击“安装”继续此安装程序。如果您想重新考虑或修改任何设置,点击“上一步”。
|
||||||
|
ReadyLabel2b=点击“安装”继续此安装程序。
|
||||||
|
ReadyMemoUserInfo=用户信息:
|
||||||
|
ReadyMemoDir=目标位置:
|
||||||
|
ReadyMemoType=安装类型:
|
||||||
|
ReadyMemoComponents=已选择组件:
|
||||||
|
ReadyMemoGroup=开始菜单文件夹:
|
||||||
|
ReadyMemoTasks=附加任务:
|
||||||
|
|
||||||
|
; *** TExtractionWizardPage 向导页面与 ExtractArchive
|
||||||
|
ExtractingLabel=正在解压文件...
|
||||||
|
ButtonStopExtraction=停止解压(&S)
|
||||||
|
StopExtraction=您确定要停止解压吗?
|
||||||
|
ErrorExtractionAborted=解压已中止
|
||||||
|
ErrorExtractionFailed=解压失败:%1
|
||||||
|
|
||||||
|
; *** 压缩文件解压失败详情
|
||||||
|
ArchiveIncorrectPassword=压缩文件密码不正确
|
||||||
|
ArchiveIsCorrupted=压缩文件已损坏
|
||||||
|
ArchiveUnsupportedFormat=不支持的压缩文件格式
|
||||||
|
|
||||||
|
; *** TDownloadWizardPage 向导页面和 DownloadTemporaryFile
|
||||||
|
DownloadingLabel2=正在下载文件...
|
||||||
|
ButtonStopDownload=停止下载(&S)
|
||||||
|
StopDownload=您确定要停止下载吗?
|
||||||
|
ErrorDownloadAborted=下载已中止
|
||||||
|
ErrorDownloadFailed=下载失败:%1 %2
|
||||||
|
ErrorDownloadSizeFailed=获取下载大小失败:%1 %2
|
||||||
|
ErrorProgress=无效的进度:%1 / %2
|
||||||
|
ErrorFileSize=文件大小错误:预期 %1,实际 %2
|
||||||
|
|
||||||
|
; *** “正在准备安装”向导页
|
||||||
|
WizardPreparing=正在准备安装
|
||||||
|
PreparingDesc=安装程序正在准备安装 [name] 到您的电脑。
|
||||||
|
PreviousInstallNotCompleted=先前的程序安装或卸载未完成,您需要重启您的电脑以完成。%n%n在重启电脑后,再次运行安装程序以完成 [name] 的安装。
|
||||||
|
CannotContinue=安装程序不能继续。请点击“取消”退出。
|
||||||
|
ApplicationsFound=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。
|
||||||
|
ApplicationsFound2=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。安装完成后,安装程序将尝试重新启动这些应用程序。
|
||||||
|
CloseApplications=自动关闭应用程序(&A)
|
||||||
|
DontCloseApplications=不要关闭应用程序(&D)
|
||||||
|
ErrorCloseApplications=安装程序无法自动关闭所有应用程序。建议您在继续之前,关闭所有在使用需要由安装程序更新的文件的应用程序。
|
||||||
|
PrepareToInstallNeedsRestart=安装程序必须重启您的计算机。计算机重启后,请再次运行安装程序以完成 [name] 的安装。%n%n是否立即重新启动?
|
||||||
|
|
||||||
|
; *** “正在安装”向导页
|
||||||
|
WizardInstalling=正在安装
|
||||||
|
InstallingLabel=安装程序正在安装 [name] 到您的电脑,请稍候。
|
||||||
|
|
||||||
|
; *** “安装完成”向导页
|
||||||
|
FinishedHeadingLabel=[name] 安装完成
|
||||||
|
FinishedLabelNoIcons=安装程序已在您的电脑中安装了 [name]。
|
||||||
|
FinishedLabel=安装程序已在您的电脑中安装了 [name]。您可以通过已安装的快捷方式运行此应用程序。
|
||||||
|
ClickFinish=点击“完成”退出安装程序。
|
||||||
|
FinishedRestartLabel=为完成 [name] 的安装,安装程序必须重新启动您的电脑。要立即重启吗?
|
||||||
|
FinishedRestartMessage=为完成 [name] 的安装,安装程序必须重新启动您的电脑。%n%n要立即重启吗?
|
||||||
|
ShowReadmeCheck=是,我想查阅自述文件
|
||||||
|
YesRadio=是,立即重启电脑(&Y)
|
||||||
|
NoRadio=否,稍后重启电脑(&N)
|
||||||
|
; used for example as 'Run MyProg.exe'
|
||||||
|
RunEntryExec=运行 %1
|
||||||
|
; used for example as 'View Readme.txt'
|
||||||
|
RunEntryShellExec=查阅 %1
|
||||||
|
|
||||||
|
; *** “安装程序需要下一张磁盘”提示
|
||||||
|
ChangeDiskTitle=安装程序需要下一张磁盘
|
||||||
|
SelectDiskLabel2=请插入磁盘 %1 并点击“确定”。%n%n如果这个磁盘中的文件可以在下列文件夹之外的文件夹中找到,请输入正确的路径或点击“浏览”。
|
||||||
|
PathLabel=路径(&P):
|
||||||
|
FileNotInDir2=“%2”中找不到文件“%1”。请插入正确的磁盘或选择其他文件夹。
|
||||||
|
SelectDirectoryLabel=请指定下一张磁盘的位置。
|
||||||
|
|
||||||
|
; *** 安装阶段消息
|
||||||
|
SetupAborted=安装程序未完成安装。%n%n请修正这个问题并重新运行安装程序。
|
||||||
|
AbortRetryIgnoreSelectAction=选择操作
|
||||||
|
AbortRetryIgnoreRetry=重试(&T)
|
||||||
|
AbortRetryIgnoreIgnore=忽略错误并继续(&I)
|
||||||
|
AbortRetryIgnoreCancel=关闭安装程序
|
||||||
|
RetryCancelSelectAction=选择操作
|
||||||
|
RetryCancelRetry=重试(&T)
|
||||||
|
RetryCancelCancel=取消(&C)
|
||||||
|
|
||||||
|
; *** 安装状态消息
|
||||||
|
StatusClosingApplications=正在关闭应用程序...
|
||||||
|
StatusCreateDirs=正在创建目录...
|
||||||
|
StatusExtractFiles=正在提取文件...
|
||||||
|
StatusDownloadFiles=正在下载文件...
|
||||||
|
StatusCreateIcons=正在创建快捷方式...
|
||||||
|
StatusCreateIniEntries=正在创建 INI 条目...
|
||||||
|
StatusCreateRegistryEntries=正在创建注册表条目...
|
||||||
|
StatusRegisterFiles=正在注册文件...
|
||||||
|
StatusSavingUninstall=正在保存卸载信息...
|
||||||
|
StatusRunProgram=正在完成安装...
|
||||||
|
StatusRestartingApplications=正在重启应用程序...
|
||||||
|
StatusRollback=正在撤销更改...
|
||||||
|
|
||||||
|
; *** 其他错误
|
||||||
|
ErrorInternal2=内部错误:%1
|
||||||
|
ErrorFunctionFailedNoCode=%1 失败
|
||||||
|
ErrorFunctionFailed=%1 失败;错误代码 %2
|
||||||
|
ErrorFunctionFailedWithMessage=%1 失败;错误代码 %2.%n%3
|
||||||
|
ErrorExecutingProgram=无法执行文件:%n%1
|
||||||
|
|
||||||
|
; *** 注册表错误
|
||||||
|
ErrorRegOpenKey=打开注册表项时出错:%n%1\%2
|
||||||
|
ErrorRegCreateKey=创建注册表项时出错:%n%1\%2
|
||||||
|
ErrorRegWriteKey=写入注册表项时出错:%n%1\%2
|
||||||
|
|
||||||
|
; *** INI 错误
|
||||||
|
ErrorIniEntry=在文件“%1”中创建 INI 条目时出错。
|
||||||
|
|
||||||
|
; *** 文件复制错误
|
||||||
|
FileAbortRetryIgnoreSkipNotRecommended=跳过此文件(&S) (不推荐)
|
||||||
|
FileAbortRetryIgnoreIgnoreNotRecommended=忽略错误并继续(&I) (不推荐)
|
||||||
|
SourceIsCorrupted=源文件已损坏
|
||||||
|
SourceDoesntExist=源文件“%1”不存在
|
||||||
|
SourceVerificationFailed=源文件验证失败: %1
|
||||||
|
VerificationSignatureDoesntExist=签名文件“%1”不存在
|
||||||
|
VerificationSignatureInvalid=签名文件“%1”无效
|
||||||
|
VerificationKeyNotFound=签名文件“%1”使用了未知密钥
|
||||||
|
VerificationFileNameIncorrect=文件名不正确
|
||||||
|
VerificationFileTagIncorrect=文件标签不正确
|
||||||
|
VerificationFileSizeIncorrect=文件大小不正确
|
||||||
|
VerificationFileHashIncorrect=文件哈希值不正确
|
||||||
|
ExistingFileReadOnly2=无法替换现有文件,它是只读的。
|
||||||
|
ExistingFileReadOnlyRetry=移除只读属性并重试(&R)
|
||||||
|
ExistingFileReadOnlyKeepExisting=保留现有文件(&K)
|
||||||
|
ErrorReadingExistingDest=尝试读取现有文件时出错:
|
||||||
|
FileExistsSelectAction=选择操作
|
||||||
|
FileExists2=文件已经存在。
|
||||||
|
FileExistsOverwriteExisting=覆盖已存在的文件(&O)
|
||||||
|
FileExistsKeepExisting=保留现有的文件(&K)
|
||||||
|
FileExistsOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
|
||||||
|
ExistingFileNewerSelectAction=选择操作
|
||||||
|
ExistingFileNewer2=现有的文件比安装程序将要安装的文件还要新。
|
||||||
|
ExistingFileNewerOverwriteExisting=覆盖已存在的文件(&O)
|
||||||
|
ExistingFileNewerKeepExisting=保留现有的文件(&K) (推荐)
|
||||||
|
ExistingFileNewerOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
|
||||||
|
ErrorChangingAttr=尝试更改下列现有文件的属性时出错:
|
||||||
|
ErrorCreatingTemp=尝试在目标目录创建文件时出错:
|
||||||
|
ErrorReadingSource=尝试读取下列源文件时出错:
|
||||||
|
ErrorCopying=尝试复制下列文件时出错:
|
||||||
|
ErrorDownloading=下载文件时出错:
|
||||||
|
ErrorExtracting=解压压缩文件时出错:
|
||||||
|
ErrorReplacingExistingFile=尝试替换现有文件时出错:
|
||||||
|
ErrorRestartReplace=重启并替换失败:
|
||||||
|
ErrorRenamingTemp=尝试重命名下列目标目录中的一个文件时出错:
|
||||||
|
ErrorRegisterServer=无法注册 DLL/OCX:%1
|
||||||
|
ErrorRegSvr32Failed=RegSvr32 失败;退出代码 %1
|
||||||
|
ErrorRegisterTypeLib=无法注册类库:%1
|
||||||
|
|
||||||
|
; *** 卸载显示名字标记
|
||||||
|
; used for example as 'My Program (32-bit)'
|
||||||
|
UninstallDisplayNameMark=%1 (%2)
|
||||||
|
; used for example as 'My Program (32-bit, All users)'
|
||||||
|
UninstallDisplayNameMarks=%1 (%2, %3)
|
||||||
|
UninstallDisplayNameMark32Bit=32 位
|
||||||
|
UninstallDisplayNameMark64Bit=64 位
|
||||||
|
UninstallDisplayNameMarkAllUsers=所有用户
|
||||||
|
UninstallDisplayNameMarkCurrentUser=当前用户
|
||||||
|
|
||||||
|
; *** 安装后错误
|
||||||
|
ErrorOpeningReadme=尝试打开自述文件时出错。
|
||||||
|
ErrorRestartingComputer=安装程序无法重启电脑,请手动重启。
|
||||||
|
|
||||||
|
; *** 卸载消息
|
||||||
|
UninstallNotFound=文件“%1”不存在。无法卸载。
|
||||||
|
UninstallOpenError=文件“%1”不能被打开。无法卸载。
|
||||||
|
UninstallUnsupportedVer=此版本的卸载程序无法识别卸载日志文件“%1”的格式。无法卸载
|
||||||
|
UninstallUnknownEntry=卸载日志中遇到一个未知条目 (%1)
|
||||||
|
ConfirmUninstall=您确认要完全移除 %1 及其所有组件吗?
|
||||||
|
UninstallOnlyOnWin64=仅允许在 64 位 Windows 中卸载此程序。
|
||||||
|
OnlyAdminCanUninstall=仅使用管理员权限的用户能完成此卸载。
|
||||||
|
UninstallStatusLabel=正在从您的电脑中移除 %1,请稍候。
|
||||||
|
UninstalledAll=已顺利从您的电脑中移除 %1。
|
||||||
|
UninstalledMost=%1 卸载完成。%n%n有部分内容未能被删除,但您可以手动删除它们。
|
||||||
|
UninstalledAndNeedsRestart=为完成 %1 的卸载,需要重启您的电脑。%n%n立即重启电脑吗?
|
||||||
|
UninstallDataCorrupted=文件“%1”已损坏。无法卸载
|
||||||
|
|
||||||
|
; *** 卸载状态消息
|
||||||
|
ConfirmDeleteSharedFileTitle=删除共享的文件吗?
|
||||||
|
ConfirmDeleteSharedFile2=系统表示下列共享的文件已不有其他程序使用。您希望卸载程序删除这些共享的文件吗?%n%n如果删除这些文件,但仍有程序在使用这些文件,则这些程序可能出现异常。如果您不能确定,请选择“否”,在系统中保留这些文件以免引发问题。
|
||||||
|
SharedFileNameLabel=文件名:
|
||||||
|
SharedFileLocationLabel=位置:
|
||||||
|
WizardUninstalling=卸载状态
|
||||||
|
StatusUninstalling=正在卸载 %1...
|
||||||
|
|
||||||
|
; *** Shutdown block reasons
|
||||||
|
ShutdownBlockReasonInstallingApp=正在安装 %1。
|
||||||
|
ShutdownBlockReasonUninstallingApp=正在卸载 %1。
|
||||||
|
|
||||||
|
; The custom messages below aren't used by Setup itself, but if you make
|
||||||
|
; use of them in your scripts, you'll want to translate them.
|
||||||
|
|
||||||
|
[CustomMessages]
|
||||||
|
|
||||||
|
NameAndVersion=%1 版本 %2
|
||||||
|
AdditionalIcons=附加快捷方式:
|
||||||
|
CreateDesktopIcon=创建桌面快捷方式(&D)
|
||||||
|
CreateQuickLaunchIcon=创建快速启动栏快捷方式(&Q)
|
||||||
|
ProgramOnTheWeb=%1 网站
|
||||||
|
UninstallProgram=卸载 %1
|
||||||
|
LaunchProgram=运行 %1
|
||||||
|
AssocFileExtension=将 %2 文件扩展名与 %1 建立关联(&A)
|
||||||
|
AssocingFileExtension=正在将 %2 文件扩展名与 %1 建立关联...
|
||||||
|
AutoStartProgramGroupDescription=启动:
|
||||||
|
AutoStartProgram=自动启动 %1
|
||||||
|
AddonHostProgramNotFound=您选择的文件夹中无法找到 %1。%n%n您要继续吗?
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
; ============================================================================
|
||||||
|
; smPlayer Windows Installer (Inno Setup 6)
|
||||||
|
; ----------------------------------------------------------------------------
|
||||||
|
; 用法:
|
||||||
|
; 1. 先执行:flutter build windows --release
|
||||||
|
; 2. 再执行:iscc installer\sm_emby.iss
|
||||||
|
; 或一键: powershell -ExecutionPolicy Bypass -File scripts\build-installer.ps1
|
||||||
|
;
|
||||||
|
; 命令行可覆盖版本号:iscc /DAppVersion=1.2.3 installer\sm_emby.iss
|
||||||
|
; ============================================================================
|
||||||
|
|
||||||
|
#define AppName "smPlayer"
|
||||||
|
#define AppPublisher "smPlayer"
|
||||||
|
#define AppExeName "smplayer.exe"
|
||||||
|
#define AppId "{{A8E3F1C2-7D5B-4A6E-9C8D-3F2E1B0A9D7C}"
|
||||||
|
|
||||||
|
#ifndef AppVersion
|
||||||
|
#define AppVersion "0.0.15"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ProjectRoot SourcePath + ".."
|
||||||
|
#define BuildDir ProjectRoot + "\build\windows\x64\runner\Release"
|
||||||
|
#define IconFile ProjectRoot + "\windows\runner\resources\app_icon.ico"
|
||||||
|
#define OutputDirAbs ProjectRoot + "\build\installer"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppId={#AppId}
|
||||||
|
AppName={#AppName}
|
||||||
|
AppVersion={#AppVersion}
|
||||||
|
AppVerName={#AppName} {#AppVersion}
|
||||||
|
AppPublisher={#AppPublisher}
|
||||||
|
DefaultDirName={autopf}\{#AppName}
|
||||||
|
DefaultGroupName={#AppName}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
DisableDirPage=no
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
ArchitecturesAllowed=x64compatible
|
||||||
|
OutputDir={#OutputDirAbs}
|
||||||
|
OutputBaseFilename=smplayer-setup-{#AppVersion}-x64
|
||||||
|
SetupIconFile={#IconFile}
|
||||||
|
UninstallDisplayIcon={app}\{#AppExeName}
|
||||||
|
Compression=lzma2/ultra64
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
PrivilegesRequired=admin
|
||||||
|
PrivilegesRequiredOverridesAllowed=dialog
|
||||||
|
CloseApplications=force
|
||||||
|
RestartApplications=no
|
||||||
|
MinVersion=10.0
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
; ChineseSimplified.isl 来自 https://github.com/jrsoftware/issrc (Files/Languages/Unofficial)
|
||||||
|
; 已随仓库提交至 installer/lang/ 以避免依赖系统级 Inno Setup 安装
|
||||||
|
Name: "chinesesimp"; MessagesFile: "lang\ChineseSimplified.isl"
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
; 主可执行文件
|
||||||
|
Source: "{#BuildDir}\{#AppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
; 所有 DLL(Flutter 引擎、fvp/MDK、media_kit/mpv、插件等)
|
||||||
|
Source: "{#BuildDir}\*.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
; native_assets 清单
|
||||||
|
Source: "{#BuildDir}\native_assets.json"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist
|
||||||
|
; data 目录(flutter_assets + icudtl.dat + app.so)
|
||||||
|
Source: "{#BuildDir}\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"
|
||||||
|
Name: "{group}\{cm:UninstallProgram,{#AppName}}"; Filename: "{uninstallexe}"
|
||||||
|
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Registry]
|
||||||
|
; 自定义 URL scheme:smplayer:// → 本程序(Trakt OAuth 回跳 smplayer://oauth-trakt)。
|
||||||
|
; 安装到 HKLM\Software\Classes,正式安装走管理员权限写入。
|
||||||
|
; app 已运行时点链接会拉起第二个进程,由 windows/runner/main.cpp 的 SendAppLinkToInstance
|
||||||
|
; 把深链转发给已存在窗口后立即退出(app_links 官方单实例范式),不会出现孤立的第二实例。
|
||||||
|
Root: HKLM; Subkey: "Software\Classes\smplayer"; ValueType: string; ValueName: ""; ValueData: "URL:smplayer Protocol"; Flags: uninsdeletekey
|
||||||
|
Root: HKLM; Subkey: "Software\Classes\smplayer"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""
|
||||||
|
Root: HKLM; Subkey: "Software\Classes\smplayer\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppExeName},0"
|
||||||
|
Root: HKLM; Subkey: "Software\Classes\smplayer\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppExeName}"" ""%1"""
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#AppName}}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
; 清理用户数据目录(可选;如需保留用户配置可注释掉)
|
||||||
|
; Type: filesandordirs; Name: "{userappdata}\sm_emby"
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart'
|
||||||
|
show defaultTargetPlatform, TargetPlatform;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:forui/forui.dart';
|
||||||
|
|
||||||
|
import 'features/update/update_dialog.dart';
|
||||||
|
import 'providers/appearance_provider.dart';
|
||||||
|
import 'providers/di_providers.dart';
|
||||||
|
import 'providers/server_sync_provider.dart';
|
||||||
|
import 'providers/sm_account_provider.dart';
|
||||||
|
import 'providers/update_provider.dart';
|
||||||
|
import 'router/app_router.dart' show appRouterProvider, rootNavigatorKey;
|
||||||
|
import 'shared/theme/app_scroll_behavior.dart';
|
||||||
|
import 'shared/theme/app_theme.dart';
|
||||||
|
import 'shared/utils/app_logger.dart';
|
||||||
|
|
||||||
|
class SmPlayerApp extends ConsumerStatefulWidget {
|
||||||
|
const SmPlayerApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<SmPlayerApp> createState() => _SmPlayerAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SmPlayerAppState extends ConsumerState<SmPlayerApp> {
|
||||||
|
bool _updateDialogShown = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
try {
|
||||||
|
final queue = await ref.read(traktSyncQueueProvider.future);
|
||||||
|
await queue.drain();
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.debug('Trakt', 'startup drain skipped', e);
|
||||||
|
}
|
||||||
|
unawaited(ref.read(updateProvider.notifier).silentCheck());
|
||||||
|
unawaited(_autoSyncPull());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _autoSyncPull() async {
|
||||||
|
try {
|
||||||
|
final account = await ref.read(smAccountProvider.future);
|
||||||
|
if (!account.hasVip) return;
|
||||||
|
final outcome = await ref.read(serverSyncProvider.notifier).pull();
|
||||||
|
if (outcome.isError) {
|
||||||
|
AppLogger.debug('CloudSync', 'auto-pull failed: ${outcome.message}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.debug('CloudSync', 'auto-pull skipped', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final router = ref.watch(appRouterProvider);
|
||||||
|
final appearance = ref.watch(appearanceProvider);
|
||||||
|
final flutterMode = appearance.maybeWhen(
|
||||||
|
data: (d) => d.themeMode,
|
||||||
|
orElse: () => ThemeMode.system,
|
||||||
|
);
|
||||||
|
|
||||||
|
return MaterialApp.router(
|
||||||
|
title: 'smPlayer',
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
locale: const Locale('zh', 'CN'),
|
||||||
|
supportedLocales: const [Locale('zh', 'CN'), Locale('en')],
|
||||||
|
localizationsDelegates: const [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
],
|
||||||
|
theme: AppTheme.light(),
|
||||||
|
darkTheme: AppTheme.dark(),
|
||||||
|
themeMode: flutterMode,
|
||||||
|
scrollBehavior: const AppScrollBehavior(),
|
||||||
|
routerConfig: router,
|
||||||
|
builder: (context, child) {
|
||||||
|
final zinc = Theme.of(context).brightness == Brightness.dark
|
||||||
|
? FThemes.zinc.dark
|
||||||
|
: FThemes.zinc.light;
|
||||||
|
return FTheme(
|
||||||
|
data: _isTouchPlatform ? zinc.touch : zinc.desktop,
|
||||||
|
child: FToaster(
|
||||||
|
child: Consumer(
|
||||||
|
builder: (context, ref, _) {
|
||||||
|
ref.listen<UpdateState>(updateProvider, (previous, next) {
|
||||||
|
if (_updateDialogShown ||
|
||||||
|
next.status != UpdateStatus.available ||
|
||||||
|
!next.silent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_updateDialogShown = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
final navContext = rootNavigatorKey.currentContext;
|
||||||
|
if (navContext == null) return;
|
||||||
|
showFDialog(
|
||||||
|
context: navContext,
|
||||||
|
builder: (_, _, _) => const UpdateDialog(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return child ?? const SizedBox();
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool get _isTouchPlatform => switch (defaultTargetPlatform) {
|
||||||
|
TargetPlatform.android ||
|
||||||
|
TargetPlatform.iOS ||
|
||||||
|
TargetPlatform.fuchsia => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
class AppUpdateAsset {
|
||||||
|
final String name;
|
||||||
|
final String downloadUrl;
|
||||||
|
final int size;
|
||||||
|
final String? sha256;
|
||||||
|
|
||||||
|
|
||||||
|
final String? version;
|
||||||
|
|
||||||
|
const AppUpdateAsset({
|
||||||
|
required this.name,
|
||||||
|
required this.downloadUrl,
|
||||||
|
required this.size,
|
||||||
|
this.sha256,
|
||||||
|
this.version,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AppUpdateAsset.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AppUpdateAsset(
|
||||||
|
name: json['name']?.toString() ?? '',
|
||||||
|
downloadUrl:
|
||||||
|
json['browser_download_url']?.toString() ??
|
||||||
|
json['browserDownloadUrl']?.toString() ??
|
||||||
|
json['url']?.toString() ??
|
||||||
|
'',
|
||||||
|
size: json['size'] is int ? json['size'] as int : 0,
|
||||||
|
sha256: (json['sha256']?.toString().trim().isEmpty ?? true)
|
||||||
|
? null
|
||||||
|
: json['sha256'].toString().trim().toLowerCase(),
|
||||||
|
version: (json['version']?.toString().trim().isEmpty ?? true)
|
||||||
|
? null
|
||||||
|
: json['version'].toString().trim(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppUpdateInfo {
|
||||||
|
final String version;
|
||||||
|
final String releaseNotes;
|
||||||
|
final Uri htmlUrl;
|
||||||
|
final List<AppUpdateAsset> assets;
|
||||||
|
final Map<String, AppUpdateAsset> platformAssets;
|
||||||
|
final AppUpdateAsset? selectedAsset;
|
||||||
|
final bool noAssetForPlatform;
|
||||||
|
|
||||||
|
const AppUpdateInfo({
|
||||||
|
required this.version,
|
||||||
|
required this.releaseNotes,
|
||||||
|
required this.htmlUrl,
|
||||||
|
required this.assets,
|
||||||
|
this.platformAssets = const {},
|
||||||
|
this.selectedAsset,
|
||||||
|
this.noAssetForPlatform = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
AppUpdateInfo copyWith({
|
||||||
|
String? version,
|
||||||
|
String? releaseNotes,
|
||||||
|
Uri? htmlUrl,
|
||||||
|
List<AppUpdateAsset>? assets,
|
||||||
|
Map<String, AppUpdateAsset>? platformAssets,
|
||||||
|
AppUpdateAsset? selectedAsset,
|
||||||
|
bool? noAssetForPlatform,
|
||||||
|
}) {
|
||||||
|
return AppUpdateInfo(
|
||||||
|
version: version ?? this.version,
|
||||||
|
releaseNotes: releaseNotes ?? this.releaseNotes,
|
||||||
|
htmlUrl: htmlUrl ?? this.htmlUrl,
|
||||||
|
assets: assets ?? this.assets,
|
||||||
|
platformAssets: platformAssets ?? this.platformAssets,
|
||||||
|
selectedAsset: selectedAsset ?? this.selectedAsset,
|
||||||
|
noAssetForPlatform: noAssetForPlatform ?? this.noAssetForPlatform,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateCheckResult {
|
||||||
|
final bool hasUpdate;
|
||||||
|
final AppUpdateInfo? info;
|
||||||
|
|
||||||
|
const UpdateCheckResult({required this.hasUpdate, this.info});
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'auth.freezed.dart';
|
||||||
|
part 'auth.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class AuthByNameReq with _$AuthByNameReq {
|
||||||
|
const factory AuthByNameReq({
|
||||||
|
required String serverId,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
}) = _AuthByNameReq;
|
||||||
|
|
||||||
|
factory AuthByNameReq.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AuthByNameReqFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SessionData with _$SessionData {
|
||||||
|
const factory SessionData({
|
||||||
|
required String serverId,
|
||||||
|
required String token,
|
||||||
|
required String userId,
|
||||||
|
required String userName,
|
||||||
|
@JsonKey(includeIfNull: false) String? password,
|
||||||
|
required String createdAt,
|
||||||
|
}) = _SessionData;
|
||||||
|
|
||||||
|
factory SessionData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SessionDataFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class AuthedSession with _$AuthedSession {
|
||||||
|
const factory AuthedSession({
|
||||||
|
required String serverId,
|
||||||
|
required String serverName,
|
||||||
|
required String serverUrl,
|
||||||
|
required String token,
|
||||||
|
required String userId,
|
||||||
|
required String userName,
|
||||||
|
@JsonKey(includeIfNull: false) String? password,
|
||||||
|
required bool isActive,
|
||||||
|
}) = _AuthedSession;
|
||||||
|
|
||||||
|
factory AuthedSession.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AuthedSessionFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SessionListRes with _$SessionListRes {
|
||||||
|
const factory SessionListRes({
|
||||||
|
required List<AuthedSession> sessions,
|
||||||
|
|
||||||
|
|
||||||
|
String? activeServerId,
|
||||||
|
}) = _SessionListRes;
|
||||||
|
|
||||||
|
factory SessionListRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SessionListResFromJson(json);
|
||||||
|
}
|
||||||
Generated
+1119
File diff suppressed because it is too large
Load Diff
Generated
+78
@@ -0,0 +1,78 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'auth.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_AuthByNameReq _$AuthByNameReqFromJson(Map<String, dynamic> json) =>
|
||||||
|
_AuthByNameReq(
|
||||||
|
serverId: json['serverId'] as String,
|
||||||
|
username: json['username'] as String,
|
||||||
|
password: json['password'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$AuthByNameReqToJson(_AuthByNameReq instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'serverId': instance.serverId,
|
||||||
|
'username': instance.username,
|
||||||
|
'password': instance.password,
|
||||||
|
};
|
||||||
|
|
||||||
|
_SessionData _$SessionDataFromJson(Map<String, dynamic> json) => _SessionData(
|
||||||
|
serverId: json['serverId'] as String,
|
||||||
|
token: json['token'] as String,
|
||||||
|
userId: json['userId'] as String,
|
||||||
|
userName: json['userName'] as String,
|
||||||
|
password: json['password'] as String?,
|
||||||
|
createdAt: json['createdAt'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SessionDataToJson(_SessionData instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'serverId': instance.serverId,
|
||||||
|
'token': instance.token,
|
||||||
|
'userId': instance.userId,
|
||||||
|
'userName': instance.userName,
|
||||||
|
'password': ?instance.password,
|
||||||
|
'createdAt': instance.createdAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
_AuthedSession _$AuthedSessionFromJson(Map<String, dynamic> json) =>
|
||||||
|
_AuthedSession(
|
||||||
|
serverId: json['serverId'] as String,
|
||||||
|
serverName: json['serverName'] as String,
|
||||||
|
serverUrl: json['serverUrl'] as String,
|
||||||
|
token: json['token'] as String,
|
||||||
|
userId: json['userId'] as String,
|
||||||
|
userName: json['userName'] as String,
|
||||||
|
password: json['password'] as String?,
|
||||||
|
isActive: json['isActive'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$AuthedSessionToJson(_AuthedSession instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'serverId': instance.serverId,
|
||||||
|
'serverName': instance.serverName,
|
||||||
|
'serverUrl': instance.serverUrl,
|
||||||
|
'token': instance.token,
|
||||||
|
'userId': instance.userId,
|
||||||
|
'userName': instance.userName,
|
||||||
|
'password': ?instance.password,
|
||||||
|
'isActive': instance.isActive,
|
||||||
|
};
|
||||||
|
|
||||||
|
_SessionListRes _$SessionListResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SessionListRes(
|
||||||
|
sessions: (json['sessions'] as List<dynamic>)
|
||||||
|
.map((e) => AuthedSession.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
activeServerId: json['activeServerId'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SessionListResToJson(_SessionListRes instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'sessions': instance.sessions,
|
||||||
|
'activeServerId': instance.activeServerId,
|
||||||
|
};
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import 'auth.dart';
|
||||||
|
import 'danmaku.dart';
|
||||||
|
import 'script_widget.dart';
|
||||||
|
import 'server.dart';
|
||||||
|
|
||||||
|
part 'backup.freezed.dart';
|
||||||
|
part 'backup.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class BackupBundle with _$BackupBundle {
|
||||||
|
const factory BackupBundle({
|
||||||
|
required int version,
|
||||||
|
required String exportedAt,
|
||||||
|
required List<EmbyServer> servers,
|
||||||
|
required List<SessionData> sessions,
|
||||||
|
@JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson)
|
||||||
|
@Default(<DanmakuSource>[])
|
||||||
|
List<DanmakuSource> danmakuSources,
|
||||||
|
@Default(<InstalledScriptWidget>[])
|
||||||
|
List<InstalledScriptWidget> scriptWidgets,
|
||||||
|
@Default(<ScriptWidgetSubscription>[])
|
||||||
|
List<ScriptWidgetSubscription> scriptWidgetSubscriptions,
|
||||||
|
}) = _BackupBundle;
|
||||||
|
|
||||||
|
factory BackupBundle.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$BackupBundleFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DanmakuSource> _danmakuSourcesFromJson(Object? raw) {
|
||||||
|
if (raw is! List) return const [];
|
||||||
|
final out = <DanmakuSource>[];
|
||||||
|
for (final item in raw) {
|
||||||
|
if (item is Map<String, dynamic>) {
|
||||||
|
final source = DanmakuSource.fromJson(item);
|
||||||
|
if (source.url.isNotEmpty) out.add(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _danmakuSourcesToJson(List<DanmakuSource> sources) {
|
||||||
|
return [
|
||||||
|
for (final s in sources)
|
||||||
|
if (s.url.trim().isNotEmpty) {'url': s.url.trim(), 'name': s.name},
|
||||||
|
];
|
||||||
|
}
|
||||||
Generated
+325
@@ -0,0 +1,325 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'backup.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$BackupBundle {
|
||||||
|
|
||||||
|
int get version; String get exportedAt; List<EmbyServer> get servers; List<SessionData> get sessions;@JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) List<DanmakuSource> get danmakuSources; List<InstalledScriptWidget> get scriptWidgets; List<ScriptWidgetSubscription> get scriptWidgetSubscriptions;
|
||||||
|
/// Create a copy of BackupBundle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$BackupBundleCopyWith<BackupBundle> get copyWith => _$BackupBundleCopyWithImpl<BackupBundle>(this as BackupBundle, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this BackupBundle to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is BackupBundle&&(identical(other.version, version) || other.version == version)&&(identical(other.exportedAt, exportedAt) || other.exportedAt == exportedAt)&&const DeepCollectionEquality().equals(other.servers, servers)&&const DeepCollectionEquality().equals(other.sessions, sessions)&&const DeepCollectionEquality().equals(other.danmakuSources, danmakuSources)&&const DeepCollectionEquality().equals(other.scriptWidgets, scriptWidgets)&&const DeepCollectionEquality().equals(other.scriptWidgetSubscriptions, scriptWidgetSubscriptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,version,exportedAt,const DeepCollectionEquality().hash(servers),const DeepCollectionEquality().hash(sessions),const DeepCollectionEquality().hash(danmakuSources),const DeepCollectionEquality().hash(scriptWidgets),const DeepCollectionEquality().hash(scriptWidgetSubscriptions));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'BackupBundle(version: $version, exportedAt: $exportedAt, servers: $servers, sessions: $sessions, danmakuSources: $danmakuSources, scriptWidgets: $scriptWidgets, scriptWidgetSubscriptions: $scriptWidgetSubscriptions)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $BackupBundleCopyWith<$Res> {
|
||||||
|
factory $BackupBundleCopyWith(BackupBundle value, $Res Function(BackupBundle) _then) = _$BackupBundleCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
int version, String exportedAt, List<EmbyServer> servers, List<SessionData> sessions,@JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) List<DanmakuSource> danmakuSources, List<InstalledScriptWidget> scriptWidgets, List<ScriptWidgetSubscription> scriptWidgetSubscriptions
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$BackupBundleCopyWithImpl<$Res>
|
||||||
|
implements $BackupBundleCopyWith<$Res> {
|
||||||
|
_$BackupBundleCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final BackupBundle _self;
|
||||||
|
final $Res Function(BackupBundle) _then;
|
||||||
|
|
||||||
|
/// Create a copy of BackupBundle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? version = null,Object? exportedAt = null,Object? servers = null,Object? sessions = null,Object? danmakuSources = null,Object? scriptWidgets = null,Object? scriptWidgetSubscriptions = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,exportedAt: null == exportedAt ? _self.exportedAt : exportedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,servers: null == servers ? _self.servers : servers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<EmbyServer>,sessions: null == sessions ? _self.sessions : sessions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SessionData>,danmakuSources: null == danmakuSources ? _self.danmakuSources : danmakuSources // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<DanmakuSource>,scriptWidgets: null == scriptWidgets ? _self.scriptWidgets : scriptWidgets // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<InstalledScriptWidget>,scriptWidgetSubscriptions: null == scriptWidgetSubscriptions ? _self.scriptWidgetSubscriptions : scriptWidgetSubscriptions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<ScriptWidgetSubscription>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [BackupBundle].
|
||||||
|
extension BackupBundlePatterns on BackupBundle {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _BackupBundle value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _BackupBundle() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _BackupBundle value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _BackupBundle():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _BackupBundle value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _BackupBundle() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int version, String exportedAt, List<EmbyServer> servers, List<SessionData> sessions, @JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) List<DanmakuSource> danmakuSources, List<InstalledScriptWidget> scriptWidgets, List<ScriptWidgetSubscription> scriptWidgetSubscriptions)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _BackupBundle() when $default != null:
|
||||||
|
return $default(_that.version,_that.exportedAt,_that.servers,_that.sessions,_that.danmakuSources,_that.scriptWidgets,_that.scriptWidgetSubscriptions);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int version, String exportedAt, List<EmbyServer> servers, List<SessionData> sessions, @JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) List<DanmakuSource> danmakuSources, List<InstalledScriptWidget> scriptWidgets, List<ScriptWidgetSubscription> scriptWidgetSubscriptions) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _BackupBundle():
|
||||||
|
return $default(_that.version,_that.exportedAt,_that.servers,_that.sessions,_that.danmakuSources,_that.scriptWidgets,_that.scriptWidgetSubscriptions);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int version, String exportedAt, List<EmbyServer> servers, List<SessionData> sessions, @JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) List<DanmakuSource> danmakuSources, List<InstalledScriptWidget> scriptWidgets, List<ScriptWidgetSubscription> scriptWidgetSubscriptions)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _BackupBundle() when $default != null:
|
||||||
|
return $default(_that.version,_that.exportedAt,_that.servers,_that.sessions,_that.danmakuSources,_that.scriptWidgets,_that.scriptWidgetSubscriptions);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _BackupBundle implements BackupBundle {
|
||||||
|
const _BackupBundle({required this.version, required this.exportedAt, required final List<EmbyServer> servers, required final List<SessionData> sessions, @JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) final List<DanmakuSource> danmakuSources = const <DanmakuSource>[], final List<InstalledScriptWidget> scriptWidgets = const <InstalledScriptWidget>[], final List<ScriptWidgetSubscription> scriptWidgetSubscriptions = const <ScriptWidgetSubscription>[]}): _servers = servers,_sessions = sessions,_danmakuSources = danmakuSources,_scriptWidgets = scriptWidgets,_scriptWidgetSubscriptions = scriptWidgetSubscriptions;
|
||||||
|
factory _BackupBundle.fromJson(Map<String, dynamic> json) => _$BackupBundleFromJson(json);
|
||||||
|
|
||||||
|
@override final int version;
|
||||||
|
@override final String exportedAt;
|
||||||
|
final List<EmbyServer> _servers;
|
||||||
|
@override List<EmbyServer> get servers {
|
||||||
|
if (_servers is EqualUnmodifiableListView) return _servers;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<SessionData> _sessions;
|
||||||
|
@override List<SessionData> get sessions {
|
||||||
|
if (_sessions is EqualUnmodifiableListView) return _sessions;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<DanmakuSource> _danmakuSources;
|
||||||
|
@override@JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) List<DanmakuSource> get danmakuSources {
|
||||||
|
if (_danmakuSources is EqualUnmodifiableListView) return _danmakuSources;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_danmakuSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<InstalledScriptWidget> _scriptWidgets;
|
||||||
|
@override@JsonKey() List<InstalledScriptWidget> get scriptWidgets {
|
||||||
|
if (_scriptWidgets is EqualUnmodifiableListView) return _scriptWidgets;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_scriptWidgets);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ScriptWidgetSubscription> _scriptWidgetSubscriptions;
|
||||||
|
@override@JsonKey() List<ScriptWidgetSubscription> get scriptWidgetSubscriptions {
|
||||||
|
if (_scriptWidgetSubscriptions is EqualUnmodifiableListView) return _scriptWidgetSubscriptions;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_scriptWidgetSubscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of BackupBundle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$BackupBundleCopyWith<_BackupBundle> get copyWith => __$BackupBundleCopyWithImpl<_BackupBundle>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$BackupBundleToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BackupBundle&&(identical(other.version, version) || other.version == version)&&(identical(other.exportedAt, exportedAt) || other.exportedAt == exportedAt)&&const DeepCollectionEquality().equals(other._servers, _servers)&&const DeepCollectionEquality().equals(other._sessions, _sessions)&&const DeepCollectionEquality().equals(other._danmakuSources, _danmakuSources)&&const DeepCollectionEquality().equals(other._scriptWidgets, _scriptWidgets)&&const DeepCollectionEquality().equals(other._scriptWidgetSubscriptions, _scriptWidgetSubscriptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,version,exportedAt,const DeepCollectionEquality().hash(_servers),const DeepCollectionEquality().hash(_sessions),const DeepCollectionEquality().hash(_danmakuSources),const DeepCollectionEquality().hash(_scriptWidgets),const DeepCollectionEquality().hash(_scriptWidgetSubscriptions));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'BackupBundle(version: $version, exportedAt: $exportedAt, servers: $servers, sessions: $sessions, danmakuSources: $danmakuSources, scriptWidgets: $scriptWidgets, scriptWidgetSubscriptions: $scriptWidgetSubscriptions)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$BackupBundleCopyWith<$Res> implements $BackupBundleCopyWith<$Res> {
|
||||||
|
factory _$BackupBundleCopyWith(_BackupBundle value, $Res Function(_BackupBundle) _then) = __$BackupBundleCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
int version, String exportedAt, List<EmbyServer> servers, List<SessionData> sessions,@JsonKey(fromJson: _danmakuSourcesFromJson, toJson: _danmakuSourcesToJson) List<DanmakuSource> danmakuSources, List<InstalledScriptWidget> scriptWidgets, List<ScriptWidgetSubscription> scriptWidgetSubscriptions
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$BackupBundleCopyWithImpl<$Res>
|
||||||
|
implements _$BackupBundleCopyWith<$Res> {
|
||||||
|
__$BackupBundleCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _BackupBundle _self;
|
||||||
|
final $Res Function(_BackupBundle) _then;
|
||||||
|
|
||||||
|
/// Create a copy of BackupBundle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? version = null,Object? exportedAt = null,Object? servers = null,Object? sessions = null,Object? danmakuSources = null,Object? scriptWidgets = null,Object? scriptWidgetSubscriptions = null,}) {
|
||||||
|
return _then(_BackupBundle(
|
||||||
|
version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,exportedAt: null == exportedAt ? _self.exportedAt : exportedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,servers: null == servers ? _self._servers : servers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<EmbyServer>,sessions: null == sessions ? _self._sessions : sessions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SessionData>,danmakuSources: null == danmakuSources ? _self._danmakuSources : danmakuSources // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<DanmakuSource>,scriptWidgets: null == scriptWidgets ? _self._scriptWidgets : scriptWidgets // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<InstalledScriptWidget>,scriptWidgetSubscriptions: null == scriptWidgetSubscriptions ? _self._scriptWidgetSubscriptions : scriptWidgetSubscriptions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<ScriptWidgetSubscription>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
Generated
+50
@@ -0,0 +1,50 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'backup.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_BackupBundle _$BackupBundleFromJson(Map<String, dynamic> json) =>
|
||||||
|
_BackupBundle(
|
||||||
|
version: (json['version'] as num).toInt(),
|
||||||
|
exportedAt: json['exportedAt'] as String,
|
||||||
|
servers: (json['servers'] as List<dynamic>)
|
||||||
|
.map((e) => EmbyServer.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
sessions: (json['sessions'] as List<dynamic>)
|
||||||
|
.map((e) => SessionData.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
danmakuSources: json['danmakuSources'] == null
|
||||||
|
? const <DanmakuSource>[]
|
||||||
|
: _danmakuSourcesFromJson(json['danmakuSources']),
|
||||||
|
scriptWidgets:
|
||||||
|
(json['scriptWidgets'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) =>
|
||||||
|
InstalledScriptWidget.fromJson(e as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
const <InstalledScriptWidget>[],
|
||||||
|
scriptWidgetSubscriptions:
|
||||||
|
(json['scriptWidgetSubscriptions'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) => ScriptWidgetSubscription.fromJson(
|
||||||
|
e as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptWidgetSubscription>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$BackupBundleToJson(_BackupBundle instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'version': instance.version,
|
||||||
|
'exportedAt': instance.exportedAt,
|
||||||
|
'servers': instance.servers,
|
||||||
|
'sessions': instance.sessions,
|
||||||
|
'danmakuSources': _danmakuSourcesToJson(instance.danmakuSources),
|
||||||
|
'scriptWidgets': instance.scriptWidgets,
|
||||||
|
'scriptWidgetSubscriptions': instance.scriptWidgetSubscriptions,
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
class CancelledException implements Exception {
|
||||||
|
final String? reason;
|
||||||
|
const CancelledException([this.reason]);
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
reason == null ? 'CancelledException' : 'CancelledException: $reason';
|
||||||
|
}
|
||||||
|
|
||||||
|
class CancellationToken {
|
||||||
|
final Completer<void> _completer = Completer<void>();
|
||||||
|
String? _reason;
|
||||||
|
|
||||||
|
bool get isCancelled => _completer.isCompleted;
|
||||||
|
String? get reason => _reason;
|
||||||
|
Future<void> get whenCancelled => _completer.future;
|
||||||
|
|
||||||
|
void cancel([String? reason]) {
|
||||||
|
if (_completer.isCompleted) return;
|
||||||
|
_reason = reason;
|
||||||
|
_completer.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
void throwIfCancelled() {
|
||||||
|
if (isCancelled) throw CancelledException(_reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export 'auth.dart';
|
||||||
|
export 'backup.dart';
|
||||||
|
export 'cancellation.dart';
|
||||||
|
export 'error.dart';
|
||||||
|
export 'library.dart';
|
||||||
|
export 'playback.dart';
|
||||||
|
export 'server.dart';
|
||||||
|
export 'tmdb.dart';
|
||||||
|
export 'danmaku.dart';
|
||||||
|
export 'icon_library.dart';
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
import 'package:flutter/painting.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'danmaku.freezed.dart';
|
||||||
|
part 'danmaku.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class DanmakuPanelSettings {
|
||||||
|
final bool enabled;
|
||||||
|
final double opacity;
|
||||||
|
final double area;
|
||||||
|
final double fontSize;
|
||||||
|
final double speed;
|
||||||
|
final double offset;
|
||||||
|
final bool showTop;
|
||||||
|
final bool showScroll;
|
||||||
|
final bool showBottom;
|
||||||
|
final bool colored;
|
||||||
|
final double strokeWidth;
|
||||||
|
final bool bold;
|
||||||
|
final String? fontFamily;
|
||||||
|
final double lineHeight;
|
||||||
|
final bool fixedToScroll;
|
||||||
|
final int maxRows;
|
||||||
|
final bool heatmapEnabled;
|
||||||
|
|
||||||
|
const DanmakuPanelSettings({
|
||||||
|
this.enabled = true,
|
||||||
|
this.opacity = 1.0,
|
||||||
|
this.area = 0.85,
|
||||||
|
this.fontSize = 20,
|
||||||
|
this.speed = 1.0,
|
||||||
|
this.offset = 0.0,
|
||||||
|
this.showTop = true,
|
||||||
|
this.showScroll = true,
|
||||||
|
this.showBottom = false,
|
||||||
|
this.colored = true,
|
||||||
|
this.strokeWidth = 1.5,
|
||||||
|
this.bold = false,
|
||||||
|
this.fontFamily,
|
||||||
|
this.lineHeight = 1.6,
|
||||||
|
this.fixedToScroll = false,
|
||||||
|
this.maxRows = 0,
|
||||||
|
this.heatmapEnabled = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
DanmakuPanelSettings copyWith({
|
||||||
|
bool? enabled,
|
||||||
|
double? opacity,
|
||||||
|
double? area,
|
||||||
|
double? fontSize,
|
||||||
|
double? speed,
|
||||||
|
double? offset,
|
||||||
|
bool? showTop,
|
||||||
|
bool? showScroll,
|
||||||
|
bool? showBottom,
|
||||||
|
bool? colored,
|
||||||
|
double? strokeWidth,
|
||||||
|
bool? bold,
|
||||||
|
Object? fontFamily = _sentinel,
|
||||||
|
double? lineHeight,
|
||||||
|
bool? fixedToScroll,
|
||||||
|
int? maxRows,
|
||||||
|
bool? heatmapEnabled,
|
||||||
|
}) => DanmakuPanelSettings(
|
||||||
|
enabled: enabled ?? this.enabled,
|
||||||
|
opacity: opacity ?? this.opacity,
|
||||||
|
area: area ?? this.area,
|
||||||
|
fontSize: fontSize ?? this.fontSize,
|
||||||
|
speed: speed ?? this.speed,
|
||||||
|
offset: offset ?? this.offset,
|
||||||
|
showTop: showTop ?? this.showTop,
|
||||||
|
showScroll: showScroll ?? this.showScroll,
|
||||||
|
showBottom: showBottom ?? this.showBottom,
|
||||||
|
colored: colored ?? this.colored,
|
||||||
|
strokeWidth: strokeWidth ?? this.strokeWidth,
|
||||||
|
bold: bold ?? this.bold,
|
||||||
|
fontFamily: fontFamily == _sentinel
|
||||||
|
? this.fontFamily
|
||||||
|
: fontFamily as String?,
|
||||||
|
lineHeight: lineHeight ?? this.lineHeight,
|
||||||
|
fixedToScroll: fixedToScroll ?? this.fixedToScroll,
|
||||||
|
maxRows: maxRows ?? this.maxRows,
|
||||||
|
heatmapEnabled: heatmapEnabled ?? this.heatmapEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
other is DanmakuPanelSettings &&
|
||||||
|
enabled == other.enabled &&
|
||||||
|
opacity == other.opacity &&
|
||||||
|
area == other.area &&
|
||||||
|
fontSize == other.fontSize &&
|
||||||
|
speed == other.speed &&
|
||||||
|
offset == other.offset &&
|
||||||
|
showTop == other.showTop &&
|
||||||
|
showScroll == other.showScroll &&
|
||||||
|
showBottom == other.showBottom &&
|
||||||
|
colored == other.colored &&
|
||||||
|
strokeWidth == other.strokeWidth &&
|
||||||
|
bold == other.bold &&
|
||||||
|
fontFamily == other.fontFamily &&
|
||||||
|
lineHeight == other.lineHeight &&
|
||||||
|
fixedToScroll == other.fixedToScroll &&
|
||||||
|
maxRows == other.maxRows &&
|
||||||
|
heatmapEnabled == other.heatmapEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
enabled,
|
||||||
|
opacity,
|
||||||
|
area,
|
||||||
|
fontSize,
|
||||||
|
speed,
|
||||||
|
offset,
|
||||||
|
showTop,
|
||||||
|
showScroll,
|
||||||
|
showBottom,
|
||||||
|
colored,
|
||||||
|
strokeWidth,
|
||||||
|
bold,
|
||||||
|
fontFamily,
|
||||||
|
lineHeight,
|
||||||
|
fixedToScroll,
|
||||||
|
maxRows,
|
||||||
|
heatmapEnabled,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Object _sentinel = Object();
|
||||||
|
|
||||||
|
class DanmakuSource {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String url;
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
|
const DanmakuSource({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.url,
|
||||||
|
this.enabled = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
DanmakuSource copyWith({
|
||||||
|
String? id,
|
||||||
|
String? name,
|
||||||
|
String? url,
|
||||||
|
bool? enabled,
|
||||||
|
}) => DanmakuSource(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
url: url ?? this.url,
|
||||||
|
enabled: enabled ?? this.enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
String get displayName {
|
||||||
|
final trimmed = name.trim();
|
||||||
|
if (trimmed.isNotEmpty) return trimmed;
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'url': url,
|
||||||
|
'enabled': enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
factory DanmakuSource.fromJson(Map<String, dynamic> json) {
|
||||||
|
final url = (json['url'] ?? '').toString().trim();
|
||||||
|
return DanmakuSource(
|
||||||
|
id: (json['id'] ?? url).toString(),
|
||||||
|
name: (json['name'] ?? '').toString(),
|
||||||
|
url: url,
|
||||||
|
enabled: json['enabled'] is bool ? json['enabled'] as bool : true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
other is DanmakuSource &&
|
||||||
|
id == other.id &&
|
||||||
|
name == other.name &&
|
||||||
|
url == other.url &&
|
||||||
|
enabled == other.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(id, name, url, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum DanmakuCommentStatus { loading, ready, failed }
|
||||||
|
|
||||||
|
|
||||||
|
sealed class DanmakuSessionState {
|
||||||
|
const DanmakuSessionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DanmakuSessionIdle extends DanmakuSessionState {
|
||||||
|
const DanmakuSessionIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DanmakuSessionMatching extends DanmakuSessionState {
|
||||||
|
const DanmakuSessionMatching();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DanmakuSessionMatched extends DanmakuSessionState {
|
||||||
|
final List<DanmakuMatchCandidate> matches;
|
||||||
|
final int selectedIndex;
|
||||||
|
final DanmakuCommentStatus commentStatus;
|
||||||
|
final List<DanmakuComment> comments;
|
||||||
|
|
||||||
|
const DanmakuSessionMatched({
|
||||||
|
required this.matches,
|
||||||
|
required this.selectedIndex,
|
||||||
|
required this.commentStatus,
|
||||||
|
this.comments = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
DanmakuSessionMatched copyWith({
|
||||||
|
List<DanmakuMatchCandidate>? matches,
|
||||||
|
int? selectedIndex,
|
||||||
|
DanmakuCommentStatus? commentStatus,
|
||||||
|
List<DanmakuComment>? comments,
|
||||||
|
}) => DanmakuSessionMatched(
|
||||||
|
matches: matches ?? this.matches,
|
||||||
|
selectedIndex: selectedIndex ?? this.selectedIndex,
|
||||||
|
commentStatus: commentStatus ?? this.commentStatus,
|
||||||
|
comments: comments ?? this.comments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DanmakuSessionEmpty extends DanmakuSessionState {
|
||||||
|
const DanmakuSessionEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DanmakuSessionFailed extends DanmakuSessionState {
|
||||||
|
final String message;
|
||||||
|
const DanmakuSessionFailed(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class DanmakuMatchContext with _$DanmakuMatchContext {
|
||||||
|
const factory DanmakuMatchContext({
|
||||||
|
required String itemId,
|
||||||
|
required String name,
|
||||||
|
required String? type,
|
||||||
|
required String? seriesName,
|
||||||
|
required int? season,
|
||||||
|
required int? episode,
|
||||||
|
required int durationSec,
|
||||||
|
}) = _DanmakuMatchContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class DanmakuMatch with _$DanmakuMatch {
|
||||||
|
const factory DanmakuMatch({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int episodeId,
|
||||||
|
@JsonKey(fromJson: _intOrZero) @Default(0) int animeId,
|
||||||
|
@Default('') String animeTitle,
|
||||||
|
@Default('') String episodeTitle,
|
||||||
|
}) = _DanmakuMatch;
|
||||||
|
|
||||||
|
factory DanmakuMatch.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DanmakuMatchFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DanmakuMatchCandidate {
|
||||||
|
final DanmakuSource source;
|
||||||
|
final DanmakuMatch match;
|
||||||
|
|
||||||
|
const DanmakuMatchCandidate({required this.source, required this.match});
|
||||||
|
|
||||||
|
int get episodeId => match.episodeId;
|
||||||
|
int get animeId => match.animeId;
|
||||||
|
String get animeTitle => match.animeTitle;
|
||||||
|
String get episodeTitle => match.episodeTitle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
other is DanmakuMatchCandidate &&
|
||||||
|
source == other.source &&
|
||||||
|
match == other.match;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(source, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class DanmakuBangumiEpisode with _$DanmakuBangumiEpisode {
|
||||||
|
const factory DanmakuBangumiEpisode({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int episodeId,
|
||||||
|
@Default('') String episodeTitle,
|
||||||
|
@JsonKey(fromJson: _stringFromAny) @Default('') String episodeNumber,
|
||||||
|
}) = _DanmakuBangumiEpisode;
|
||||||
|
|
||||||
|
factory DanmakuBangumiEpisode.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DanmakuBangumiEpisodeFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _intRequired(Object? value) => (value as num).toInt();
|
||||||
|
|
||||||
|
int _intOrZero(Object? value) => (value as num?)?.toInt() ?? 0;
|
||||||
|
|
||||||
|
String _stringFromAny(Object? value) => value?.toString() ?? '';
|
||||||
|
|
||||||
|
class DanmakuComment {
|
||||||
|
final int cid;
|
||||||
|
final double time;
|
||||||
|
final int type;
|
||||||
|
final Color color;
|
||||||
|
final String text;
|
||||||
|
int lane;
|
||||||
|
|
||||||
|
DanmakuComment({
|
||||||
|
required this.cid,
|
||||||
|
required this.time,
|
||||||
|
required this.type,
|
||||||
|
required this.color,
|
||||||
|
required this.text,
|
||||||
|
this.lane = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
factory DanmakuComment.fromRaw(int cid, String p, String m) {
|
||||||
|
final parts = p.split(',');
|
||||||
|
final time = parts.isNotEmpty ? (double.tryParse(parts[0]) ?? 0.0) : 0.0;
|
||||||
|
final type = parts.length > 1 ? (int.tryParse(parts[1]) ?? 1) : 1;
|
||||||
|
final colorInt = parts.length > 2
|
||||||
|
? (int.tryParse(parts[2]) ?? 0xFFFFFF)
|
||||||
|
: 0xFFFFFF;
|
||||||
|
return DanmakuComment(
|
||||||
|
cid: cid,
|
||||||
|
time: time,
|
||||||
|
type: type,
|
||||||
|
color: Color(0xFF000000 | (colorInt & 0xFFFFFF)),
|
||||||
|
text: m,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+830
@@ -0,0 +1,830 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'danmaku.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$DanmakuMatchContext {
|
||||||
|
|
||||||
|
String get itemId; String get name; String? get type; String? get seriesName; int? get season; int? get episode; int get durationSec;
|
||||||
|
/// Create a copy of DanmakuMatchContext
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DanmakuMatchContextCopyWith<DanmakuMatchContext> get copyWith => _$DanmakuMatchContextCopyWithImpl<DanmakuMatchContext>(this as DanmakuMatchContext, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is DanmakuMatchContext&&(identical(other.itemId, itemId) || other.itemId == itemId)&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.seriesName, seriesName) || other.seriesName == seriesName)&&(identical(other.season, season) || other.season == season)&&(identical(other.episode, episode) || other.episode == episode)&&(identical(other.durationSec, durationSec) || other.durationSec == durationSec));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,itemId,name,type,seriesName,season,episode,durationSec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DanmakuMatchContext(itemId: $itemId, name: $name, type: $type, seriesName: $seriesName, season: $season, episode: $episode, durationSec: $durationSec)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $DanmakuMatchContextCopyWith<$Res> {
|
||||||
|
factory $DanmakuMatchContextCopyWith(DanmakuMatchContext value, $Res Function(DanmakuMatchContext) _then) = _$DanmakuMatchContextCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String itemId, String name, String? type, String? seriesName, int? season, int? episode, int durationSec
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$DanmakuMatchContextCopyWithImpl<$Res>
|
||||||
|
implements $DanmakuMatchContextCopyWith<$Res> {
|
||||||
|
_$DanmakuMatchContextCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final DanmakuMatchContext _self;
|
||||||
|
final $Res Function(DanmakuMatchContext) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuMatchContext
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? itemId = null,Object? name = null,Object? type = freezed,Object? seriesName = freezed,Object? season = freezed,Object? episode = freezed,Object? durationSec = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
itemId: null == itemId ? _self.itemId : itemId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,seriesName: freezed == seriesName ? _self.seriesName : seriesName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,season: freezed == season ? _self.season : season // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,episode: freezed == episode ? _self.episode : episode // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,durationSec: null == durationSec ? _self.durationSec : durationSec // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [DanmakuMatchContext].
|
||||||
|
extension DanmakuMatchContextPatterns on DanmakuMatchContext {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DanmakuMatchContext value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatchContext() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DanmakuMatchContext value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatchContext():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DanmakuMatchContext value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatchContext() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String itemId, String name, String? type, String? seriesName, int? season, int? episode, int durationSec)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatchContext() when $default != null:
|
||||||
|
return $default(_that.itemId,_that.name,_that.type,_that.seriesName,_that.season,_that.episode,_that.durationSec);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String itemId, String name, String? type, String? seriesName, int? season, int? episode, int durationSec) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatchContext():
|
||||||
|
return $default(_that.itemId,_that.name,_that.type,_that.seriesName,_that.season,_that.episode,_that.durationSec);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String itemId, String name, String? type, String? seriesName, int? season, int? episode, int durationSec)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatchContext() when $default != null:
|
||||||
|
return $default(_that.itemId,_that.name,_that.type,_that.seriesName,_that.season,_that.episode,_that.durationSec);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _DanmakuMatchContext implements DanmakuMatchContext {
|
||||||
|
const _DanmakuMatchContext({required this.itemId, required this.name, required this.type, required this.seriesName, required this.season, required this.episode, required this.durationSec});
|
||||||
|
|
||||||
|
|
||||||
|
@override final String itemId;
|
||||||
|
@override final String name;
|
||||||
|
@override final String? type;
|
||||||
|
@override final String? seriesName;
|
||||||
|
@override final int? season;
|
||||||
|
@override final int? episode;
|
||||||
|
@override final int durationSec;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuMatchContext
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$DanmakuMatchContextCopyWith<_DanmakuMatchContext> get copyWith => __$DanmakuMatchContextCopyWithImpl<_DanmakuMatchContext>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DanmakuMatchContext&&(identical(other.itemId, itemId) || other.itemId == itemId)&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.seriesName, seriesName) || other.seriesName == seriesName)&&(identical(other.season, season) || other.season == season)&&(identical(other.episode, episode) || other.episode == episode)&&(identical(other.durationSec, durationSec) || other.durationSec == durationSec));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,itemId,name,type,seriesName,season,episode,durationSec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DanmakuMatchContext(itemId: $itemId, name: $name, type: $type, seriesName: $seriesName, season: $season, episode: $episode, durationSec: $durationSec)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$DanmakuMatchContextCopyWith<$Res> implements $DanmakuMatchContextCopyWith<$Res> {
|
||||||
|
factory _$DanmakuMatchContextCopyWith(_DanmakuMatchContext value, $Res Function(_DanmakuMatchContext) _then) = __$DanmakuMatchContextCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String itemId, String name, String? type, String? seriesName, int? season, int? episode, int durationSec
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$DanmakuMatchContextCopyWithImpl<$Res>
|
||||||
|
implements _$DanmakuMatchContextCopyWith<$Res> {
|
||||||
|
__$DanmakuMatchContextCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _DanmakuMatchContext _self;
|
||||||
|
final $Res Function(_DanmakuMatchContext) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuMatchContext
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? itemId = null,Object? name = null,Object? type = freezed,Object? seriesName = freezed,Object? season = freezed,Object? episode = freezed,Object? durationSec = null,}) {
|
||||||
|
return _then(_DanmakuMatchContext(
|
||||||
|
itemId: null == itemId ? _self.itemId : itemId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,seriesName: freezed == seriesName ? _self.seriesName : seriesName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,season: freezed == season ? _self.season : season // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,episode: freezed == episode ? _self.episode : episode // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,durationSec: null == durationSec ? _self.durationSec : durationSec // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$DanmakuMatch {
|
||||||
|
|
||||||
|
@JsonKey(fromJson: _intRequired) int get episodeId;@JsonKey(fromJson: _intOrZero) int get animeId; String get animeTitle; String get episodeTitle;
|
||||||
|
/// Create a copy of DanmakuMatch
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DanmakuMatchCopyWith<DanmakuMatch> get copyWith => _$DanmakuMatchCopyWithImpl<DanmakuMatch>(this as DanmakuMatch, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this DanmakuMatch to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is DanmakuMatch&&(identical(other.episodeId, episodeId) || other.episodeId == episodeId)&&(identical(other.animeId, animeId) || other.animeId == animeId)&&(identical(other.animeTitle, animeTitle) || other.animeTitle == animeTitle)&&(identical(other.episodeTitle, episodeTitle) || other.episodeTitle == episodeTitle));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,episodeId,animeId,animeTitle,episodeTitle);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DanmakuMatch(episodeId: $episodeId, animeId: $animeId, animeTitle: $animeTitle, episodeTitle: $episodeTitle)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $DanmakuMatchCopyWith<$Res> {
|
||||||
|
factory $DanmakuMatchCopyWith(DanmakuMatch value, $Res Function(DanmakuMatch) _then) = _$DanmakuMatchCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int episodeId,@JsonKey(fromJson: _intOrZero) int animeId, String animeTitle, String episodeTitle
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$DanmakuMatchCopyWithImpl<$Res>
|
||||||
|
implements $DanmakuMatchCopyWith<$Res> {
|
||||||
|
_$DanmakuMatchCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final DanmakuMatch _self;
|
||||||
|
final $Res Function(DanmakuMatch) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuMatch
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? episodeId = null,Object? animeId = null,Object? animeTitle = null,Object? episodeTitle = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
episodeId: null == episodeId ? _self.episodeId : episodeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,animeId: null == animeId ? _self.animeId : animeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,animeTitle: null == animeTitle ? _self.animeTitle : animeTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,episodeTitle: null == episodeTitle ? _self.episodeTitle : episodeTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [DanmakuMatch].
|
||||||
|
extension DanmakuMatchPatterns on DanmakuMatch {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DanmakuMatch value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatch() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DanmakuMatch value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatch():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DanmakuMatch value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatch() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int episodeId, @JsonKey(fromJson: _intOrZero) int animeId, String animeTitle, String episodeTitle)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatch() when $default != null:
|
||||||
|
return $default(_that.episodeId,_that.animeId,_that.animeTitle,_that.episodeTitle);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int episodeId, @JsonKey(fromJson: _intOrZero) int animeId, String animeTitle, String episodeTitle) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatch():
|
||||||
|
return $default(_that.episodeId,_that.animeId,_that.animeTitle,_that.episodeTitle);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(fromJson: _intRequired) int episodeId, @JsonKey(fromJson: _intOrZero) int animeId, String animeTitle, String episodeTitle)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuMatch() when $default != null:
|
||||||
|
return $default(_that.episodeId,_that.animeId,_that.animeTitle,_that.episodeTitle);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _DanmakuMatch implements DanmakuMatch {
|
||||||
|
const _DanmakuMatch({@JsonKey(fromJson: _intRequired) required this.episodeId, @JsonKey(fromJson: _intOrZero) this.animeId = 0, this.animeTitle = '', this.episodeTitle = ''});
|
||||||
|
factory _DanmakuMatch.fromJson(Map<String, dynamic> json) => _$DanmakuMatchFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey(fromJson: _intRequired) final int episodeId;
|
||||||
|
@override@JsonKey(fromJson: _intOrZero) final int animeId;
|
||||||
|
@override@JsonKey() final String animeTitle;
|
||||||
|
@override@JsonKey() final String episodeTitle;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuMatch
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$DanmakuMatchCopyWith<_DanmakuMatch> get copyWith => __$DanmakuMatchCopyWithImpl<_DanmakuMatch>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$DanmakuMatchToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DanmakuMatch&&(identical(other.episodeId, episodeId) || other.episodeId == episodeId)&&(identical(other.animeId, animeId) || other.animeId == animeId)&&(identical(other.animeTitle, animeTitle) || other.animeTitle == animeTitle)&&(identical(other.episodeTitle, episodeTitle) || other.episodeTitle == episodeTitle));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,episodeId,animeId,animeTitle,episodeTitle);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DanmakuMatch(episodeId: $episodeId, animeId: $animeId, animeTitle: $animeTitle, episodeTitle: $episodeTitle)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$DanmakuMatchCopyWith<$Res> implements $DanmakuMatchCopyWith<$Res> {
|
||||||
|
factory _$DanmakuMatchCopyWith(_DanmakuMatch value, $Res Function(_DanmakuMatch) _then) = __$DanmakuMatchCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int episodeId,@JsonKey(fromJson: _intOrZero) int animeId, String animeTitle, String episodeTitle
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$DanmakuMatchCopyWithImpl<$Res>
|
||||||
|
implements _$DanmakuMatchCopyWith<$Res> {
|
||||||
|
__$DanmakuMatchCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _DanmakuMatch _self;
|
||||||
|
final $Res Function(_DanmakuMatch) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuMatch
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? episodeId = null,Object? animeId = null,Object? animeTitle = null,Object? episodeTitle = null,}) {
|
||||||
|
return _then(_DanmakuMatch(
|
||||||
|
episodeId: null == episodeId ? _self.episodeId : episodeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,animeId: null == animeId ? _self.animeId : animeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,animeTitle: null == animeTitle ? _self.animeTitle : animeTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,episodeTitle: null == episodeTitle ? _self.episodeTitle : episodeTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$DanmakuBangumiEpisode {
|
||||||
|
|
||||||
|
@JsonKey(fromJson: _intRequired) int get episodeId; String get episodeTitle;@JsonKey(fromJson: _stringFromAny) String get episodeNumber;
|
||||||
|
/// Create a copy of DanmakuBangumiEpisode
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DanmakuBangumiEpisodeCopyWith<DanmakuBangumiEpisode> get copyWith => _$DanmakuBangumiEpisodeCopyWithImpl<DanmakuBangumiEpisode>(this as DanmakuBangumiEpisode, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this DanmakuBangumiEpisode to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is DanmakuBangumiEpisode&&(identical(other.episodeId, episodeId) || other.episodeId == episodeId)&&(identical(other.episodeTitle, episodeTitle) || other.episodeTitle == episodeTitle)&&(identical(other.episodeNumber, episodeNumber) || other.episodeNumber == episodeNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,episodeId,episodeTitle,episodeNumber);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DanmakuBangumiEpisode(episodeId: $episodeId, episodeTitle: $episodeTitle, episodeNumber: $episodeNumber)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $DanmakuBangumiEpisodeCopyWith<$Res> {
|
||||||
|
factory $DanmakuBangumiEpisodeCopyWith(DanmakuBangumiEpisode value, $Res Function(DanmakuBangumiEpisode) _then) = _$DanmakuBangumiEpisodeCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int episodeId, String episodeTitle,@JsonKey(fromJson: _stringFromAny) String episodeNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$DanmakuBangumiEpisodeCopyWithImpl<$Res>
|
||||||
|
implements $DanmakuBangumiEpisodeCopyWith<$Res> {
|
||||||
|
_$DanmakuBangumiEpisodeCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final DanmakuBangumiEpisode _self;
|
||||||
|
final $Res Function(DanmakuBangumiEpisode) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuBangumiEpisode
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? episodeId = null,Object? episodeTitle = null,Object? episodeNumber = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
episodeId: null == episodeId ? _self.episodeId : episodeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,episodeTitle: null == episodeTitle ? _self.episodeTitle : episodeTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,episodeNumber: null == episodeNumber ? _self.episodeNumber : episodeNumber // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [DanmakuBangumiEpisode].
|
||||||
|
extension DanmakuBangumiEpisodePatterns on DanmakuBangumiEpisode {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DanmakuBangumiEpisode value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuBangumiEpisode() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DanmakuBangumiEpisode value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuBangumiEpisode():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DanmakuBangumiEpisode value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuBangumiEpisode() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int episodeId, String episodeTitle, @JsonKey(fromJson: _stringFromAny) String episodeNumber)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuBangumiEpisode() when $default != null:
|
||||||
|
return $default(_that.episodeId,_that.episodeTitle,_that.episodeNumber);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int episodeId, String episodeTitle, @JsonKey(fromJson: _stringFromAny) String episodeNumber) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuBangumiEpisode():
|
||||||
|
return $default(_that.episodeId,_that.episodeTitle,_that.episodeNumber);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(fromJson: _intRequired) int episodeId, String episodeTitle, @JsonKey(fromJson: _stringFromAny) String episodeNumber)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _DanmakuBangumiEpisode() when $default != null:
|
||||||
|
return $default(_that.episodeId,_that.episodeTitle,_that.episodeNumber);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _DanmakuBangumiEpisode implements DanmakuBangumiEpisode {
|
||||||
|
const _DanmakuBangumiEpisode({@JsonKey(fromJson: _intRequired) required this.episodeId, this.episodeTitle = '', @JsonKey(fromJson: _stringFromAny) this.episodeNumber = ''});
|
||||||
|
factory _DanmakuBangumiEpisode.fromJson(Map<String, dynamic> json) => _$DanmakuBangumiEpisodeFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey(fromJson: _intRequired) final int episodeId;
|
||||||
|
@override@JsonKey() final String episodeTitle;
|
||||||
|
@override@JsonKey(fromJson: _stringFromAny) final String episodeNumber;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuBangumiEpisode
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$DanmakuBangumiEpisodeCopyWith<_DanmakuBangumiEpisode> get copyWith => __$DanmakuBangumiEpisodeCopyWithImpl<_DanmakuBangumiEpisode>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$DanmakuBangumiEpisodeToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DanmakuBangumiEpisode&&(identical(other.episodeId, episodeId) || other.episodeId == episodeId)&&(identical(other.episodeTitle, episodeTitle) || other.episodeTitle == episodeTitle)&&(identical(other.episodeNumber, episodeNumber) || other.episodeNumber == episodeNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,episodeId,episodeTitle,episodeNumber);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DanmakuBangumiEpisode(episodeId: $episodeId, episodeTitle: $episodeTitle, episodeNumber: $episodeNumber)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$DanmakuBangumiEpisodeCopyWith<$Res> implements $DanmakuBangumiEpisodeCopyWith<$Res> {
|
||||||
|
factory _$DanmakuBangumiEpisodeCopyWith(_DanmakuBangumiEpisode value, $Res Function(_DanmakuBangumiEpisode) _then) = __$DanmakuBangumiEpisodeCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int episodeId, String episodeTitle,@JsonKey(fromJson: _stringFromAny) String episodeNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$DanmakuBangumiEpisodeCopyWithImpl<$Res>
|
||||||
|
implements _$DanmakuBangumiEpisodeCopyWith<$Res> {
|
||||||
|
__$DanmakuBangumiEpisodeCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _DanmakuBangumiEpisode _self;
|
||||||
|
final $Res Function(_DanmakuBangumiEpisode) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DanmakuBangumiEpisode
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? episodeId = null,Object? episodeTitle = null,Object? episodeNumber = null,}) {
|
||||||
|
return _then(_DanmakuBangumiEpisode(
|
||||||
|
episodeId: null == episodeId ? _self.episodeId : episodeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,episodeTitle: null == episodeTitle ? _self.episodeTitle : episodeTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,episodeNumber: null == episodeNumber ? _self.episodeNumber : episodeNumber // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
Generated
+41
@@ -0,0 +1,41 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'danmaku.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_DanmakuMatch _$DanmakuMatchFromJson(Map<String, dynamic> json) =>
|
||||||
|
_DanmakuMatch(
|
||||||
|
episodeId: _intRequired(json['episodeId']),
|
||||||
|
animeId: json['animeId'] == null ? 0 : _intOrZero(json['animeId']),
|
||||||
|
animeTitle: json['animeTitle'] as String? ?? '',
|
||||||
|
episodeTitle: json['episodeTitle'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DanmakuMatchToJson(_DanmakuMatch instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'episodeId': instance.episodeId,
|
||||||
|
'animeId': instance.animeId,
|
||||||
|
'animeTitle': instance.animeTitle,
|
||||||
|
'episodeTitle': instance.episodeTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
_DanmakuBangumiEpisode _$DanmakuBangumiEpisodeFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _DanmakuBangumiEpisode(
|
||||||
|
episodeId: _intRequired(json['episodeId']),
|
||||||
|
episodeTitle: json['episodeTitle'] as String? ?? '',
|
||||||
|
episodeNumber: json['episodeNumber'] == null
|
||||||
|
? ''
|
||||||
|
: _stringFromAny(json['episodeNumber']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DanmakuBangumiEpisodeToJson(
|
||||||
|
_DanmakuBangumiEpisode instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'episodeId': instance.episodeId,
|
||||||
|
'episodeTitle': instance.episodeTitle,
|
||||||
|
'episodeNumber': instance.episodeNumber,
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
enum ErrorCode {
|
||||||
|
invalidParams('INVALID_PARAMS'),
|
||||||
|
serverUnreachable('SERVER_UNREACHABLE'),
|
||||||
|
serverTimeout('SERVER_TIMEOUT'),
|
||||||
|
serverHttpError('SERVER_HTTP_ERROR'),
|
||||||
|
authInvalidCredentials('AUTH_INVALID_CREDENTIALS'),
|
||||||
|
authForbidden('AUTH_FORBIDDEN'),
|
||||||
|
storeConflict('STORE_CONFLICT'),
|
||||||
|
storeNotFound('STORE_NOT_FOUND'),
|
||||||
|
internalError('INTERNAL_ERROR');
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
const ErrorCode(this.value);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'icon_library.freezed.dart';
|
||||||
|
part 'icon_library.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class IconEntry with _$IconEntry {
|
||||||
|
const factory IconEntry({
|
||||||
|
@JsonKey(fromJson: _stringOrEmpty) @Default('') String name,
|
||||||
|
@JsonKey(fromJson: _stringOrEmpty) @Default('') String url,
|
||||||
|
}) = _IconEntry;
|
||||||
|
|
||||||
|
factory IconEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$IconEntryFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class IconLibrary with _$IconLibrary {
|
||||||
|
const factory IconLibrary({
|
||||||
|
|
||||||
|
required String sourceUrl,
|
||||||
|
@JsonKey(fromJson: _stringOrEmpty) @Default('') String name,
|
||||||
|
@JsonKey(fromJson: _stringOrEmpty) @Default('') String description,
|
||||||
|
@Default(<IconEntry>[]) List<IconEntry> icons,
|
||||||
|
}) = _IconLibrary;
|
||||||
|
|
||||||
|
factory IconLibrary.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$IconLibraryFromJson(json);
|
||||||
|
|
||||||
|
|
||||||
|
static IconLibrary parse(String sourceUrl, Map<String, dynamic> json) {
|
||||||
|
final raw = json['icons'];
|
||||||
|
final iconsList = raw is List
|
||||||
|
? raw
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(IconEntry.fromJson)
|
||||||
|
.where((e) => e.url.isNotEmpty)
|
||||||
|
.toList()
|
||||||
|
: const <IconEntry>[];
|
||||||
|
return IconLibrary(
|
||||||
|
sourceUrl: sourceUrl,
|
||||||
|
name: (json['name'] as String?) ?? '',
|
||||||
|
description: (json['description'] as String?) ?? '',
|
||||||
|
icons: iconsList,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _stringOrEmpty(Object? value) => value?.toString() ?? '';
|
||||||
+560
@@ -0,0 +1,560 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'icon_library.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$IconEntry {
|
||||||
|
|
||||||
|
@JsonKey(fromJson: _stringOrEmpty) String get name;@JsonKey(fromJson: _stringOrEmpty) String get url;
|
||||||
|
/// Create a copy of IconEntry
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$IconEntryCopyWith<IconEntry> get copyWith => _$IconEntryCopyWithImpl<IconEntry>(this as IconEntry, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this IconEntry to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is IconEntry&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,name,url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'IconEntry(name: $name, url: $url)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $IconEntryCopyWith<$Res> {
|
||||||
|
factory $IconEntryCopyWith(IconEntry value, $Res Function(IconEntry) _then) = _$IconEntryCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _stringOrEmpty) String name,@JsonKey(fromJson: _stringOrEmpty) String url
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$IconEntryCopyWithImpl<$Res>
|
||||||
|
implements $IconEntryCopyWith<$Res> {
|
||||||
|
_$IconEntryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final IconEntry _self;
|
||||||
|
final $Res Function(IconEntry) _then;
|
||||||
|
|
||||||
|
/// Create a copy of IconEntry
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? url = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [IconEntry].
|
||||||
|
extension IconEntryPatterns on IconEntry {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _IconEntry value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconEntry() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _IconEntry value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconEntry():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _IconEntry value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconEntry() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _stringOrEmpty) String name, @JsonKey(fromJson: _stringOrEmpty) String url)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconEntry() when $default != null:
|
||||||
|
return $default(_that.name,_that.url);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _stringOrEmpty) String name, @JsonKey(fromJson: _stringOrEmpty) String url) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconEntry():
|
||||||
|
return $default(_that.name,_that.url);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(fromJson: _stringOrEmpty) String name, @JsonKey(fromJson: _stringOrEmpty) String url)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconEntry() when $default != null:
|
||||||
|
return $default(_that.name,_that.url);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _IconEntry implements IconEntry {
|
||||||
|
const _IconEntry({@JsonKey(fromJson: _stringOrEmpty) this.name = '', @JsonKey(fromJson: _stringOrEmpty) this.url = ''});
|
||||||
|
factory _IconEntry.fromJson(Map<String, dynamic> json) => _$IconEntryFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey(fromJson: _stringOrEmpty) final String name;
|
||||||
|
@override@JsonKey(fromJson: _stringOrEmpty) final String url;
|
||||||
|
|
||||||
|
/// Create a copy of IconEntry
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$IconEntryCopyWith<_IconEntry> get copyWith => __$IconEntryCopyWithImpl<_IconEntry>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$IconEntryToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _IconEntry&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,name,url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'IconEntry(name: $name, url: $url)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$IconEntryCopyWith<$Res> implements $IconEntryCopyWith<$Res> {
|
||||||
|
factory _$IconEntryCopyWith(_IconEntry value, $Res Function(_IconEntry) _then) = __$IconEntryCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _stringOrEmpty) String name,@JsonKey(fromJson: _stringOrEmpty) String url
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$IconEntryCopyWithImpl<$Res>
|
||||||
|
implements _$IconEntryCopyWith<$Res> {
|
||||||
|
__$IconEntryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _IconEntry _self;
|
||||||
|
final $Res Function(_IconEntry) _then;
|
||||||
|
|
||||||
|
/// Create a copy of IconEntry
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? url = null,}) {
|
||||||
|
return _then(_IconEntry(
|
||||||
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$IconLibrary {
|
||||||
|
|
||||||
|
/// 来源 URL(去除尾随斜杠后的标准化字符串)
|
||||||
|
String get sourceUrl;@JsonKey(fromJson: _stringOrEmpty) String get name;@JsonKey(fromJson: _stringOrEmpty) String get description; List<IconEntry> get icons;
|
||||||
|
/// Create a copy of IconLibrary
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$IconLibraryCopyWith<IconLibrary> get copyWith => _$IconLibraryCopyWithImpl<IconLibrary>(this as IconLibrary, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this IconLibrary to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is IconLibrary&&(identical(other.sourceUrl, sourceUrl) || other.sourceUrl == sourceUrl)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.icons, icons));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,sourceUrl,name,description,const DeepCollectionEquality().hash(icons));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'IconLibrary(sourceUrl: $sourceUrl, name: $name, description: $description, icons: $icons)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $IconLibraryCopyWith<$Res> {
|
||||||
|
factory $IconLibraryCopyWith(IconLibrary value, $Res Function(IconLibrary) _then) = _$IconLibraryCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String sourceUrl,@JsonKey(fromJson: _stringOrEmpty) String name,@JsonKey(fromJson: _stringOrEmpty) String description, List<IconEntry> icons
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$IconLibraryCopyWithImpl<$Res>
|
||||||
|
implements $IconLibraryCopyWith<$Res> {
|
||||||
|
_$IconLibraryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final IconLibrary _self;
|
||||||
|
final $Res Function(IconLibrary) _then;
|
||||||
|
|
||||||
|
/// Create a copy of IconLibrary
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? sourceUrl = null,Object? name = null,Object? description = null,Object? icons = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
sourceUrl: null == sourceUrl ? _self.sourceUrl : sourceUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,icons: null == icons ? _self.icons : icons // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<IconEntry>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [IconLibrary].
|
||||||
|
extension IconLibraryPatterns on IconLibrary {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _IconLibrary value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconLibrary() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _IconLibrary value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconLibrary():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _IconLibrary value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconLibrary() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String sourceUrl, @JsonKey(fromJson: _stringOrEmpty) String name, @JsonKey(fromJson: _stringOrEmpty) String description, List<IconEntry> icons)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconLibrary() when $default != null:
|
||||||
|
return $default(_that.sourceUrl,_that.name,_that.description,_that.icons);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String sourceUrl, @JsonKey(fromJson: _stringOrEmpty) String name, @JsonKey(fromJson: _stringOrEmpty) String description, List<IconEntry> icons) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconLibrary():
|
||||||
|
return $default(_that.sourceUrl,_that.name,_that.description,_that.icons);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String sourceUrl, @JsonKey(fromJson: _stringOrEmpty) String name, @JsonKey(fromJson: _stringOrEmpty) String description, List<IconEntry> icons)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _IconLibrary() when $default != null:
|
||||||
|
return $default(_that.sourceUrl,_that.name,_that.description,_that.icons);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _IconLibrary implements IconLibrary {
|
||||||
|
const _IconLibrary({required this.sourceUrl, @JsonKey(fromJson: _stringOrEmpty) this.name = '', @JsonKey(fromJson: _stringOrEmpty) this.description = '', final List<IconEntry> icons = const <IconEntry>[]}): _icons = icons;
|
||||||
|
factory _IconLibrary.fromJson(Map<String, dynamic> json) => _$IconLibraryFromJson(json);
|
||||||
|
|
||||||
|
/// 来源 URL(去除尾随斜杠后的标准化字符串)
|
||||||
|
@override final String sourceUrl;
|
||||||
|
@override@JsonKey(fromJson: _stringOrEmpty) final String name;
|
||||||
|
@override@JsonKey(fromJson: _stringOrEmpty) final String description;
|
||||||
|
final List<IconEntry> _icons;
|
||||||
|
@override@JsonKey() List<IconEntry> get icons {
|
||||||
|
if (_icons is EqualUnmodifiableListView) return _icons;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_icons);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of IconLibrary
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$IconLibraryCopyWith<_IconLibrary> get copyWith => __$IconLibraryCopyWithImpl<_IconLibrary>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$IconLibraryToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _IconLibrary&&(identical(other.sourceUrl, sourceUrl) || other.sourceUrl == sourceUrl)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._icons, _icons));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,sourceUrl,name,description,const DeepCollectionEquality().hash(_icons));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'IconLibrary(sourceUrl: $sourceUrl, name: $name, description: $description, icons: $icons)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$IconLibraryCopyWith<$Res> implements $IconLibraryCopyWith<$Res> {
|
||||||
|
factory _$IconLibraryCopyWith(_IconLibrary value, $Res Function(_IconLibrary) _then) = __$IconLibraryCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String sourceUrl,@JsonKey(fromJson: _stringOrEmpty) String name,@JsonKey(fromJson: _stringOrEmpty) String description, List<IconEntry> icons
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$IconLibraryCopyWithImpl<$Res>
|
||||||
|
implements _$IconLibraryCopyWith<$Res> {
|
||||||
|
__$IconLibraryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _IconLibrary _self;
|
||||||
|
final $Res Function(_IconLibrary) _then;
|
||||||
|
|
||||||
|
/// Create a copy of IconLibrary
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? sourceUrl = null,Object? name = null,Object? description = null,Object? icons = null,}) {
|
||||||
|
return _then(_IconLibrary(
|
||||||
|
sourceUrl: null == sourceUrl ? _self.sourceUrl : sourceUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,icons: null == icons ? _self._icons : icons // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<IconEntry>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
Generated
+36
@@ -0,0 +1,36 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'icon_library.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_IconEntry _$IconEntryFromJson(Map<String, dynamic> json) => _IconEntry(
|
||||||
|
name: json['name'] == null ? '' : _stringOrEmpty(json['name']),
|
||||||
|
url: json['url'] == null ? '' : _stringOrEmpty(json['url']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$IconEntryToJson(_IconEntry instance) =>
|
||||||
|
<String, dynamic>{'name': instance.name, 'url': instance.url};
|
||||||
|
|
||||||
|
_IconLibrary _$IconLibraryFromJson(Map<String, dynamic> json) => _IconLibrary(
|
||||||
|
sourceUrl: json['sourceUrl'] as String,
|
||||||
|
name: json['name'] == null ? '' : _stringOrEmpty(json['name']),
|
||||||
|
description: json['description'] == null
|
||||||
|
? ''
|
||||||
|
: _stringOrEmpty(json['description']),
|
||||||
|
icons:
|
||||||
|
(json['icons'] as List<dynamic>?)
|
||||||
|
?.map((e) => IconEntry.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <IconEntry>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$IconLibraryToJson(_IconLibrary instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'sourceUrl': instance.sourceUrl,
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
'icons': instance.icons,
|
||||||
|
};
|
||||||
@@ -0,0 +1,722 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'library.freezed.dart';
|
||||||
|
part 'library.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawUserData with _$EmbyRawUserData {
|
||||||
|
const factory EmbyRawUserData({
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false)
|
||||||
|
int? PlaybackPositionTicks,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? PlayCount,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? UnplayedItemCount,
|
||||||
|
@JsonKey(includeIfNull: false) bool? IsFavorite,
|
||||||
|
@JsonKey(includeIfNull: false) bool? Played,
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@Default(<String, dynamic>{})
|
||||||
|
Map<String, dynamic> extra,
|
||||||
|
}) = _EmbyRawUserData;
|
||||||
|
|
||||||
|
factory EmbyRawUserData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyRawUserDataFromJson(json).copyWith(extra: json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawItem with _$EmbyRawItem {
|
||||||
|
const factory EmbyRawItem({
|
||||||
|
required String Id,
|
||||||
|
required String Name,
|
||||||
|
@JsonKey(includeIfNull: false) String? Type,
|
||||||
|
@JsonKey(includeIfNull: false) String? Overview,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? ProductionYear,
|
||||||
|
@JsonKey(fromJson: _doubleOrNull, includeIfNull: false)
|
||||||
|
double? CommunityRating,
|
||||||
|
@JsonKey(includeIfNull: false) String? PremiereDate,
|
||||||
|
@JsonKey(includeIfNull: false) String? SeriesName,
|
||||||
|
@JsonKey(includeIfNull: false) String? SeriesId,
|
||||||
|
@JsonKey(includeIfNull: false) String? SeasonId,
|
||||||
|
@JsonKey(includeIfNull: false) String? SeasonName,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? IndexNumber,
|
||||||
|
@JsonKey(includeIfNull: false) String? ParentId,
|
||||||
|
@JsonKey(includeIfNull: false) String? PrimaryImageTag,
|
||||||
|
@JsonKey(fromJson: _stringMapOrNull, includeIfNull: false)
|
||||||
|
Map<String, String>? ImageTags,
|
||||||
|
@JsonKey(fromJson: _stringListOrNull, includeIfNull: false)
|
||||||
|
List<String>? BackdropImageTags,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? RunTimeTicks,
|
||||||
|
@JsonKey(includeIfNull: false) EmbyRawUserData? UserData,
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@Default(<String, dynamic>{})
|
||||||
|
Map<String, dynamic> extra,
|
||||||
|
}) = _EmbyRawItem;
|
||||||
|
|
||||||
|
factory EmbyRawItem.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyRawItemFromJson(json).copyWith(extra: json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawSeason with _$EmbyRawSeason {
|
||||||
|
const factory EmbyRawSeason({
|
||||||
|
required String Id,
|
||||||
|
required String Name,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? IndexNumber,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? ProductionYear,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? ChildCount,
|
||||||
|
@JsonKey(includeIfNull: false) String? PrimaryImageTag,
|
||||||
|
@JsonKey(fromJson: _stringMapOrNull, includeIfNull: false)
|
||||||
|
Map<String, String>? ImageTags,
|
||||||
|
}) = _EmbyRawSeason;
|
||||||
|
|
||||||
|
factory EmbyRawSeason.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyRawSeasonFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawLibrary with _$EmbyRawLibrary {
|
||||||
|
const factory EmbyRawLibrary({
|
||||||
|
required String Id,
|
||||||
|
required String Name,
|
||||||
|
@JsonKey(includeIfNull: false) String? CollectionType,
|
||||||
|
@JsonKey(fromJson: _stringMapOrNull, includeIfNull: false)
|
||||||
|
Map<String, String>? ImageTags,
|
||||||
|
}) = _EmbyRawLibrary;
|
||||||
|
|
||||||
|
factory EmbyRawLibrary.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyRawLibraryFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawLibraries with _$EmbyRawLibraries {
|
||||||
|
const factory EmbyRawLibraries({required List<EmbyRawLibrary> Items}) =
|
||||||
|
_EmbyRawLibraries;
|
||||||
|
|
||||||
|
factory EmbyRawLibraries.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyRawLibrariesFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawMediaStream with _$EmbyRawMediaStream {
|
||||||
|
const factory EmbyRawMediaStream({
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? Index,
|
||||||
|
@JsonKey(includeIfNull: false) String? Type,
|
||||||
|
@JsonKey(includeIfNull: false) String? Language,
|
||||||
|
@JsonKey(includeIfNull: false) String? Codec,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? Channels,
|
||||||
|
@JsonKey(includeIfNull: false) String? DisplayTitle,
|
||||||
|
@JsonKey(includeIfNull: false) String? Title,
|
||||||
|
@JsonKey(includeIfNull: false) bool? IsDefault,
|
||||||
|
@JsonKey(includeIfNull: false) bool? IsForced,
|
||||||
|
@JsonKey(includeIfNull: false) bool? IsExternal,
|
||||||
|
@JsonKey(includeIfNull: false) String? DeliveryMethod,
|
||||||
|
@JsonKey(includeIfNull: false) String? DeliveryUrl,
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@Default(<String, dynamic>{})
|
||||||
|
Map<String, dynamic> extra,
|
||||||
|
}) = _EmbyRawMediaStream;
|
||||||
|
|
||||||
|
factory EmbyRawMediaStream.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyRawMediaStreamFromJson(json).copyWith(extra: json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawMediaSource with _$EmbyRawMediaSource {
|
||||||
|
const factory EmbyRawMediaSource({
|
||||||
|
@JsonKey(includeIfNull: false) String? Id,
|
||||||
|
@JsonKey(includeIfNull: false) String? Name,
|
||||||
|
@JsonKey(includeIfNull: false) String? Container,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? Bitrate,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? Size,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? Width,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? Height,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false)
|
||||||
|
int? DefaultSubtitleStreamIndex,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false)
|
||||||
|
int? DefaultAudioStreamIndex,
|
||||||
|
@JsonKey(includeIfNull: false) List<EmbyRawMediaStream>? MediaStreams,
|
||||||
|
}) = _EmbyRawMediaSource;
|
||||||
|
|
||||||
|
factory EmbyRawMediaSource.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyRawMediaSourceFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class LibraryLatestReq with _$LibraryLatestReq {
|
||||||
|
const LibraryLatestReq._();
|
||||||
|
const factory LibraryLatestReq({required String parentId, int? limit}) =
|
||||||
|
_LibraryLatestReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'parentId': parentId,
|
||||||
|
if (limit != null) 'limit': limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class LibraryLatestRes with _$LibraryLatestRes {
|
||||||
|
const factory LibraryLatestRes({
|
||||||
|
required String parentId,
|
||||||
|
required List<EmbyRawItem> items,
|
||||||
|
@Default(0) int totalCount,
|
||||||
|
}) = _LibraryLatestRes;
|
||||||
|
|
||||||
|
factory LibraryLatestRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$LibraryLatestResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class LibraryResumeRes with _$LibraryResumeRes {
|
||||||
|
const factory LibraryResumeRes({required List<EmbyRawItem> Items}) =
|
||||||
|
_LibraryResumeRes;
|
||||||
|
|
||||||
|
factory LibraryResumeRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$LibraryResumeResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef LibraryViewsRes = EmbyRawLibraries;
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MediaDetailReq with _$MediaDetailReq {
|
||||||
|
const MediaDetailReq._();
|
||||||
|
const factory MediaDetailReq({required String itemId}) = _MediaDetailReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'itemId': itemId};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawMediaDetailSeriesExtra
|
||||||
|
with _$EmbyRawMediaDetailSeriesExtra {
|
||||||
|
const factory EmbyRawMediaDetailSeriesExtra({
|
||||||
|
required List<EmbyRawItem> nextUp,
|
||||||
|
required List<EmbyRawSeason> seasons,
|
||||||
|
}) = _EmbyRawMediaDetailSeriesExtra;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyRawMediaDetailSeriesContext
|
||||||
|
with _$EmbyRawMediaDetailSeriesContext {
|
||||||
|
const factory EmbyRawMediaDetailSeriesContext({
|
||||||
|
required EmbyRawItem seriesBase,
|
||||||
|
required EmbyRawMediaDetailSeriesExtra seriesExtra,
|
||||||
|
}) = _EmbyRawMediaDetailSeriesContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MediaDetailRes with _$MediaDetailRes {
|
||||||
|
const factory MediaDetailRes({
|
||||||
|
required EmbyRawItem base,
|
||||||
|
EmbyRawMediaDetailSeriesExtra? seriesExtra,
|
||||||
|
EmbyRawMediaDetailSeriesContext? seriesContext,
|
||||||
|
List<EmbyRawItem>? similarItems,
|
||||||
|
List<EmbyRawItem>? boxSetItems,
|
||||||
|
}) = _MediaDetailRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class FavoriteSetReq with _$FavoriteSetReq {
|
||||||
|
const FavoriteSetReq._();
|
||||||
|
const factory FavoriteSetReq({
|
||||||
|
required String itemId,
|
||||||
|
required bool isFavorite,
|
||||||
|
}) = _FavoriteSetReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'itemId': itemId, 'isFavorite': isFavorite};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class FavoriteSetRes with _$FavoriteSetRes {
|
||||||
|
const factory FavoriteSetRes({
|
||||||
|
required String itemId,
|
||||||
|
required bool isFavorite,
|
||||||
|
}) = _FavoriteSetRes;
|
||||||
|
|
||||||
|
factory FavoriteSetRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$FavoriteSetResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class PlayedSetReq with _$PlayedSetReq {
|
||||||
|
const PlayedSetReq._();
|
||||||
|
const factory PlayedSetReq({required String itemId, required bool played}) =
|
||||||
|
_PlayedSetReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'itemId': itemId, 'played': played};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class PlayedSetRes with _$PlayedSetRes {
|
||||||
|
const factory PlayedSetRes({required String itemId, required bool played}) =
|
||||||
|
_PlayedSetRes;
|
||||||
|
|
||||||
|
factory PlayedSetRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PlayedSetResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class HideFromResumeReq with _$HideFromResumeReq {
|
||||||
|
const HideFromResumeReq._();
|
||||||
|
const factory HideFromResumeReq({
|
||||||
|
required String itemId,
|
||||||
|
required bool hide,
|
||||||
|
}) = _HideFromResumeReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'itemId': itemId, 'hide': hide};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class HideFromResumeRes with _$HideFromResumeRes {
|
||||||
|
const factory HideFromResumeRes({
|
||||||
|
required String itemId,
|
||||||
|
required bool hide,
|
||||||
|
}) = _HideFromResumeRes;
|
||||||
|
|
||||||
|
factory HideFromResumeRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$HideFromResumeResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MediaEpisodesReq with _$MediaEpisodesReq {
|
||||||
|
const MediaEpisodesReq._();
|
||||||
|
const factory MediaEpisodesReq({
|
||||||
|
required String seriesId,
|
||||||
|
required String seasonId,
|
||||||
|
}) = _MediaEpisodesReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'seriesId': seriesId, 'seasonId': seasonId};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MediaEpisodesRes with _$MediaEpisodesRes {
|
||||||
|
const factory MediaEpisodesRes({
|
||||||
|
required String seriesId,
|
||||||
|
required String seasonId,
|
||||||
|
required List<EmbyRawItem> items,
|
||||||
|
}) = _MediaEpisodesRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SeriesSeasonsReq with _$SeriesSeasonsReq {
|
||||||
|
const SeriesSeasonsReq._();
|
||||||
|
const factory SeriesSeasonsReq({required String seriesId}) =
|
||||||
|
_SeriesSeasonsReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'seriesId': seriesId};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SeriesSeasonsRes with _$SeriesSeasonsRes {
|
||||||
|
const factory SeriesSeasonsRes({
|
||||||
|
required String seriesId,
|
||||||
|
required List<EmbyRawSeason> items,
|
||||||
|
}) = _SeriesSeasonsRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SimilarItemsReq with _$SimilarItemsReq {
|
||||||
|
const SimilarItemsReq._();
|
||||||
|
const factory SimilarItemsReq({required String itemId, int? limit}) =
|
||||||
|
_SimilarItemsReq;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'itemId': itemId,
|
||||||
|
if (limit != null) 'limit': limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SimilarItemsRes with _$SimilarItemsRes {
|
||||||
|
const factory SimilarItemsRes({
|
||||||
|
required String itemId,
|
||||||
|
required List<EmbyRawItem> items,
|
||||||
|
}) = _SimilarItemsRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class AdditionalPartsReq with _$AdditionalPartsReq {
|
||||||
|
const factory AdditionalPartsReq({required String itemId}) =
|
||||||
|
_AdditionalPartsReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class AdditionalPartsRes with _$AdditionalPartsRes {
|
||||||
|
const factory AdditionalPartsRes({
|
||||||
|
required String itemId,
|
||||||
|
required List<EmbyRawItem> items,
|
||||||
|
}) = _AdditionalPartsRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class CrossServerSourceCard with _$CrossServerSourceCard {
|
||||||
|
const factory CrossServerSourceCard({
|
||||||
|
required String serverId,
|
||||||
|
required String serverName,
|
||||||
|
required String serverUrl,
|
||||||
|
required String token,
|
||||||
|
required String userId,
|
||||||
|
required String landingItemId,
|
||||||
|
required String mediaSourceId,
|
||||||
|
required EmbyRawItem item,
|
||||||
|
required EmbyRawMediaSource mediaSource,
|
||||||
|
MediaQualityInfo? quality,
|
||||||
|
}) = _CrossServerSourceCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MediaQualityInfo with _$MediaQualityInfo {
|
||||||
|
const MediaQualityInfo._();
|
||||||
|
const factory MediaQualityInfo({
|
||||||
|
int? height,
|
||||||
|
int? width,
|
||||||
|
String? videoCodec,
|
||||||
|
String? container,
|
||||||
|
String? videoRange,
|
||||||
|
String? videoRangeType,
|
||||||
|
int? bitDepth,
|
||||||
|
double? fps,
|
||||||
|
}) = _MediaQualityInfo;
|
||||||
|
|
||||||
|
factory MediaQualityInfo.fromMediaSource(EmbyRawMediaSource source) {
|
||||||
|
final streams = source.MediaStreams ?? const [];
|
||||||
|
for (final stream in streams) {
|
||||||
|
if (stream.Type != 'Video') continue;
|
||||||
|
final ex = stream.extra;
|
||||||
|
final realFps = ex['RealFrameRate'] as num?;
|
||||||
|
final avgFps = ex['AverageFrameRate'] as num?;
|
||||||
|
return MediaQualityInfo(
|
||||||
|
height: (ex['Height'] as num?)?.toInt() ?? source.Height,
|
||||||
|
width: (ex['Width'] as num?)?.toInt() ?? source.Width,
|
||||||
|
videoCodec: stream.Codec,
|
||||||
|
container: source.Container,
|
||||||
|
videoRange: ex['VideoRange'] as String?,
|
||||||
|
videoRangeType: ex['VideoRangeType'] as String?,
|
||||||
|
bitDepth: (ex['BitDepth'] as num?)?.toInt(),
|
||||||
|
fps: realFps?.toDouble() ?? avgFps?.toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final nameFallback = fromSourceName(
|
||||||
|
source.Name,
|
||||||
|
fallbackHeight: source.Height,
|
||||||
|
container: source.Container,
|
||||||
|
);
|
||||||
|
if (nameFallback != null) return nameFallback;
|
||||||
|
return MediaQualityInfo(
|
||||||
|
height: source.Height,
|
||||||
|
width: source.Width,
|
||||||
|
container: source.Container,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static MediaQualityInfo? fromSourceName(
|
||||||
|
String? name, {
|
||||||
|
int? fallbackHeight,
|
||||||
|
String? container,
|
||||||
|
}) {
|
||||||
|
final height = _heightFromName(name) ?? fallbackHeight;
|
||||||
|
final range = _rangeFromName(name);
|
||||||
|
if (height == null && range == null) return null;
|
||||||
|
return MediaQualityInfo(
|
||||||
|
height: height,
|
||||||
|
container: container,
|
||||||
|
videoRange: range,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int? _heightFromName(String? name) {
|
||||||
|
if (name == null || name.isEmpty) return null;
|
||||||
|
final lower = name.toLowerCase();
|
||||||
|
if (RegExp(r'(?:^|[^0-9])2160(?![0-9])p?|\b4k\b|\buhd\b').hasMatch(lower)) {
|
||||||
|
return 2160;
|
||||||
|
}
|
||||||
|
if (RegExp(r'(?:^|[^0-9])1440(?![0-9])p?|\b2k\b').hasMatch(lower)) {
|
||||||
|
return 1440;
|
||||||
|
}
|
||||||
|
if (RegExp(r'(?:^|[^0-9])1080(?![0-9])p?|\bfhd\b').hasMatch(lower)) {
|
||||||
|
return 1080;
|
||||||
|
}
|
||||||
|
if (RegExp(r'(?:^|[^0-9])720(?![0-9])p?|\bhd\b').hasMatch(lower)) {
|
||||||
|
return 720;
|
||||||
|
}
|
||||||
|
if (RegExp(r'(?:^|[^0-9])480(?![0-9])p?').hasMatch(lower)) return 480;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _rangeFromName(String? name) {
|
||||||
|
if (name == null || name.isEmpty) return null;
|
||||||
|
final lower = name.toLowerCase();
|
||||||
|
if (RegExp(r'\bdv\b|dolby[\s._-]*vision|dovi').hasMatch(lower)) {
|
||||||
|
return 'DolbyVision';
|
||||||
|
}
|
||||||
|
if (RegExp(r'hdr10\+|hdr10plus').hasMatch(lower)) return 'HDR10Plus';
|
||||||
|
if (RegExp(r'\bhlg\b').hasMatch(lower)) return 'HLG';
|
||||||
|
if (RegExp(r'hdr10').hasMatch(lower)) return 'HDR10';
|
||||||
|
if (RegExp(r'\bhdr\b').hasMatch(lower)) return 'HDR';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get resolutionLabel {
|
||||||
|
final h = height;
|
||||||
|
if (h == null) return '';
|
||||||
|
if (h >= 2160) return '4K';
|
||||||
|
if (h >= 1440) return '2K';
|
||||||
|
if (h >= 1080) return '1080P';
|
||||||
|
if (h >= 720) return '720P';
|
||||||
|
return '${h}P';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get dynamicRangeLabel {
|
||||||
|
final typeKnown = _canonicalDynamicRange(videoRangeType);
|
||||||
|
if (typeKnown != null) return typeKnown;
|
||||||
|
final rangeKnown = _canonicalDynamicRange(videoRange);
|
||||||
|
if (rangeKnown != null) return rangeKnown;
|
||||||
|
final typeRaw = _rawDynamicRange(videoRangeType);
|
||||||
|
if (typeRaw != null) return typeRaw;
|
||||||
|
final rangeRaw = _rawDynamicRange(videoRange);
|
||||||
|
if (rangeRaw != null) return rangeRaw;
|
||||||
|
return 'SDR';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static String? _canonicalDynamicRange(String? raw) {
|
||||||
|
if (raw == null) return null;
|
||||||
|
final compact = raw.replaceAll(' ', '');
|
||||||
|
if (compact.isEmpty) return null;
|
||||||
|
final upper = compact.toUpperCase();
|
||||||
|
if (upper.contains('DOVI') || upper.contains('DOLBYVISION')) {
|
||||||
|
return 'Dolby Vision';
|
||||||
|
}
|
||||||
|
if (upper == 'HDR10PLUS' || upper == 'HDR10+') return 'HDR10+';
|
||||||
|
if (upper == 'HDR10') return 'HDR10';
|
||||||
|
if (upper == 'HLG') return 'HLG';
|
||||||
|
if (upper == 'HDR') return 'HDR';
|
||||||
|
if (upper == 'SDR') return 'SDR';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static String? _rawDynamicRange(String? raw) {
|
||||||
|
if (raw == null) return null;
|
||||||
|
final trimmed = raw.trim();
|
||||||
|
return trimmed.isEmpty ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isDolbyVision => dynamicRangeLabel == 'Dolby Vision';
|
||||||
|
bool get isHDR => dynamicRangeLabel != 'SDR';
|
||||||
|
|
||||||
|
String get codecLabel {
|
||||||
|
final c = videoCodec?.toLowerCase();
|
||||||
|
if (c == null || c.isEmpty) return '';
|
||||||
|
if (c == 'hevc' || c == 'h265') return 'H265';
|
||||||
|
if (c == 'h264' || c == 'avc') return 'H264';
|
||||||
|
if (c == 'av1') return 'AV1';
|
||||||
|
if (c == 'vp9') return 'VP9';
|
||||||
|
return c.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
String get bitDepthLabel {
|
||||||
|
if (bitDepth == null) return '';
|
||||||
|
return '${bitDepth}bit';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get fpsLabel {
|
||||||
|
if (fps == null) return '';
|
||||||
|
final f = fps!;
|
||||||
|
if ((f - f.roundToDouble()).abs() < 0.1) return '${f.round()}fps';
|
||||||
|
return '${f.toStringAsFixed(1)}fps';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get detailLine {
|
||||||
|
final parts = <String>[
|
||||||
|
codecLabel,
|
||||||
|
bitDepthLabel,
|
||||||
|
fpsLabel,
|
||||||
|
].where((s) => s.isNotEmpty).toList();
|
||||||
|
return parts.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get summary {
|
||||||
|
final parts = <String>[];
|
||||||
|
final res = resolutionLabel;
|
||||||
|
if (res.isNotEmpty) parts.add(res);
|
||||||
|
final dr = dynamicRangeLabel;
|
||||||
|
if (dr.isNotEmpty) parts.add(dr);
|
||||||
|
if (codecLabel.isNotEmpty) parts.add(codecLabel);
|
||||||
|
return parts.join(' · ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class CrossServerSearchReq with _$CrossServerSearchReq {
|
||||||
|
const factory CrossServerSearchReq({
|
||||||
|
required String anchorType,
|
||||||
|
required String itemName,
|
||||||
|
required Map<String, String> providerIds,
|
||||||
|
Map<String, String>? seriesProviderIds,
|
||||||
|
int? parentIndexNumber,
|
||||||
|
int? indexNumber,
|
||||||
|
|
||||||
|
|
||||||
|
String? excludedServerId,
|
||||||
|
}) = _CrossServerSearchReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbAvailabilityReq with _$TmdbAvailabilityReq {
|
||||||
|
const factory TmdbAvailabilityReq({
|
||||||
|
required String tmdbId,
|
||||||
|
required String mediaType,
|
||||||
|
|
||||||
|
|
||||||
|
String? imdbId,
|
||||||
|
}) = _TmdbAvailabilityReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbLibraryHit with _$TmdbLibraryHit {
|
||||||
|
const factory TmdbLibraryHit({
|
||||||
|
required String serverId,
|
||||||
|
required String serverName,
|
||||||
|
required String itemId,
|
||||||
|
required String itemType,
|
||||||
|
required String name,
|
||||||
|
int? productionYear,
|
||||||
|
|
||||||
|
|
||||||
|
int? playbackPositionTicks,
|
||||||
|
|
||||||
|
|
||||||
|
int? runTimeTicks,
|
||||||
|
}) = _TmdbLibraryHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SortOrder {
|
||||||
|
ascending('Ascending'),
|
||||||
|
descending('Descending');
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
const SortOrder(this.value);
|
||||||
|
|
||||||
|
static SortOrder? fromString(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
return SortOrder.values.firstWhere(
|
||||||
|
(e) => e.value == value,
|
||||||
|
orElse: () => SortOrder.ascending,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class LibraryItemsReq with _$LibraryItemsReq {
|
||||||
|
const factory LibraryItemsReq({
|
||||||
|
String? parentId,
|
||||||
|
String? includeItemTypes,
|
||||||
|
List<String>? genreIds,
|
||||||
|
String? searchTerm,
|
||||||
|
String? anyProviderIdEquals,
|
||||||
|
String? fields,
|
||||||
|
String? enableImageTypes,
|
||||||
|
bool? groupProgramsBySeries,
|
||||||
|
int? imageTypeLimit,
|
||||||
|
int? startIndex,
|
||||||
|
int? limit,
|
||||||
|
bool? recursive,
|
||||||
|
String? sortBy,
|
||||||
|
SortOrder? sortOrder,
|
||||||
|
}) = _LibraryItemsReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class LibraryItemsRes with _$LibraryItemsRes {
|
||||||
|
const factory LibraryItemsRes({
|
||||||
|
required String parentId,
|
||||||
|
required List<EmbyRawItem> items,
|
||||||
|
}) = _LibraryItemsRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ItemCountsRes with _$ItemCountsRes {
|
||||||
|
const factory ItemCountsRes({
|
||||||
|
@JsonKey(name: 'MovieCount', fromJson: _intOrZero) required int movieCount,
|
||||||
|
@JsonKey(name: 'EpisodeCount', fromJson: _intOrZero)
|
||||||
|
required int episodeCount,
|
||||||
|
}) = _ItemCountsRes;
|
||||||
|
|
||||||
|
factory ItemCountsRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ItemCountsResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class GlobalSearchReq with _$GlobalSearchReq {
|
||||||
|
const factory GlobalSearchReq({
|
||||||
|
required String keyword,
|
||||||
|
String? includeItemTypes,
|
||||||
|
int? limitPerServer,
|
||||||
|
String? serverId,
|
||||||
|
}) = _GlobalSearchReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class GlobalSearchServerResult with _$GlobalSearchServerResult {
|
||||||
|
const factory GlobalSearchServerResult({
|
||||||
|
required String serverId,
|
||||||
|
required String serverName,
|
||||||
|
required String serverUrl,
|
||||||
|
required String token,
|
||||||
|
required String userId,
|
||||||
|
required List<EmbyRawItem> items,
|
||||||
|
}) = _GlobalSearchServerResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _intOrNull(Object? value) => (value as num?)?.toInt();
|
||||||
|
double? _doubleOrNull(Object? value) => (value as num?)?.toDouble();
|
||||||
|
int _intOrZero(Object? value) => (value as num?)?.toInt() ?? 0;
|
||||||
|
|
||||||
|
|
||||||
|
List<EmbyRawMediaSource> mediaSourcesOfItem(EmbyRawItem? item) {
|
||||||
|
final raw = item?.extra['MediaSources'];
|
||||||
|
if (raw is! List) return const [];
|
||||||
|
return raw
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(EmbyRawMediaSource.fromJson)
|
||||||
|
.toList(growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Map<String, String> providerIdsOfItem(EmbyRawItem item) {
|
||||||
|
final raw = item.extra['ProviderIds'];
|
||||||
|
if (raw is! Map) return const {};
|
||||||
|
final ids = <String, String>{};
|
||||||
|
for (final entry in raw.entries) {
|
||||||
|
final key = entry.key?.toString() ?? '';
|
||||||
|
final value = entry.value?.toString() ?? '';
|
||||||
|
if (key.isNotEmpty && value.isNotEmpty) ids[key] = value;
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> normalizeProviderIds(Map<String, String> raw) {
|
||||||
|
if (raw.isEmpty) return const {};
|
||||||
|
final out = <String, String>{};
|
||||||
|
for (final e in raw.entries) {
|
||||||
|
final k = e.key.trim().toLowerCase();
|
||||||
|
final v = e.value.trim();
|
||||||
|
if (k.isNotEmpty && v.isNotEmpty) out[k] = v;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> normalizedProviderIdsOfItem(EmbyRawItem item) =>
|
||||||
|
normalizeProviderIds(providerIdsOfItem(item));
|
||||||
|
|
||||||
|
Map<String, String>? _stringMapOrNull(Object? value) {
|
||||||
|
if (value is! Map) return null;
|
||||||
|
return value.map((k, v) => MapEntry(k as String, v as String));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String>? _stringListOrNull(Object? value) {
|
||||||
|
if (value is! List) return null;
|
||||||
|
return value.cast<String>();
|
||||||
|
}
|
||||||
Generated
+10615
File diff suppressed because it is too large
Load Diff
Generated
+250
@@ -0,0 +1,250 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'library.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_EmbyRawUserData _$EmbyRawUserDataFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyRawUserData(
|
||||||
|
PlaybackPositionTicks: _intOrNull(json['PlaybackPositionTicks']),
|
||||||
|
PlayCount: _intOrNull(json['PlayCount']),
|
||||||
|
UnplayedItemCount: _intOrNull(json['UnplayedItemCount']),
|
||||||
|
IsFavorite: json['IsFavorite'] as bool?,
|
||||||
|
Played: json['Played'] as bool?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyRawUserDataToJson(_EmbyRawUserData instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'PlaybackPositionTicks': ?instance.PlaybackPositionTicks,
|
||||||
|
'PlayCount': ?instance.PlayCount,
|
||||||
|
'UnplayedItemCount': ?instance.UnplayedItemCount,
|
||||||
|
'IsFavorite': ?instance.IsFavorite,
|
||||||
|
'Played': ?instance.Played,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyRawItem _$EmbyRawItemFromJson(Map<String, dynamic> json) => _EmbyRawItem(
|
||||||
|
Id: json['Id'] as String,
|
||||||
|
Name: json['Name'] as String,
|
||||||
|
Type: json['Type'] as String?,
|
||||||
|
Overview: json['Overview'] as String?,
|
||||||
|
ProductionYear: _intOrNull(json['ProductionYear']),
|
||||||
|
CommunityRating: _doubleOrNull(json['CommunityRating']),
|
||||||
|
PremiereDate: json['PremiereDate'] as String?,
|
||||||
|
SeriesName: json['SeriesName'] as String?,
|
||||||
|
SeriesId: json['SeriesId'] as String?,
|
||||||
|
SeasonId: json['SeasonId'] as String?,
|
||||||
|
SeasonName: json['SeasonName'] as String?,
|
||||||
|
IndexNumber: _intOrNull(json['IndexNumber']),
|
||||||
|
ParentId: json['ParentId'] as String?,
|
||||||
|
PrimaryImageTag: json['PrimaryImageTag'] as String?,
|
||||||
|
ImageTags: _stringMapOrNull(json['ImageTags']),
|
||||||
|
BackdropImageTags: _stringListOrNull(json['BackdropImageTags']),
|
||||||
|
RunTimeTicks: _intOrNull(json['RunTimeTicks']),
|
||||||
|
UserData: json['UserData'] == null
|
||||||
|
? null
|
||||||
|
: EmbyRawUserData.fromJson(json['UserData'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyRawItemToJson(_EmbyRawItem instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'Id': instance.Id,
|
||||||
|
'Name': instance.Name,
|
||||||
|
'Type': ?instance.Type,
|
||||||
|
'Overview': ?instance.Overview,
|
||||||
|
'ProductionYear': ?instance.ProductionYear,
|
||||||
|
'CommunityRating': ?instance.CommunityRating,
|
||||||
|
'PremiereDate': ?instance.PremiereDate,
|
||||||
|
'SeriesName': ?instance.SeriesName,
|
||||||
|
'SeriesId': ?instance.SeriesId,
|
||||||
|
'SeasonId': ?instance.SeasonId,
|
||||||
|
'SeasonName': ?instance.SeasonName,
|
||||||
|
'IndexNumber': ?instance.IndexNumber,
|
||||||
|
'ParentId': ?instance.ParentId,
|
||||||
|
'PrimaryImageTag': ?instance.PrimaryImageTag,
|
||||||
|
'ImageTags': ?instance.ImageTags,
|
||||||
|
'BackdropImageTags': ?instance.BackdropImageTags,
|
||||||
|
'RunTimeTicks': ?instance.RunTimeTicks,
|
||||||
|
'UserData': ?instance.UserData,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyRawSeason _$EmbyRawSeasonFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyRawSeason(
|
||||||
|
Id: json['Id'] as String,
|
||||||
|
Name: json['Name'] as String,
|
||||||
|
IndexNumber: _intOrNull(json['IndexNumber']),
|
||||||
|
ProductionYear: _intOrNull(json['ProductionYear']),
|
||||||
|
ChildCount: _intOrNull(json['ChildCount']),
|
||||||
|
PrimaryImageTag: json['PrimaryImageTag'] as String?,
|
||||||
|
ImageTags: _stringMapOrNull(json['ImageTags']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyRawSeasonToJson(_EmbyRawSeason instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'Id': instance.Id,
|
||||||
|
'Name': instance.Name,
|
||||||
|
'IndexNumber': ?instance.IndexNumber,
|
||||||
|
'ProductionYear': ?instance.ProductionYear,
|
||||||
|
'ChildCount': ?instance.ChildCount,
|
||||||
|
'PrimaryImageTag': ?instance.PrimaryImageTag,
|
||||||
|
'ImageTags': ?instance.ImageTags,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyRawLibrary _$EmbyRawLibraryFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyRawLibrary(
|
||||||
|
Id: json['Id'] as String,
|
||||||
|
Name: json['Name'] as String,
|
||||||
|
CollectionType: json['CollectionType'] as String?,
|
||||||
|
ImageTags: _stringMapOrNull(json['ImageTags']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyRawLibraryToJson(_EmbyRawLibrary instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'Id': instance.Id,
|
||||||
|
'Name': instance.Name,
|
||||||
|
'CollectionType': ?instance.CollectionType,
|
||||||
|
'ImageTags': ?instance.ImageTags,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyRawLibraries _$EmbyRawLibrariesFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyRawLibraries(
|
||||||
|
Items: (json['Items'] as List<dynamic>)
|
||||||
|
.map((e) => EmbyRawLibrary.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyRawLibrariesToJson(_EmbyRawLibraries instance) =>
|
||||||
|
<String, dynamic>{'Items': instance.Items};
|
||||||
|
|
||||||
|
_EmbyRawMediaStream _$EmbyRawMediaStreamFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyRawMediaStream(
|
||||||
|
Index: _intOrNull(json['Index']),
|
||||||
|
Type: json['Type'] as String?,
|
||||||
|
Language: json['Language'] as String?,
|
||||||
|
Codec: json['Codec'] as String?,
|
||||||
|
Channels: _intOrNull(json['Channels']),
|
||||||
|
DisplayTitle: json['DisplayTitle'] as String?,
|
||||||
|
Title: json['Title'] as String?,
|
||||||
|
IsDefault: json['IsDefault'] as bool?,
|
||||||
|
IsForced: json['IsForced'] as bool?,
|
||||||
|
IsExternal: json['IsExternal'] as bool?,
|
||||||
|
DeliveryMethod: json['DeliveryMethod'] as String?,
|
||||||
|
DeliveryUrl: json['DeliveryUrl'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyRawMediaStreamToJson(_EmbyRawMediaStream instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'Index': ?instance.Index,
|
||||||
|
'Type': ?instance.Type,
|
||||||
|
'Language': ?instance.Language,
|
||||||
|
'Codec': ?instance.Codec,
|
||||||
|
'Channels': ?instance.Channels,
|
||||||
|
'DisplayTitle': ?instance.DisplayTitle,
|
||||||
|
'Title': ?instance.Title,
|
||||||
|
'IsDefault': ?instance.IsDefault,
|
||||||
|
'IsForced': ?instance.IsForced,
|
||||||
|
'IsExternal': ?instance.IsExternal,
|
||||||
|
'DeliveryMethod': ?instance.DeliveryMethod,
|
||||||
|
'DeliveryUrl': ?instance.DeliveryUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyRawMediaSource _$EmbyRawMediaSourceFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyRawMediaSource(
|
||||||
|
Id: json['Id'] as String?,
|
||||||
|
Name: json['Name'] as String?,
|
||||||
|
Container: json['Container'] as String?,
|
||||||
|
Bitrate: _intOrNull(json['Bitrate']),
|
||||||
|
Size: _intOrNull(json['Size']),
|
||||||
|
Width: _intOrNull(json['Width']),
|
||||||
|
Height: _intOrNull(json['Height']),
|
||||||
|
DefaultSubtitleStreamIndex: _intOrNull(
|
||||||
|
json['DefaultSubtitleStreamIndex'],
|
||||||
|
),
|
||||||
|
DefaultAudioStreamIndex: _intOrNull(json['DefaultAudioStreamIndex']),
|
||||||
|
MediaStreams: (json['MediaStreams'] as List<dynamic>?)
|
||||||
|
?.map((e) => EmbyRawMediaStream.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyRawMediaSourceToJson(_EmbyRawMediaSource instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'Id': ?instance.Id,
|
||||||
|
'Name': ?instance.Name,
|
||||||
|
'Container': ?instance.Container,
|
||||||
|
'Bitrate': ?instance.Bitrate,
|
||||||
|
'Size': ?instance.Size,
|
||||||
|
'Width': ?instance.Width,
|
||||||
|
'Height': ?instance.Height,
|
||||||
|
'DefaultSubtitleStreamIndex': ?instance.DefaultSubtitleStreamIndex,
|
||||||
|
'DefaultAudioStreamIndex': ?instance.DefaultAudioStreamIndex,
|
||||||
|
'MediaStreams': ?instance.MediaStreams,
|
||||||
|
};
|
||||||
|
|
||||||
|
_LibraryLatestRes _$LibraryLatestResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_LibraryLatestRes(
|
||||||
|
parentId: json['parentId'] as String,
|
||||||
|
items: (json['items'] as List<dynamic>)
|
||||||
|
.map((e) => EmbyRawItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
totalCount: (json['totalCount'] as num?)?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$LibraryLatestResToJson(_LibraryLatestRes instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'parentId': instance.parentId,
|
||||||
|
'items': instance.items,
|
||||||
|
'totalCount': instance.totalCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
_LibraryResumeRes _$LibraryResumeResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_LibraryResumeRes(
|
||||||
|
Items: (json['Items'] as List<dynamic>)
|
||||||
|
.map((e) => EmbyRawItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$LibraryResumeResToJson(_LibraryResumeRes instance) =>
|
||||||
|
<String, dynamic>{'Items': instance.Items};
|
||||||
|
|
||||||
|
_FavoriteSetRes _$FavoriteSetResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_FavoriteSetRes(
|
||||||
|
itemId: json['itemId'] as String,
|
||||||
|
isFavorite: json['isFavorite'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$FavoriteSetResToJson(_FavoriteSetRes instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'itemId': instance.itemId,
|
||||||
|
'isFavorite': instance.isFavorite,
|
||||||
|
};
|
||||||
|
|
||||||
|
_PlayedSetRes _$PlayedSetResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_PlayedSetRes(
|
||||||
|
itemId: json['itemId'] as String,
|
||||||
|
played: json['played'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$PlayedSetResToJson(_PlayedSetRes instance) =>
|
||||||
|
<String, dynamic>{'itemId': instance.itemId, 'played': instance.played};
|
||||||
|
|
||||||
|
_HideFromResumeRes _$HideFromResumeResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_HideFromResumeRes(
|
||||||
|
itemId: json['itemId'] as String,
|
||||||
|
hide: json['hide'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$HideFromResumeResToJson(_HideFromResumeRes instance) =>
|
||||||
|
<String, dynamic>{'itemId': instance.itemId, 'hide': instance.hide};
|
||||||
|
|
||||||
|
_ItemCountsRes _$ItemCountsResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ItemCountsRes(
|
||||||
|
movieCount: _intOrZero(json['MovieCount']),
|
||||||
|
episodeCount: _intOrZero(json['EpisodeCount']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ItemCountsResToJson(_ItemCountsRes instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'MovieCount': instance.movieCount,
|
||||||
|
'EpisodeCount': instance.episodeCount,
|
||||||
|
};
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import 'library.dart';
|
||||||
|
|
||||||
|
part 'playback.freezed.dart';
|
||||||
|
part 'playback.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class PlaybackRequestPayload with _$PlaybackRequestPayload {
|
||||||
|
const factory PlaybackRequestPayload({
|
||||||
|
required String itemId,
|
||||||
|
@JsonKey(includeIfNull: false) String? itemType,
|
||||||
|
@JsonKey(includeIfNull: false) String? mediaSourceId,
|
||||||
|
@JsonKey(includeIfNull: false) int? startPositionTicks,
|
||||||
|
@Default(false) bool playFromStart,
|
||||||
|
}) = _PlaybackRequestPayload;
|
||||||
|
|
||||||
|
factory PlaybackRequestPayload.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PlaybackRequestPayloadFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbySubtitleTrack with _$EmbySubtitleTrack {
|
||||||
|
const factory EmbySubtitleTrack({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int index,
|
||||||
|
@JsonKey(includeIfNull: false) String? title,
|
||||||
|
@JsonKey(includeIfNull: false) String? language,
|
||||||
|
@JsonKey(fromJson: _boolOrFalse) @Default(false) bool isDefault,
|
||||||
|
@JsonKey(fromJson: _boolOrFalse) @Default(false) bool isForced,
|
||||||
|
@JsonKey(fromJson: _boolOrFalse) @Default(false) bool isExternal,
|
||||||
|
@JsonKey(includeIfNull: false) String? deliveryUrl,
|
||||||
|
@JsonKey(includeIfNull: false) String? codec,
|
||||||
|
}) = _EmbySubtitleTrack;
|
||||||
|
|
||||||
|
factory EmbySubtitleTrack.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbySubtitleTrackFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyAudioTrack with _$EmbyAudioTrack {
|
||||||
|
const factory EmbyAudioTrack({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int index,
|
||||||
|
@JsonKey(includeIfNull: false) String? title,
|
||||||
|
@JsonKey(includeIfNull: false) String? language,
|
||||||
|
@JsonKey(includeIfNull: false) String? codec,
|
||||||
|
@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? channels,
|
||||||
|
@JsonKey(includeIfNull: false) String? displayTitle,
|
||||||
|
@JsonKey(fromJson: _boolOrFalse) @Default(false) bool isDefault,
|
||||||
|
}) = _EmbyAudioTrack;
|
||||||
|
|
||||||
|
factory EmbyAudioTrack.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyAudioTrackFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _intRequired(Object? value) => (value as num).toInt();
|
||||||
|
|
||||||
|
int? _intOrNull(Object? value) => (value as num?)?.toInt();
|
||||||
|
|
||||||
|
bool _boolOrFalse(Object? value) => (value as bool?) ?? false;
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackRequestResult {
|
||||||
|
|
||||||
|
final String streamUrl;
|
||||||
|
|
||||||
|
|
||||||
|
final String? container;
|
||||||
|
|
||||||
|
|
||||||
|
final String? mimeType;
|
||||||
|
|
||||||
|
|
||||||
|
final bool directStream;
|
||||||
|
|
||||||
|
|
||||||
|
final EmbyRawItem item;
|
||||||
|
|
||||||
|
|
||||||
|
final EmbyRawMediaSource? mediaSource;
|
||||||
|
|
||||||
|
|
||||||
|
final double startPositionSeconds;
|
||||||
|
|
||||||
|
|
||||||
|
final double? durationSeconds;
|
||||||
|
|
||||||
|
|
||||||
|
final List<EmbySubtitleTrack> subtitles;
|
||||||
|
|
||||||
|
|
||||||
|
final int? defaultSubtitleIndex;
|
||||||
|
|
||||||
|
|
||||||
|
final List<EmbyAudioTrack> audioTracks;
|
||||||
|
|
||||||
|
|
||||||
|
final int? defaultAudioStreamIndex;
|
||||||
|
|
||||||
|
|
||||||
|
final String? playSessionId;
|
||||||
|
|
||||||
|
|
||||||
|
final String playMethod;
|
||||||
|
|
||||||
|
const PlaybackRequestResult({
|
||||||
|
required this.streamUrl,
|
||||||
|
this.container,
|
||||||
|
this.mimeType,
|
||||||
|
required this.directStream,
|
||||||
|
required this.item,
|
||||||
|
this.mediaSource,
|
||||||
|
this.startPositionSeconds = 0,
|
||||||
|
this.durationSeconds,
|
||||||
|
this.subtitles = const [],
|
||||||
|
this.defaultSubtitleIndex,
|
||||||
|
this.audioTracks = const [],
|
||||||
|
this.defaultAudioStreamIndex,
|
||||||
|
this.playSessionId,
|
||||||
|
this.playMethod = 'DirectStream',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackReportPayload {
|
||||||
|
final String itemId;
|
||||||
|
final String? mediaSourceId;
|
||||||
|
final String? playSessionId;
|
||||||
|
final int positionTicks;
|
||||||
|
final int? runTimeTicks;
|
||||||
|
final String playMethod;
|
||||||
|
final bool canSeek;
|
||||||
|
final bool isPaused;
|
||||||
|
final bool isMuted;
|
||||||
|
final double playbackRate;
|
||||||
|
final int playlistIndex;
|
||||||
|
final int playlistLength;
|
||||||
|
final String repeatMode;
|
||||||
|
final String? eventName;
|
||||||
|
|
||||||
|
const PlaybackReportPayload({
|
||||||
|
required this.itemId,
|
||||||
|
this.mediaSourceId,
|
||||||
|
this.playSessionId,
|
||||||
|
required this.positionTicks,
|
||||||
|
this.runTimeTicks,
|
||||||
|
this.playMethod = 'DirectStream',
|
||||||
|
this.canSeek = true,
|
||||||
|
this.isPaused = false,
|
||||||
|
this.isMuted = false,
|
||||||
|
this.playbackRate = 1,
|
||||||
|
this.playlistIndex = 0,
|
||||||
|
this.playlistLength = 1,
|
||||||
|
this.repeatMode = 'RepeatNone',
|
||||||
|
this.eventName,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson({bool includeNowPlayingQueue = false}) => {
|
||||||
|
'ItemId': itemId,
|
||||||
|
if (mediaSourceId != null && mediaSourceId!.isNotEmpty)
|
||||||
|
'MediaSourceId': mediaSourceId,
|
||||||
|
if (playSessionId != null && playSessionId!.isNotEmpty)
|
||||||
|
'PlaySessionId': playSessionId,
|
||||||
|
'PositionTicks': positionTicks,
|
||||||
|
if (runTimeTicks != null) 'RunTimeTicks': runTimeTicks,
|
||||||
|
'CanSeek': canSeek,
|
||||||
|
'PlayMethod': playMethod,
|
||||||
|
'PlaylistIndex': playlistIndex,
|
||||||
|
'PlaylistLength': playlistLength,
|
||||||
|
'PlaybackRate': playbackRate,
|
||||||
|
'IsMuted': isMuted,
|
||||||
|
'IsPaused': isPaused,
|
||||||
|
'RepeatMode': repeatMode,
|
||||||
|
if (eventName != null && eventName!.isNotEmpty) 'EventName': eventName,
|
||||||
|
if (includeNowPlayingQueue)
|
||||||
|
'NowPlayingQueue': [
|
||||||
|
{'Id': itemId, 'PlaylistItemId': 'playlistItem$playlistIndex'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+854
@@ -0,0 +1,854 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'playback.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$PlaybackRequestPayload {
|
||||||
|
|
||||||
|
String get itemId;@JsonKey(includeIfNull: false) String? get itemType;@JsonKey(includeIfNull: false) String? get mediaSourceId;@JsonKey(includeIfNull: false) int? get startPositionTicks; bool get playFromStart;
|
||||||
|
/// Create a copy of PlaybackRequestPayload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$PlaybackRequestPayloadCopyWith<PlaybackRequestPayload> get copyWith => _$PlaybackRequestPayloadCopyWithImpl<PlaybackRequestPayload>(this as PlaybackRequestPayload, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this PlaybackRequestPayload to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is PlaybackRequestPayload&&(identical(other.itemId, itemId) || other.itemId == itemId)&&(identical(other.itemType, itemType) || other.itemType == itemType)&&(identical(other.mediaSourceId, mediaSourceId) || other.mediaSourceId == mediaSourceId)&&(identical(other.startPositionTicks, startPositionTicks) || other.startPositionTicks == startPositionTicks)&&(identical(other.playFromStart, playFromStart) || other.playFromStart == playFromStart));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,itemId,itemType,mediaSourceId,startPositionTicks,playFromStart);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PlaybackRequestPayload(itemId: $itemId, itemType: $itemType, mediaSourceId: $mediaSourceId, startPositionTicks: $startPositionTicks, playFromStart: $playFromStart)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $PlaybackRequestPayloadCopyWith<$Res> {
|
||||||
|
factory $PlaybackRequestPayloadCopyWith(PlaybackRequestPayload value, $Res Function(PlaybackRequestPayload) _then) = _$PlaybackRequestPayloadCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String itemId,@JsonKey(includeIfNull: false) String? itemType,@JsonKey(includeIfNull: false) String? mediaSourceId,@JsonKey(includeIfNull: false) int? startPositionTicks, bool playFromStart
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$PlaybackRequestPayloadCopyWithImpl<$Res>
|
||||||
|
implements $PlaybackRequestPayloadCopyWith<$Res> {
|
||||||
|
_$PlaybackRequestPayloadCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final PlaybackRequestPayload _self;
|
||||||
|
final $Res Function(PlaybackRequestPayload) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PlaybackRequestPayload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? itemId = null,Object? itemType = freezed,Object? mediaSourceId = freezed,Object? startPositionTicks = freezed,Object? playFromStart = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
itemId: null == itemId ? _self.itemId : itemId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,itemType: freezed == itemType ? _self.itemType : itemType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,mediaSourceId: freezed == mediaSourceId ? _self.mediaSourceId : mediaSourceId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,startPositionTicks: freezed == startPositionTicks ? _self.startPositionTicks : startPositionTicks // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,playFromStart: null == playFromStart ? _self.playFromStart : playFromStart // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [PlaybackRequestPayload].
|
||||||
|
extension PlaybackRequestPayloadPatterns on PlaybackRequestPayload {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PlaybackRequestPayload value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _PlaybackRequestPayload() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PlaybackRequestPayload value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _PlaybackRequestPayload():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PlaybackRequestPayload value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _PlaybackRequestPayload() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String itemId, @JsonKey(includeIfNull: false) String? itemType, @JsonKey(includeIfNull: false) String? mediaSourceId, @JsonKey(includeIfNull: false) int? startPositionTicks, bool playFromStart)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _PlaybackRequestPayload() when $default != null:
|
||||||
|
return $default(_that.itemId,_that.itemType,_that.mediaSourceId,_that.startPositionTicks,_that.playFromStart);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String itemId, @JsonKey(includeIfNull: false) String? itemType, @JsonKey(includeIfNull: false) String? mediaSourceId, @JsonKey(includeIfNull: false) int? startPositionTicks, bool playFromStart) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _PlaybackRequestPayload():
|
||||||
|
return $default(_that.itemId,_that.itemType,_that.mediaSourceId,_that.startPositionTicks,_that.playFromStart);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String itemId, @JsonKey(includeIfNull: false) String? itemType, @JsonKey(includeIfNull: false) String? mediaSourceId, @JsonKey(includeIfNull: false) int? startPositionTicks, bool playFromStart)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _PlaybackRequestPayload() when $default != null:
|
||||||
|
return $default(_that.itemId,_that.itemType,_that.mediaSourceId,_that.startPositionTicks,_that.playFromStart);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _PlaybackRequestPayload implements PlaybackRequestPayload {
|
||||||
|
const _PlaybackRequestPayload({required this.itemId, @JsonKey(includeIfNull: false) this.itemType, @JsonKey(includeIfNull: false) this.mediaSourceId, @JsonKey(includeIfNull: false) this.startPositionTicks, this.playFromStart = false});
|
||||||
|
factory _PlaybackRequestPayload.fromJson(Map<String, dynamic> json) => _$PlaybackRequestPayloadFromJson(json);
|
||||||
|
|
||||||
|
@override final String itemId;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? itemType;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? mediaSourceId;
|
||||||
|
@override@JsonKey(includeIfNull: false) final int? startPositionTicks;
|
||||||
|
@override@JsonKey() final bool playFromStart;
|
||||||
|
|
||||||
|
/// Create a copy of PlaybackRequestPayload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$PlaybackRequestPayloadCopyWith<_PlaybackRequestPayload> get copyWith => __$PlaybackRequestPayloadCopyWithImpl<_PlaybackRequestPayload>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$PlaybackRequestPayloadToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PlaybackRequestPayload&&(identical(other.itemId, itemId) || other.itemId == itemId)&&(identical(other.itemType, itemType) || other.itemType == itemType)&&(identical(other.mediaSourceId, mediaSourceId) || other.mediaSourceId == mediaSourceId)&&(identical(other.startPositionTicks, startPositionTicks) || other.startPositionTicks == startPositionTicks)&&(identical(other.playFromStart, playFromStart) || other.playFromStart == playFromStart));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,itemId,itemType,mediaSourceId,startPositionTicks,playFromStart);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PlaybackRequestPayload(itemId: $itemId, itemType: $itemType, mediaSourceId: $mediaSourceId, startPositionTicks: $startPositionTicks, playFromStart: $playFromStart)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$PlaybackRequestPayloadCopyWith<$Res> implements $PlaybackRequestPayloadCopyWith<$Res> {
|
||||||
|
factory _$PlaybackRequestPayloadCopyWith(_PlaybackRequestPayload value, $Res Function(_PlaybackRequestPayload) _then) = __$PlaybackRequestPayloadCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String itemId,@JsonKey(includeIfNull: false) String? itemType,@JsonKey(includeIfNull: false) String? mediaSourceId,@JsonKey(includeIfNull: false) int? startPositionTicks, bool playFromStart
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$PlaybackRequestPayloadCopyWithImpl<$Res>
|
||||||
|
implements _$PlaybackRequestPayloadCopyWith<$Res> {
|
||||||
|
__$PlaybackRequestPayloadCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _PlaybackRequestPayload _self;
|
||||||
|
final $Res Function(_PlaybackRequestPayload) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PlaybackRequestPayload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? itemId = null,Object? itemType = freezed,Object? mediaSourceId = freezed,Object? startPositionTicks = freezed,Object? playFromStart = null,}) {
|
||||||
|
return _then(_PlaybackRequestPayload(
|
||||||
|
itemId: null == itemId ? _self.itemId : itemId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,itemType: freezed == itemType ? _self.itemType : itemType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,mediaSourceId: freezed == mediaSourceId ? _self.mediaSourceId : mediaSourceId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,startPositionTicks: freezed == startPositionTicks ? _self.startPositionTicks : startPositionTicks // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,playFromStart: null == playFromStart ? _self.playFromStart : playFromStart // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$EmbySubtitleTrack {
|
||||||
|
|
||||||
|
@JsonKey(fromJson: _intRequired) int get index;@JsonKey(includeIfNull: false) String? get title;@JsonKey(includeIfNull: false) String? get language;@JsonKey(fromJson: _boolOrFalse) bool get isDefault;@JsonKey(fromJson: _boolOrFalse) bool get isForced;@JsonKey(fromJson: _boolOrFalse) bool get isExternal;@JsonKey(includeIfNull: false) String? get deliveryUrl;@JsonKey(includeIfNull: false) String? get codec;
|
||||||
|
/// Create a copy of EmbySubtitleTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$EmbySubtitleTrackCopyWith<EmbySubtitleTrack> get copyWith => _$EmbySubtitleTrackCopyWithImpl<EmbySubtitleTrack>(this as EmbySubtitleTrack, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this EmbySubtitleTrack to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is EmbySubtitleTrack&&(identical(other.index, index) || other.index == index)&&(identical(other.title, title) || other.title == title)&&(identical(other.language, language) || other.language == language)&&(identical(other.isDefault, isDefault) || other.isDefault == isDefault)&&(identical(other.isForced, isForced) || other.isForced == isForced)&&(identical(other.isExternal, isExternal) || other.isExternal == isExternal)&&(identical(other.deliveryUrl, deliveryUrl) || other.deliveryUrl == deliveryUrl)&&(identical(other.codec, codec) || other.codec == codec));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,index,title,language,isDefault,isForced,isExternal,deliveryUrl,codec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'EmbySubtitleTrack(index: $index, title: $title, language: $language, isDefault: $isDefault, isForced: $isForced, isExternal: $isExternal, deliveryUrl: $deliveryUrl, codec: $codec)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $EmbySubtitleTrackCopyWith<$Res> {
|
||||||
|
factory $EmbySubtitleTrackCopyWith(EmbySubtitleTrack value, $Res Function(EmbySubtitleTrack) _then) = _$EmbySubtitleTrackCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int index,@JsonKey(includeIfNull: false) String? title,@JsonKey(includeIfNull: false) String? language,@JsonKey(fromJson: _boolOrFalse) bool isDefault,@JsonKey(fromJson: _boolOrFalse) bool isForced,@JsonKey(fromJson: _boolOrFalse) bool isExternal,@JsonKey(includeIfNull: false) String? deliveryUrl,@JsonKey(includeIfNull: false) String? codec
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$EmbySubtitleTrackCopyWithImpl<$Res>
|
||||||
|
implements $EmbySubtitleTrackCopyWith<$Res> {
|
||||||
|
_$EmbySubtitleTrackCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final EmbySubtitleTrack _self;
|
||||||
|
final $Res Function(EmbySubtitleTrack) _then;
|
||||||
|
|
||||||
|
/// Create a copy of EmbySubtitleTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? index = null,Object? title = freezed,Object? language = freezed,Object? isDefault = null,Object? isForced = null,Object? isExternal = null,Object? deliveryUrl = freezed,Object? codec = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
index: null == index ? _self.index : index // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,isDefault: null == isDefault ? _self.isDefault : isDefault // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isForced: null == isForced ? _self.isForced : isForced // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isExternal: null == isExternal ? _self.isExternal : isExternal // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,deliveryUrl: freezed == deliveryUrl ? _self.deliveryUrl : deliveryUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,codec: freezed == codec ? _self.codec : codec // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [EmbySubtitleTrack].
|
||||||
|
extension EmbySubtitleTrackPatterns on EmbySubtitleTrack {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _EmbySubtitleTrack value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbySubtitleTrack() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _EmbySubtitleTrack value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbySubtitleTrack():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _EmbySubtitleTrack value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbySubtitleTrack() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int index, @JsonKey(includeIfNull: false) String? title, @JsonKey(includeIfNull: false) String? language, @JsonKey(fromJson: _boolOrFalse) bool isDefault, @JsonKey(fromJson: _boolOrFalse) bool isForced, @JsonKey(fromJson: _boolOrFalse) bool isExternal, @JsonKey(includeIfNull: false) String? deliveryUrl, @JsonKey(includeIfNull: false) String? codec)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbySubtitleTrack() when $default != null:
|
||||||
|
return $default(_that.index,_that.title,_that.language,_that.isDefault,_that.isForced,_that.isExternal,_that.deliveryUrl,_that.codec);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int index, @JsonKey(includeIfNull: false) String? title, @JsonKey(includeIfNull: false) String? language, @JsonKey(fromJson: _boolOrFalse) bool isDefault, @JsonKey(fromJson: _boolOrFalse) bool isForced, @JsonKey(fromJson: _boolOrFalse) bool isExternal, @JsonKey(includeIfNull: false) String? deliveryUrl, @JsonKey(includeIfNull: false) String? codec) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbySubtitleTrack():
|
||||||
|
return $default(_that.index,_that.title,_that.language,_that.isDefault,_that.isForced,_that.isExternal,_that.deliveryUrl,_that.codec);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(fromJson: _intRequired) int index, @JsonKey(includeIfNull: false) String? title, @JsonKey(includeIfNull: false) String? language, @JsonKey(fromJson: _boolOrFalse) bool isDefault, @JsonKey(fromJson: _boolOrFalse) bool isForced, @JsonKey(fromJson: _boolOrFalse) bool isExternal, @JsonKey(includeIfNull: false) String? deliveryUrl, @JsonKey(includeIfNull: false) String? codec)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbySubtitleTrack() when $default != null:
|
||||||
|
return $default(_that.index,_that.title,_that.language,_that.isDefault,_that.isForced,_that.isExternal,_that.deliveryUrl,_that.codec);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _EmbySubtitleTrack implements EmbySubtitleTrack {
|
||||||
|
const _EmbySubtitleTrack({@JsonKey(fromJson: _intRequired) required this.index, @JsonKey(includeIfNull: false) this.title, @JsonKey(includeIfNull: false) this.language, @JsonKey(fromJson: _boolOrFalse) this.isDefault = false, @JsonKey(fromJson: _boolOrFalse) this.isForced = false, @JsonKey(fromJson: _boolOrFalse) this.isExternal = false, @JsonKey(includeIfNull: false) this.deliveryUrl, @JsonKey(includeIfNull: false) this.codec});
|
||||||
|
factory _EmbySubtitleTrack.fromJson(Map<String, dynamic> json) => _$EmbySubtitleTrackFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey(fromJson: _intRequired) final int index;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? title;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? language;
|
||||||
|
@override@JsonKey(fromJson: _boolOrFalse) final bool isDefault;
|
||||||
|
@override@JsonKey(fromJson: _boolOrFalse) final bool isForced;
|
||||||
|
@override@JsonKey(fromJson: _boolOrFalse) final bool isExternal;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? deliveryUrl;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? codec;
|
||||||
|
|
||||||
|
/// Create a copy of EmbySubtitleTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$EmbySubtitleTrackCopyWith<_EmbySubtitleTrack> get copyWith => __$EmbySubtitleTrackCopyWithImpl<_EmbySubtitleTrack>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$EmbySubtitleTrackToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _EmbySubtitleTrack&&(identical(other.index, index) || other.index == index)&&(identical(other.title, title) || other.title == title)&&(identical(other.language, language) || other.language == language)&&(identical(other.isDefault, isDefault) || other.isDefault == isDefault)&&(identical(other.isForced, isForced) || other.isForced == isForced)&&(identical(other.isExternal, isExternal) || other.isExternal == isExternal)&&(identical(other.deliveryUrl, deliveryUrl) || other.deliveryUrl == deliveryUrl)&&(identical(other.codec, codec) || other.codec == codec));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,index,title,language,isDefault,isForced,isExternal,deliveryUrl,codec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'EmbySubtitleTrack(index: $index, title: $title, language: $language, isDefault: $isDefault, isForced: $isForced, isExternal: $isExternal, deliveryUrl: $deliveryUrl, codec: $codec)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$EmbySubtitleTrackCopyWith<$Res> implements $EmbySubtitleTrackCopyWith<$Res> {
|
||||||
|
factory _$EmbySubtitleTrackCopyWith(_EmbySubtitleTrack value, $Res Function(_EmbySubtitleTrack) _then) = __$EmbySubtitleTrackCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int index,@JsonKey(includeIfNull: false) String? title,@JsonKey(includeIfNull: false) String? language,@JsonKey(fromJson: _boolOrFalse) bool isDefault,@JsonKey(fromJson: _boolOrFalse) bool isForced,@JsonKey(fromJson: _boolOrFalse) bool isExternal,@JsonKey(includeIfNull: false) String? deliveryUrl,@JsonKey(includeIfNull: false) String? codec
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$EmbySubtitleTrackCopyWithImpl<$Res>
|
||||||
|
implements _$EmbySubtitleTrackCopyWith<$Res> {
|
||||||
|
__$EmbySubtitleTrackCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _EmbySubtitleTrack _self;
|
||||||
|
final $Res Function(_EmbySubtitleTrack) _then;
|
||||||
|
|
||||||
|
/// Create a copy of EmbySubtitleTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? index = null,Object? title = freezed,Object? language = freezed,Object? isDefault = null,Object? isForced = null,Object? isExternal = null,Object? deliveryUrl = freezed,Object? codec = freezed,}) {
|
||||||
|
return _then(_EmbySubtitleTrack(
|
||||||
|
index: null == index ? _self.index : index // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,isDefault: null == isDefault ? _self.isDefault : isDefault // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isForced: null == isForced ? _self.isForced : isForced // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isExternal: null == isExternal ? _self.isExternal : isExternal // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,deliveryUrl: freezed == deliveryUrl ? _self.deliveryUrl : deliveryUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,codec: freezed == codec ? _self.codec : codec // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$EmbyAudioTrack {
|
||||||
|
|
||||||
|
@JsonKey(fromJson: _intRequired) int get index;@JsonKey(includeIfNull: false) String? get title;@JsonKey(includeIfNull: false) String? get language;@JsonKey(includeIfNull: false) String? get codec;@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? get channels;@JsonKey(includeIfNull: false) String? get displayTitle;@JsonKey(fromJson: _boolOrFalse) bool get isDefault;
|
||||||
|
/// Create a copy of EmbyAudioTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$EmbyAudioTrackCopyWith<EmbyAudioTrack> get copyWith => _$EmbyAudioTrackCopyWithImpl<EmbyAudioTrack>(this as EmbyAudioTrack, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this EmbyAudioTrack to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is EmbyAudioTrack&&(identical(other.index, index) || other.index == index)&&(identical(other.title, title) || other.title == title)&&(identical(other.language, language) || other.language == language)&&(identical(other.codec, codec) || other.codec == codec)&&(identical(other.channels, channels) || other.channels == channels)&&(identical(other.displayTitle, displayTitle) || other.displayTitle == displayTitle)&&(identical(other.isDefault, isDefault) || other.isDefault == isDefault));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,index,title,language,codec,channels,displayTitle,isDefault);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'EmbyAudioTrack(index: $index, title: $title, language: $language, codec: $codec, channels: $channels, displayTitle: $displayTitle, isDefault: $isDefault)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $EmbyAudioTrackCopyWith<$Res> {
|
||||||
|
factory $EmbyAudioTrackCopyWith(EmbyAudioTrack value, $Res Function(EmbyAudioTrack) _then) = _$EmbyAudioTrackCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int index,@JsonKey(includeIfNull: false) String? title,@JsonKey(includeIfNull: false) String? language,@JsonKey(includeIfNull: false) String? codec,@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? channels,@JsonKey(includeIfNull: false) String? displayTitle,@JsonKey(fromJson: _boolOrFalse) bool isDefault
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$EmbyAudioTrackCopyWithImpl<$Res>
|
||||||
|
implements $EmbyAudioTrackCopyWith<$Res> {
|
||||||
|
_$EmbyAudioTrackCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final EmbyAudioTrack _self;
|
||||||
|
final $Res Function(EmbyAudioTrack) _then;
|
||||||
|
|
||||||
|
/// Create a copy of EmbyAudioTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? index = null,Object? title = freezed,Object? language = freezed,Object? codec = freezed,Object? channels = freezed,Object? displayTitle = freezed,Object? isDefault = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
index: null == index ? _self.index : index // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,codec: freezed == codec ? _self.codec : codec // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,channels: freezed == channels ? _self.channels : channels // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,displayTitle: freezed == displayTitle ? _self.displayTitle : displayTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,isDefault: null == isDefault ? _self.isDefault : isDefault // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [EmbyAudioTrack].
|
||||||
|
extension EmbyAudioTrackPatterns on EmbyAudioTrack {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _EmbyAudioTrack value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbyAudioTrack() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _EmbyAudioTrack value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbyAudioTrack():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _EmbyAudioTrack value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbyAudioTrack() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int index, @JsonKey(includeIfNull: false) String? title, @JsonKey(includeIfNull: false) String? language, @JsonKey(includeIfNull: false) String? codec, @JsonKey(fromJson: _intOrNull, includeIfNull: false) int? channels, @JsonKey(includeIfNull: false) String? displayTitle, @JsonKey(fromJson: _boolOrFalse) bool isDefault)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbyAudioTrack() when $default != null:
|
||||||
|
return $default(_that.index,_that.title,_that.language,_that.codec,_that.channels,_that.displayTitle,_that.isDefault);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(fromJson: _intRequired) int index, @JsonKey(includeIfNull: false) String? title, @JsonKey(includeIfNull: false) String? language, @JsonKey(includeIfNull: false) String? codec, @JsonKey(fromJson: _intOrNull, includeIfNull: false) int? channels, @JsonKey(includeIfNull: false) String? displayTitle, @JsonKey(fromJson: _boolOrFalse) bool isDefault) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbyAudioTrack():
|
||||||
|
return $default(_that.index,_that.title,_that.language,_that.codec,_that.channels,_that.displayTitle,_that.isDefault);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(fromJson: _intRequired) int index, @JsonKey(includeIfNull: false) String? title, @JsonKey(includeIfNull: false) String? language, @JsonKey(includeIfNull: false) String? codec, @JsonKey(fromJson: _intOrNull, includeIfNull: false) int? channels, @JsonKey(includeIfNull: false) String? displayTitle, @JsonKey(fromJson: _boolOrFalse) bool isDefault)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _EmbyAudioTrack() when $default != null:
|
||||||
|
return $default(_that.index,_that.title,_that.language,_that.codec,_that.channels,_that.displayTitle,_that.isDefault);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _EmbyAudioTrack implements EmbyAudioTrack {
|
||||||
|
const _EmbyAudioTrack({@JsonKey(fromJson: _intRequired) required this.index, @JsonKey(includeIfNull: false) this.title, @JsonKey(includeIfNull: false) this.language, @JsonKey(includeIfNull: false) this.codec, @JsonKey(fromJson: _intOrNull, includeIfNull: false) this.channels, @JsonKey(includeIfNull: false) this.displayTitle, @JsonKey(fromJson: _boolOrFalse) this.isDefault = false});
|
||||||
|
factory _EmbyAudioTrack.fromJson(Map<String, dynamic> json) => _$EmbyAudioTrackFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey(fromJson: _intRequired) final int index;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? title;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? language;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? codec;
|
||||||
|
@override@JsonKey(fromJson: _intOrNull, includeIfNull: false) final int? channels;
|
||||||
|
@override@JsonKey(includeIfNull: false) final String? displayTitle;
|
||||||
|
@override@JsonKey(fromJson: _boolOrFalse) final bool isDefault;
|
||||||
|
|
||||||
|
/// Create a copy of EmbyAudioTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$EmbyAudioTrackCopyWith<_EmbyAudioTrack> get copyWith => __$EmbyAudioTrackCopyWithImpl<_EmbyAudioTrack>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$EmbyAudioTrackToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _EmbyAudioTrack&&(identical(other.index, index) || other.index == index)&&(identical(other.title, title) || other.title == title)&&(identical(other.language, language) || other.language == language)&&(identical(other.codec, codec) || other.codec == codec)&&(identical(other.channels, channels) || other.channels == channels)&&(identical(other.displayTitle, displayTitle) || other.displayTitle == displayTitle)&&(identical(other.isDefault, isDefault) || other.isDefault == isDefault));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,index,title,language,codec,channels,displayTitle,isDefault);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'EmbyAudioTrack(index: $index, title: $title, language: $language, codec: $codec, channels: $channels, displayTitle: $displayTitle, isDefault: $isDefault)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$EmbyAudioTrackCopyWith<$Res> implements $EmbyAudioTrackCopyWith<$Res> {
|
||||||
|
factory _$EmbyAudioTrackCopyWith(_EmbyAudioTrack value, $Res Function(_EmbyAudioTrack) _then) = __$EmbyAudioTrackCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(fromJson: _intRequired) int index,@JsonKey(includeIfNull: false) String? title,@JsonKey(includeIfNull: false) String? language,@JsonKey(includeIfNull: false) String? codec,@JsonKey(fromJson: _intOrNull, includeIfNull: false) int? channels,@JsonKey(includeIfNull: false) String? displayTitle,@JsonKey(fromJson: _boolOrFalse) bool isDefault
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$EmbyAudioTrackCopyWithImpl<$Res>
|
||||||
|
implements _$EmbyAudioTrackCopyWith<$Res> {
|
||||||
|
__$EmbyAudioTrackCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _EmbyAudioTrack _self;
|
||||||
|
final $Res Function(_EmbyAudioTrack) _then;
|
||||||
|
|
||||||
|
/// Create a copy of EmbyAudioTrack
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? index = null,Object? title = freezed,Object? language = freezed,Object? codec = freezed,Object? channels = freezed,Object? displayTitle = freezed,Object? isDefault = null,}) {
|
||||||
|
return _then(_EmbyAudioTrack(
|
||||||
|
index: null == index ? _self.index : index // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,codec: freezed == codec ? _self.codec : codec // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,channels: freezed == channels ? _self.channels : channels // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,displayTitle: freezed == displayTitle ? _self.displayTitle : displayTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,isDefault: null == isDefault ? _self.isDefault : isDefault // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
Generated
+81
@@ -0,0 +1,81 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'playback.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_PlaybackRequestPayload _$PlaybackRequestPayloadFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _PlaybackRequestPayload(
|
||||||
|
itemId: json['itemId'] as String,
|
||||||
|
itemType: json['itemType'] as String?,
|
||||||
|
mediaSourceId: json['mediaSourceId'] as String?,
|
||||||
|
startPositionTicks: (json['startPositionTicks'] as num?)?.toInt(),
|
||||||
|
playFromStart: json['playFromStart'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$PlaybackRequestPayloadToJson(
|
||||||
|
_PlaybackRequestPayload instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'itemId': instance.itemId,
|
||||||
|
'itemType': ?instance.itemType,
|
||||||
|
'mediaSourceId': ?instance.mediaSourceId,
|
||||||
|
'startPositionTicks': ?instance.startPositionTicks,
|
||||||
|
'playFromStart': instance.playFromStart,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbySubtitleTrack _$EmbySubtitleTrackFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbySubtitleTrack(
|
||||||
|
index: _intRequired(json['index']),
|
||||||
|
title: json['title'] as String?,
|
||||||
|
language: json['language'] as String?,
|
||||||
|
isDefault: json['isDefault'] == null
|
||||||
|
? false
|
||||||
|
: _boolOrFalse(json['isDefault']),
|
||||||
|
isForced: json['isForced'] == null
|
||||||
|
? false
|
||||||
|
: _boolOrFalse(json['isForced']),
|
||||||
|
isExternal: json['isExternal'] == null
|
||||||
|
? false
|
||||||
|
: _boolOrFalse(json['isExternal']),
|
||||||
|
deliveryUrl: json['deliveryUrl'] as String?,
|
||||||
|
codec: json['codec'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbySubtitleTrackToJson(_EmbySubtitleTrack instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'index': instance.index,
|
||||||
|
'title': ?instance.title,
|
||||||
|
'language': ?instance.language,
|
||||||
|
'isDefault': instance.isDefault,
|
||||||
|
'isForced': instance.isForced,
|
||||||
|
'isExternal': instance.isExternal,
|
||||||
|
'deliveryUrl': ?instance.deliveryUrl,
|
||||||
|
'codec': ?instance.codec,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyAudioTrack _$EmbyAudioTrackFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyAudioTrack(
|
||||||
|
index: _intRequired(json['index']),
|
||||||
|
title: json['title'] as String?,
|
||||||
|
language: json['language'] as String?,
|
||||||
|
codec: json['codec'] as String?,
|
||||||
|
channels: _intOrNull(json['channels']),
|
||||||
|
displayTitle: json['displayTitle'] as String?,
|
||||||
|
isDefault: json['isDefault'] == null
|
||||||
|
? false
|
||||||
|
: _boolOrFalse(json['isDefault']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyAudioTrackToJson(_EmbyAudioTrack instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'index': instance.index,
|
||||||
|
'title': ?instance.title,
|
||||||
|
'language': ?instance.language,
|
||||||
|
'codec': ?instance.codec,
|
||||||
|
'channels': ?instance.channels,
|
||||||
|
'displayTitle': ?instance.displayTitle,
|
||||||
|
'isDefault': instance.isDefault,
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
enum GestureAction {
|
||||||
|
none,
|
||||||
|
playPause,
|
||||||
|
fullscreen,
|
||||||
|
toggleControls,
|
||||||
|
seekBackward,
|
||||||
|
seekForward,
|
||||||
|
previousItem,
|
||||||
|
nextItem,
|
||||||
|
exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
GestureAction gestureActionFromName(String? name, GestureAction fallback) {
|
||||||
|
if (name == null) return fallback;
|
||||||
|
for (final v in GestureAction.values) {
|
||||||
|
if (v.name == name) return v;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'script_widget.freezed.dart';
|
||||||
|
part 'script_widget.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetOption with _$ScriptWidgetOption {
|
||||||
|
const factory ScriptWidgetOption({
|
||||||
|
@Default('') String title,
|
||||||
|
@JsonKey(fromJson: _stringFromAny) @Default('') String value,
|
||||||
|
}) = _ScriptWidgetOption;
|
||||||
|
|
||||||
|
factory ScriptWidgetOption.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetOptionFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetBelongTo with _$ScriptWidgetBelongTo {
|
||||||
|
const factory ScriptWidgetBelongTo({
|
||||||
|
@Default('') String paramName,
|
||||||
|
@JsonKey(fromJson: _stringListFromAny)
|
||||||
|
@Default(<String>[])
|
||||||
|
List<String> value,
|
||||||
|
}) = _ScriptWidgetBelongTo;
|
||||||
|
|
||||||
|
factory ScriptWidgetBelongTo.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetBelongToFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetParam with _$ScriptWidgetParam {
|
||||||
|
const factory ScriptWidgetParam({
|
||||||
|
required String name,
|
||||||
|
@Default('') String title,
|
||||||
|
@Default('input') String type,
|
||||||
|
@Default('') String description,
|
||||||
|
Object? value,
|
||||||
|
ScriptWidgetBelongTo? belongTo,
|
||||||
|
@Default(<ScriptWidgetOption>[]) List<ScriptWidgetOption> placeholders,
|
||||||
|
@Default(<ScriptWidgetOption>[]) List<ScriptWidgetOption> enumOptions,
|
||||||
|
}) = _ScriptWidgetParam;
|
||||||
|
|
||||||
|
factory ScriptWidgetParam.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetParamFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object?> buildScriptWidgetParameterDefaults(
|
||||||
|
List<ScriptWidgetParam> parameters,
|
||||||
|
) {
|
||||||
|
return <String, Object?>{
|
||||||
|
for (final parameter in parameters)
|
||||||
|
parameter.name: _resolveScriptWidgetParameterDefault(parameter),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object? _resolveScriptWidgetParameterDefault(ScriptWidgetParam parameter) {
|
||||||
|
if (parameter.value != null) return parameter.value;
|
||||||
|
if (parameter.enumOptions.isNotEmpty) {
|
||||||
|
return parameter.enumOptions.first.value;
|
||||||
|
}
|
||||||
|
if (parameter.placeholders.isNotEmpty) {
|
||||||
|
return parameter.placeholders.first.value;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetModule with _$ScriptWidgetModule {
|
||||||
|
const factory ScriptWidgetModule({
|
||||||
|
@Default('') String id,
|
||||||
|
required String title,
|
||||||
|
@Default('') String description,
|
||||||
|
@Default(false) bool requiresWebView,
|
||||||
|
required String functionName,
|
||||||
|
@Default(false) bool sectionMode,
|
||||||
|
@Default(3600) int cacheDuration,
|
||||||
|
@Default('list') String type,
|
||||||
|
@Default(<ScriptWidgetParam>[]) List<ScriptWidgetParam> params,
|
||||||
|
}) = _ScriptWidgetModule;
|
||||||
|
|
||||||
|
factory ScriptWidgetModule.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetModuleFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetManifest with _$ScriptWidgetManifest {
|
||||||
|
const factory ScriptWidgetManifest({
|
||||||
|
required String id,
|
||||||
|
required String title,
|
||||||
|
@Default('') String description,
|
||||||
|
@Default('') String author,
|
||||||
|
@Default('') String site,
|
||||||
|
@Default('0.0.0') String version,
|
||||||
|
@Default('0.0.1') String requiredVersion,
|
||||||
|
@Default(60) int detailCacheDuration,
|
||||||
|
@Default(<ScriptWidgetParam>[]) List<ScriptWidgetParam> globalParams,
|
||||||
|
@Default(<ScriptWidgetModule>[]) List<ScriptWidgetModule> modules,
|
||||||
|
ScriptWidgetModule? search,
|
||||||
|
}) = _ScriptWidgetManifest;
|
||||||
|
|
||||||
|
factory ScriptWidgetManifest.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetManifestFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptVideoPerson with _$ScriptVideoPerson {
|
||||||
|
const factory ScriptVideoPerson({
|
||||||
|
@JsonKey(fromJson: _stringFromAny) @Default('') String id,
|
||||||
|
@Default('') String title,
|
||||||
|
@Default('') String avatar,
|
||||||
|
@Default('') String role,
|
||||||
|
}) = _ScriptVideoPerson;
|
||||||
|
|
||||||
|
factory ScriptVideoPerson.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptVideoPersonFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptVideoGenre with _$ScriptVideoGenre {
|
||||||
|
const factory ScriptVideoGenre({
|
||||||
|
@JsonKey(fromJson: _stringFromAny) @Default('') String id,
|
||||||
|
@Default('') String title,
|
||||||
|
}) = _ScriptVideoGenre;
|
||||||
|
|
||||||
|
factory ScriptVideoGenre.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptVideoGenreFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptVideoTrailer with _$ScriptVideoTrailer {
|
||||||
|
const factory ScriptVideoTrailer({
|
||||||
|
@Default('') String coverUrl,
|
||||||
|
@Default('') String url,
|
||||||
|
}) = _ScriptVideoTrailer;
|
||||||
|
|
||||||
|
factory ScriptVideoTrailer.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptVideoTrailerFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptVideoItem with _$ScriptVideoItem {
|
||||||
|
const factory ScriptVideoItem({
|
||||||
|
@JsonKey(fromJson: _stringFromAny) required String id,
|
||||||
|
@Default('link') String type,
|
||||||
|
@Default('') String title,
|
||||||
|
@Default('') String coverUrl,
|
||||||
|
@JsonKey(readValue: _readPosterPath) @Default('') String posterPath,
|
||||||
|
@Default('') String detailPoster,
|
||||||
|
@Default('') String backdropPath,
|
||||||
|
@Default(<String>[]) List<String> backdropPaths,
|
||||||
|
@Default('') String releaseDate,
|
||||||
|
@Default('') String mediaType,
|
||||||
|
@JsonKey(fromJson: _doubleFromAny) double? rating,
|
||||||
|
@Default('') String genreTitle,
|
||||||
|
@Default(<ScriptVideoGenre>[]) List<ScriptVideoGenre> genreItems,
|
||||||
|
@Default(<ScriptVideoPerson>[]) List<ScriptVideoPerson> peoples,
|
||||||
|
@JsonKey(fromJson: _intFromAny) int? duration,
|
||||||
|
@Default('') String durationText,
|
||||||
|
@Default('') String previewUrl,
|
||||||
|
@Default(<ScriptVideoTrailer>[]) List<ScriptVideoTrailer> trailers,
|
||||||
|
@Default('') String videoUrl,
|
||||||
|
@Default('') String link,
|
||||||
|
@JsonKey(fromJson: _intFromAny) int? episode,
|
||||||
|
@Default('') String description,
|
||||||
|
@Default('app') String playerType,
|
||||||
|
@Default(<ScriptVideoItem>[]) List<ScriptVideoItem> childItems,
|
||||||
|
@Default(<ScriptVideoItem>[]) List<ScriptVideoItem> episodeItems,
|
||||||
|
@Default(<ScriptVideoItem>[]) List<ScriptVideoItem> relatedItems,
|
||||||
|
}) = _ScriptVideoItem;
|
||||||
|
|
||||||
|
factory ScriptVideoItem.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptVideoItemFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptVideoResource with _$ScriptVideoResource {
|
||||||
|
const factory ScriptVideoResource({
|
||||||
|
@Default('') String name,
|
||||||
|
@Default('') String description,
|
||||||
|
required String url,
|
||||||
|
@JsonKey(readValue: _readResourceHeaders)
|
||||||
|
@Default(<String, String>{})
|
||||||
|
Map<String, String> customHeaders,
|
||||||
|
@Default('app') String playerType,
|
||||||
|
}) = _ScriptVideoResource;
|
||||||
|
|
||||||
|
factory ScriptVideoResource.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptVideoResourceFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class InstalledScriptWidget with _$InstalledScriptWidget {
|
||||||
|
const factory InstalledScriptWidget({
|
||||||
|
required ScriptWidgetManifest manifest,
|
||||||
|
required String scriptSource,
|
||||||
|
@Default('') String sourceUrl,
|
||||||
|
@Default(true) bool enabled,
|
||||||
|
@Default(<String, Object?>{}) Map<String, Object?> globalParams,
|
||||||
|
required DateTime installedAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
}) = _InstalledScriptWidget;
|
||||||
|
|
||||||
|
factory InstalledScriptWidget.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$InstalledScriptWidgetFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetCatalogEntry with _$ScriptWidgetCatalogEntry {
|
||||||
|
const factory ScriptWidgetCatalogEntry({
|
||||||
|
required String id,
|
||||||
|
required String title,
|
||||||
|
@Default('') String description,
|
||||||
|
@Default('') String requiredVersion,
|
||||||
|
@Default('') String version,
|
||||||
|
@Default('') String author,
|
||||||
|
required String url,
|
||||||
|
}) = _ScriptWidgetCatalogEntry;
|
||||||
|
|
||||||
|
factory ScriptWidgetCatalogEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetCatalogEntryFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetCatalog with _$ScriptWidgetCatalog {
|
||||||
|
const factory ScriptWidgetCatalog({
|
||||||
|
required String title,
|
||||||
|
@Default('') String description,
|
||||||
|
@Default('') String icon,
|
||||||
|
@Default(<ScriptWidgetCatalogEntry>[])
|
||||||
|
List<ScriptWidgetCatalogEntry> widgets,
|
||||||
|
}) = _ScriptWidgetCatalog;
|
||||||
|
|
||||||
|
factory ScriptWidgetCatalog.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetCatalogFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ScriptWidgetSubscription with _$ScriptWidgetSubscription {
|
||||||
|
const factory ScriptWidgetSubscription({
|
||||||
|
required String url,
|
||||||
|
required ScriptWidgetCatalog catalog,
|
||||||
|
required DateTime refreshedAt,
|
||||||
|
}) = _ScriptWidgetSubscription;
|
||||||
|
|
||||||
|
factory ScriptWidgetSubscription.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ScriptWidgetSubscriptionFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _stringFromAny(Object? value) => value?.toString() ?? '';
|
||||||
|
|
||||||
|
int? _intFromAny(Object? value) {
|
||||||
|
if (value is num) return value.toInt();
|
||||||
|
return int.tryParse(value?.toString() ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
double? _doubleFromAny(Object? value) {
|
||||||
|
if (value is num) return value.toDouble();
|
||||||
|
return double.tryParse(value?.toString() ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _stringListFromAny(Object? value) {
|
||||||
|
if (value is List) {
|
||||||
|
return value.map((item) => item.toString()).toList(growable: false);
|
||||||
|
}
|
||||||
|
if (value == null) return const <String>[];
|
||||||
|
return <String>[value.toString()];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object? _readPosterPath(Map<dynamic, dynamic> json, String key) {
|
||||||
|
return json[key] ?? json['posterUrl'] ?? json['poster_url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object? _readResourceHeaders(Map<dynamic, dynamic> json, String key) {
|
||||||
|
final value = json[key] ?? json['headers'];
|
||||||
|
if (value is! Map) return const <String, String>{};
|
||||||
|
return <String, String>{
|
||||||
|
for (final entry in value.entries)
|
||||||
|
entry.key.toString(): entry.value.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
+4108
File diff suppressed because it is too large
Load Diff
Generated
+381
@@ -0,0 +1,381 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'script_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_ScriptWidgetOption _$ScriptWidgetOptionFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ScriptWidgetOption(
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
value: json['value'] == null ? '' : _stringFromAny(json['value']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetOptionToJson(_ScriptWidgetOption instance) =>
|
||||||
|
<String, dynamic>{'title': instance.title, 'value': instance.value};
|
||||||
|
|
||||||
|
_ScriptWidgetBelongTo _$ScriptWidgetBelongToFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _ScriptWidgetBelongTo(
|
||||||
|
paramName: json['paramName'] as String? ?? '',
|
||||||
|
value: json['value'] == null
|
||||||
|
? const <String>[]
|
||||||
|
: _stringListFromAny(json['value']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetBelongToToJson(
|
||||||
|
_ScriptWidgetBelongTo instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'paramName': instance.paramName,
|
||||||
|
'value': instance.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptWidgetParam _$ScriptWidgetParamFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _ScriptWidgetParam(
|
||||||
|
name: json['name'] as String,
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
type: json['type'] as String? ?? 'input',
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
value: json['value'],
|
||||||
|
belongTo: json['belongTo'] == null
|
||||||
|
? null
|
||||||
|
: ScriptWidgetBelongTo.fromJson(json['belongTo'] as Map<String, dynamic>),
|
||||||
|
placeholders:
|
||||||
|
(json['placeholders'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptWidgetOption.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptWidgetOption>[],
|
||||||
|
enumOptions:
|
||||||
|
(json['enumOptions'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptWidgetOption.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptWidgetOption>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetParamToJson(_ScriptWidgetParam instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'name': instance.name,
|
||||||
|
'title': instance.title,
|
||||||
|
'type': instance.type,
|
||||||
|
'description': instance.description,
|
||||||
|
'value': instance.value,
|
||||||
|
'belongTo': instance.belongTo,
|
||||||
|
'placeholders': instance.placeholders,
|
||||||
|
'enumOptions': instance.enumOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptWidgetModule _$ScriptWidgetModuleFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ScriptWidgetModule(
|
||||||
|
id: json['id'] as String? ?? '',
|
||||||
|
title: json['title'] as String,
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
requiresWebView: json['requiresWebView'] as bool? ?? false,
|
||||||
|
functionName: json['functionName'] as String,
|
||||||
|
sectionMode: json['sectionMode'] as bool? ?? false,
|
||||||
|
cacheDuration: (json['cacheDuration'] as num?)?.toInt() ?? 3600,
|
||||||
|
type: json['type'] as String? ?? 'list',
|
||||||
|
params:
|
||||||
|
(json['params'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) => ScriptWidgetParam.fromJson(e as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptWidgetParam>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetModuleToJson(_ScriptWidgetModule instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'requiresWebView': instance.requiresWebView,
|
||||||
|
'functionName': instance.functionName,
|
||||||
|
'sectionMode': instance.sectionMode,
|
||||||
|
'cacheDuration': instance.cacheDuration,
|
||||||
|
'type': instance.type,
|
||||||
|
'params': instance.params,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptWidgetManifest _$ScriptWidgetManifestFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _ScriptWidgetManifest(
|
||||||
|
id: json['id'] as String,
|
||||||
|
title: json['title'] as String,
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
author: json['author'] as String? ?? '',
|
||||||
|
site: json['site'] as String? ?? '',
|
||||||
|
version: json['version'] as String? ?? '0.0.0',
|
||||||
|
requiredVersion: json['requiredVersion'] as String? ?? '0.0.1',
|
||||||
|
detailCacheDuration: (json['detailCacheDuration'] as num?)?.toInt() ?? 60,
|
||||||
|
globalParams:
|
||||||
|
(json['globalParams'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptWidgetParam.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptWidgetParam>[],
|
||||||
|
modules:
|
||||||
|
(json['modules'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptWidgetModule.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptWidgetModule>[],
|
||||||
|
search: json['search'] == null
|
||||||
|
? null
|
||||||
|
: ScriptWidgetModule.fromJson(json['search'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetManifestToJson(
|
||||||
|
_ScriptWidgetManifest instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'author': instance.author,
|
||||||
|
'site': instance.site,
|
||||||
|
'version': instance.version,
|
||||||
|
'requiredVersion': instance.requiredVersion,
|
||||||
|
'detailCacheDuration': instance.detailCacheDuration,
|
||||||
|
'globalParams': instance.globalParams,
|
||||||
|
'modules': instance.modules,
|
||||||
|
'search': instance.search,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptVideoPerson _$ScriptVideoPersonFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ScriptVideoPerson(
|
||||||
|
id: json['id'] == null ? '' : _stringFromAny(json['id']),
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
avatar: json['avatar'] as String? ?? '',
|
||||||
|
role: json['role'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptVideoPersonToJson(_ScriptVideoPerson instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'avatar': instance.avatar,
|
||||||
|
'role': instance.role,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptVideoGenre _$ScriptVideoGenreFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ScriptVideoGenre(
|
||||||
|
id: json['id'] == null ? '' : _stringFromAny(json['id']),
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptVideoGenreToJson(_ScriptVideoGenre instance) =>
|
||||||
|
<String, dynamic>{'id': instance.id, 'title': instance.title};
|
||||||
|
|
||||||
|
_ScriptVideoTrailer _$ScriptVideoTrailerFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ScriptVideoTrailer(
|
||||||
|
coverUrl: json['coverUrl'] as String? ?? '',
|
||||||
|
url: json['url'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptVideoTrailerToJson(_ScriptVideoTrailer instance) =>
|
||||||
|
<String, dynamic>{'coverUrl': instance.coverUrl, 'url': instance.url};
|
||||||
|
|
||||||
|
_ScriptVideoItem _$ScriptVideoItemFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _ScriptVideoItem(
|
||||||
|
id: _stringFromAny(json['id']),
|
||||||
|
type: json['type'] as String? ?? 'link',
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
coverUrl: json['coverUrl'] as String? ?? '',
|
||||||
|
posterPath: _readPosterPath(json, 'posterPath') as String? ?? '',
|
||||||
|
detailPoster: json['detailPoster'] as String? ?? '',
|
||||||
|
backdropPath: json['backdropPath'] as String? ?? '',
|
||||||
|
backdropPaths:
|
||||||
|
(json['backdropPaths'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList() ??
|
||||||
|
const <String>[],
|
||||||
|
releaseDate: json['releaseDate'] as String? ?? '',
|
||||||
|
mediaType: json['mediaType'] as String? ?? '',
|
||||||
|
rating: _doubleFromAny(json['rating']),
|
||||||
|
genreTitle: json['genreTitle'] as String? ?? '',
|
||||||
|
genreItems:
|
||||||
|
(json['genreItems'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptVideoGenre.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptVideoGenre>[],
|
||||||
|
peoples:
|
||||||
|
(json['peoples'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptVideoPerson.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptVideoPerson>[],
|
||||||
|
duration: _intFromAny(json['duration']),
|
||||||
|
durationText: json['durationText'] as String? ?? '',
|
||||||
|
previewUrl: json['previewUrl'] as String? ?? '',
|
||||||
|
trailers:
|
||||||
|
(json['trailers'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptVideoTrailer.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptVideoTrailer>[],
|
||||||
|
videoUrl: json['videoUrl'] as String? ?? '',
|
||||||
|
link: json['link'] as String? ?? '',
|
||||||
|
episode: _intFromAny(json['episode']),
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
playerType: json['playerType'] as String? ?? 'app',
|
||||||
|
childItems:
|
||||||
|
(json['childItems'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptVideoItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptVideoItem>[],
|
||||||
|
episodeItems:
|
||||||
|
(json['episodeItems'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptVideoItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptVideoItem>[],
|
||||||
|
relatedItems:
|
||||||
|
(json['relatedItems'] as List<dynamic>?)
|
||||||
|
?.map((e) => ScriptVideoItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptVideoItem>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptVideoItemToJson(_ScriptVideoItem instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'type': instance.type,
|
||||||
|
'title': instance.title,
|
||||||
|
'coverUrl': instance.coverUrl,
|
||||||
|
'posterPath': instance.posterPath,
|
||||||
|
'detailPoster': instance.detailPoster,
|
||||||
|
'backdropPath': instance.backdropPath,
|
||||||
|
'backdropPaths': instance.backdropPaths,
|
||||||
|
'releaseDate': instance.releaseDate,
|
||||||
|
'mediaType': instance.mediaType,
|
||||||
|
'rating': instance.rating,
|
||||||
|
'genreTitle': instance.genreTitle,
|
||||||
|
'genreItems': instance.genreItems,
|
||||||
|
'peoples': instance.peoples,
|
||||||
|
'duration': instance.duration,
|
||||||
|
'durationText': instance.durationText,
|
||||||
|
'previewUrl': instance.previewUrl,
|
||||||
|
'trailers': instance.trailers,
|
||||||
|
'videoUrl': instance.videoUrl,
|
||||||
|
'link': instance.link,
|
||||||
|
'episode': instance.episode,
|
||||||
|
'description': instance.description,
|
||||||
|
'playerType': instance.playerType,
|
||||||
|
'childItems': instance.childItems,
|
||||||
|
'episodeItems': instance.episodeItems,
|
||||||
|
'relatedItems': instance.relatedItems,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptVideoResource _$ScriptVideoResourceFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ScriptVideoResource(
|
||||||
|
name: json['name'] as String? ?? '',
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
url: json['url'] as String,
|
||||||
|
customHeaders:
|
||||||
|
(_readResourceHeaders(json, 'customHeaders') as Map<String, dynamic>?)
|
||||||
|
?.map((k, e) => MapEntry(k, e as String)) ??
|
||||||
|
const <String, String>{},
|
||||||
|
playerType: json['playerType'] as String? ?? 'app',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptVideoResourceToJson(
|
||||||
|
_ScriptVideoResource instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
'url': instance.url,
|
||||||
|
'customHeaders': instance.customHeaders,
|
||||||
|
'playerType': instance.playerType,
|
||||||
|
};
|
||||||
|
|
||||||
|
_InstalledScriptWidget _$InstalledScriptWidgetFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _InstalledScriptWidget(
|
||||||
|
manifest: ScriptWidgetManifest.fromJson(
|
||||||
|
json['manifest'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
scriptSource: json['scriptSource'] as String,
|
||||||
|
sourceUrl: json['sourceUrl'] as String? ?? '',
|
||||||
|
enabled: json['enabled'] as bool? ?? true,
|
||||||
|
globalParams:
|
||||||
|
json['globalParams'] as Map<String, dynamic>? ??
|
||||||
|
const <String, Object?>{},
|
||||||
|
installedAt: DateTime.parse(json['installedAt'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$InstalledScriptWidgetToJson(
|
||||||
|
_InstalledScriptWidget instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'manifest': instance.manifest,
|
||||||
|
'scriptSource': instance.scriptSource,
|
||||||
|
'sourceUrl': instance.sourceUrl,
|
||||||
|
'enabled': instance.enabled,
|
||||||
|
'globalParams': instance.globalParams,
|
||||||
|
'installedAt': instance.installedAt.toIso8601String(),
|
||||||
|
'updatedAt': instance.updatedAt.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptWidgetCatalogEntry _$ScriptWidgetCatalogEntryFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _ScriptWidgetCatalogEntry(
|
||||||
|
id: json['id'] as String,
|
||||||
|
title: json['title'] as String,
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
requiredVersion: json['requiredVersion'] as String? ?? '',
|
||||||
|
version: json['version'] as String? ?? '',
|
||||||
|
author: json['author'] as String? ?? '',
|
||||||
|
url: json['url'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetCatalogEntryToJson(
|
||||||
|
_ScriptWidgetCatalogEntry instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'requiredVersion': instance.requiredVersion,
|
||||||
|
'version': instance.version,
|
||||||
|
'author': instance.author,
|
||||||
|
'url': instance.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptWidgetCatalog _$ScriptWidgetCatalogFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ScriptWidgetCatalog(
|
||||||
|
title: json['title'] as String,
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
icon: json['icon'] as String? ?? '',
|
||||||
|
widgets:
|
||||||
|
(json['widgets'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) => ScriptWidgetCatalogEntry.fromJson(
|
||||||
|
e as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
const <ScriptWidgetCatalogEntry>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetCatalogToJson(
|
||||||
|
_ScriptWidgetCatalog instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'icon': instance.icon,
|
||||||
|
'widgets': instance.widgets,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ScriptWidgetSubscription _$ScriptWidgetSubscriptionFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _ScriptWidgetSubscription(
|
||||||
|
url: json['url'] as String,
|
||||||
|
catalog: ScriptWidgetCatalog.fromJson(
|
||||||
|
json['catalog'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
refreshedAt: DateTime.parse(json['refreshedAt'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ScriptWidgetSubscriptionToJson(
|
||||||
|
_ScriptWidgetSubscription instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'url': instance.url,
|
||||||
|
'catalog': instance.catalog,
|
||||||
|
'refreshedAt': instance.refreshedAt.toIso8601String(),
|
||||||
|
};
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'server.freezed.dart';
|
||||||
|
part 'server.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyServer with _$EmbyServer {
|
||||||
|
const factory EmbyServer({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
required String baseUrl,
|
||||||
|
required String createdAt,
|
||||||
|
required String updatedAt,
|
||||||
|
@JsonKey(includeIfNull: false) String? lastConnectedAt,
|
||||||
|
|
||||||
|
|
||||||
|
@JsonKey(
|
||||||
|
fromJson: _stringOrEmpty,
|
||||||
|
toJson: _emptyStringToNull,
|
||||||
|
includeIfNull: false,
|
||||||
|
)
|
||||||
|
@Default('')
|
||||||
|
String iconUrl,
|
||||||
|
|
||||||
|
|
||||||
|
@Default(false) bool paused,
|
||||||
|
}) = _EmbyServer;
|
||||||
|
|
||||||
|
factory EmbyServer.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyServerFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyServerSaveReq with _$EmbyServerSaveReq {
|
||||||
|
const factory EmbyServerSaveReq({
|
||||||
|
@JsonKey(includeIfNull: false) String? id,
|
||||||
|
required String name,
|
||||||
|
required String baseUrl,
|
||||||
|
|
||||||
|
|
||||||
|
@JsonKey(
|
||||||
|
fromJson: _stringOrEmpty,
|
||||||
|
toJson: _emptyStringToNull,
|
||||||
|
includeIfNull: false,
|
||||||
|
)
|
||||||
|
@Default('')
|
||||||
|
String iconUrl,
|
||||||
|
}) = _EmbyServerSaveReq;
|
||||||
|
|
||||||
|
factory EmbyServerSaveReq.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyServerSaveReqFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class EmbyServerProbeReq with _$EmbyServerProbeReq {
|
||||||
|
const factory EmbyServerProbeReq({required String baseUrl}) =
|
||||||
|
_EmbyServerProbeReq;
|
||||||
|
|
||||||
|
factory EmbyServerProbeReq.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EmbyServerProbeReqFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ProbePublicInfoRes with _$ProbePublicInfoRes {
|
||||||
|
const factory ProbePublicInfoRes({
|
||||||
|
required String serverName,
|
||||||
|
required String version,
|
||||||
|
required String productName,
|
||||||
|
}) = _ProbePublicInfoRes;
|
||||||
|
|
||||||
|
factory ProbePublicInfoRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ProbePublicInfoResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _stringOrEmpty(Object? value) => value?.toString() ?? '';
|
||||||
|
|
||||||
|
String? _emptyStringToNull(String value) => value.isEmpty ? null : value;
|
||||||
Generated
+1112
File diff suppressed because it is too large
Load Diff
Generated
+66
@@ -0,0 +1,66 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'server.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_EmbyServer _$EmbyServerFromJson(Map<String, dynamic> json) => _EmbyServer(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
baseUrl: json['baseUrl'] as String,
|
||||||
|
createdAt: json['createdAt'] as String,
|
||||||
|
updatedAt: json['updatedAt'] as String,
|
||||||
|
lastConnectedAt: json['lastConnectedAt'] as String?,
|
||||||
|
iconUrl: json['iconUrl'] == null ? '' : _stringOrEmpty(json['iconUrl']),
|
||||||
|
paused: json['paused'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyServerToJson(_EmbyServer instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'baseUrl': instance.baseUrl,
|
||||||
|
'createdAt': instance.createdAt,
|
||||||
|
'updatedAt': instance.updatedAt,
|
||||||
|
'lastConnectedAt': ?instance.lastConnectedAt,
|
||||||
|
'iconUrl': ?_emptyStringToNull(instance.iconUrl),
|
||||||
|
'paused': instance.paused,
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyServerSaveReq _$EmbyServerSaveReqFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyServerSaveReq(
|
||||||
|
id: json['id'] as String?,
|
||||||
|
name: json['name'] as String,
|
||||||
|
baseUrl: json['baseUrl'] as String,
|
||||||
|
iconUrl: json['iconUrl'] == null ? '' : _stringOrEmpty(json['iconUrl']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyServerSaveReqToJson(_EmbyServerSaveReq instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': ?instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'baseUrl': instance.baseUrl,
|
||||||
|
'iconUrl': ?_emptyStringToNull(instance.iconUrl),
|
||||||
|
};
|
||||||
|
|
||||||
|
_EmbyServerProbeReq _$EmbyServerProbeReqFromJson(Map<String, dynamic> json) =>
|
||||||
|
_EmbyServerProbeReq(baseUrl: json['baseUrl'] as String);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EmbyServerProbeReqToJson(_EmbyServerProbeReq instance) =>
|
||||||
|
<String, dynamic>{'baseUrl': instance.baseUrl};
|
||||||
|
|
||||||
|
_ProbePublicInfoRes _$ProbePublicInfoResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ProbePublicInfoRes(
|
||||||
|
serverName: json['serverName'] as String,
|
||||||
|
version: json['version'] as String,
|
||||||
|
productName: json['productName'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ProbePublicInfoResToJson(_ProbePublicInfoRes instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'serverName': instance.serverName,
|
||||||
|
'version': instance.version,
|
||||||
|
'productName': instance.productName,
|
||||||
|
};
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'tmdb.freezed.dart';
|
||||||
|
part 'tmdb.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbCastMember with _$TmdbCastMember {
|
||||||
|
const factory TmdbCastMember({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
String? character,
|
||||||
|
@JsonKey(name: 'profile_path') String? profilePath,
|
||||||
|
@JsonKey(fromJson: _intOrZero) @Default(0) int order,
|
||||||
|
}) = _TmdbCastMember;
|
||||||
|
|
||||||
|
factory TmdbCastMember.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbCastMemberFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbCrewMember with _$TmdbCrewMember {
|
||||||
|
const factory TmdbCrewMember({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
String? job,
|
||||||
|
String? department,
|
||||||
|
@JsonKey(name: 'profile_path') String? profilePath,
|
||||||
|
}) = _TmdbCrewMember;
|
||||||
|
|
||||||
|
factory TmdbCrewMember.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbCrewMemberFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbCreditsRes with _$TmdbCreditsRes {
|
||||||
|
const factory TmdbCreditsRes({
|
||||||
|
@Default(<TmdbCastMember>[]) List<TmdbCastMember> cast,
|
||||||
|
@Default(<TmdbCrewMember>[]) List<TmdbCrewMember> crew,
|
||||||
|
}) = _TmdbCreditsRes;
|
||||||
|
|
||||||
|
factory TmdbCreditsRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbCreditsResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbRecommendation with _$TmdbRecommendation {
|
||||||
|
const factory TmdbRecommendation({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
required String title,
|
||||||
|
@JsonKey(name: 'poster_path') String? posterPath,
|
||||||
|
@JsonKey(name: 'backdrop_path') String? backdropPath,
|
||||||
|
String? overview,
|
||||||
|
@JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
|
||||||
|
@JsonKey(name: 'media_type') String? mediaType,
|
||||||
|
}) = _TmdbRecommendation;
|
||||||
|
|
||||||
|
factory TmdbRecommendation.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbRecommendationFromJson({
|
||||||
|
...json,
|
||||||
|
'title': json['title'] ?? json['name'] ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbRecommendationsRes with _$TmdbRecommendationsRes {
|
||||||
|
const factory TmdbRecommendationsRes({
|
||||||
|
@Default(<TmdbRecommendation>[]) List<TmdbRecommendation> results,
|
||||||
|
@JsonKey(name: 'total_results', fromJson: _intOrZero)
|
||||||
|
@Default(0)
|
||||||
|
int totalResults,
|
||||||
|
}) = _TmdbRecommendationsRes;
|
||||||
|
|
||||||
|
factory TmdbRecommendationsRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbRecommendationsResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbEpisode with _$TmdbEpisode {
|
||||||
|
const factory TmdbEpisode({
|
||||||
|
@JsonKey(name: 'episode_number', fromJson: _intRequired)
|
||||||
|
required int episodeNumber,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
String? overview,
|
||||||
|
@JsonKey(name: 'still_path') String? stillPath,
|
||||||
|
@JsonKey(name: 'air_date') String? airDate,
|
||||||
|
@JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
|
||||||
|
@JsonKey(fromJson: _intOrNull) int? runtime,
|
||||||
|
}) = _TmdbEpisode;
|
||||||
|
|
||||||
|
factory TmdbEpisode.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbEpisodeFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbSeasonDetail with _$TmdbSeasonDetail {
|
||||||
|
const factory TmdbSeasonDetail({
|
||||||
|
@JsonKey(name: 'season_number', fromJson: _intRequired)
|
||||||
|
required int seasonNumber,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
String? overview,
|
||||||
|
@JsonKey(name: 'poster_path') String? posterPath,
|
||||||
|
@Default(<TmdbEpisode>[]) List<TmdbEpisode> episodes,
|
||||||
|
}) = _TmdbSeasonDetail;
|
||||||
|
|
||||||
|
factory TmdbSeasonDetail.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbSeasonDetailFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbVideo with _$TmdbVideo {
|
||||||
|
const TmdbVideo._();
|
||||||
|
|
||||||
|
const factory TmdbVideo({
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String id,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String key,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String site,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String type,
|
||||||
|
@JsonKey(fromJson: _intOrZero) @Default(0) int size,
|
||||||
|
@JsonKey(fromJson: _boolOrFalse) @Default(false) bool official,
|
||||||
|
}) = _TmdbVideo;
|
||||||
|
|
||||||
|
factory TmdbVideo.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbVideoFromJson(json);
|
||||||
|
|
||||||
|
String? get youtubeUrl => site == 'YouTube' && key.isNotEmpty
|
||||||
|
? 'https://www.youtube.com/watch?v=$key'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
String? get youtubeThumbnailUrl => site == 'YouTube' && key.isNotEmpty
|
||||||
|
? 'https://img.youtube.com/vi/$key/mqdefault.jpg'
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbVideosRes with _$TmdbVideosRes {
|
||||||
|
const factory TmdbVideosRes({
|
||||||
|
@Default(<TmdbVideo>[]) List<TmdbVideo> results,
|
||||||
|
}) = _TmdbVideosRes;
|
||||||
|
|
||||||
|
factory TmdbVideosRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbVideosResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbPersonDetail with _$TmdbPersonDetail {
|
||||||
|
const factory TmdbPersonDetail({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
String? biography,
|
||||||
|
String? birthday,
|
||||||
|
String? deathday,
|
||||||
|
@JsonKey(name: 'place_of_birth') String? placeOfBirth,
|
||||||
|
@JsonKey(name: 'profile_path') String? profilePath,
|
||||||
|
@JsonKey(name: 'known_for_department') String? knownForDepartment,
|
||||||
|
@JsonKey(fromJson: _doubleOrNull) double? popularity,
|
||||||
|
}) = _TmdbPersonDetail;
|
||||||
|
|
||||||
|
factory TmdbPersonDetail.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbPersonDetailFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbPersonCredit with _$TmdbPersonCredit {
|
||||||
|
const factory TmdbPersonCredit({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
required String title,
|
||||||
|
String? character,
|
||||||
|
@JsonKey(name: 'poster_path') String? posterPath,
|
||||||
|
@JsonKey(name: 'release_date') String? releaseDate,
|
||||||
|
@JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
|
||||||
|
@JsonKey(name: 'media_type') String? mediaType,
|
||||||
|
}) = _TmdbPersonCredit;
|
||||||
|
|
||||||
|
factory TmdbPersonCredit.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbPersonCreditFromJson({
|
||||||
|
...json,
|
||||||
|
'title': json['title'] ?? json['name'] ?? '',
|
||||||
|
'release_date': json['release_date'] ?? json['first_air_date'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbPersonCreditsRes with _$TmdbPersonCreditsRes {
|
||||||
|
const factory TmdbPersonCreditsRes({
|
||||||
|
@Default(<TmdbPersonCredit>[]) List<TmdbPersonCredit> cast,
|
||||||
|
}) = _TmdbPersonCreditsRes;
|
||||||
|
|
||||||
|
factory TmdbPersonCreditsRes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbPersonCreditsResFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbGenre with _$TmdbGenre {
|
||||||
|
const factory TmdbGenre({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
}) = _TmdbGenre;
|
||||||
|
|
||||||
|
factory TmdbGenre.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbGenreFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbMediaDetail with _$TmdbMediaDetail {
|
||||||
|
const factory TmdbMediaDetail({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String title,
|
||||||
|
@JsonKey(name: 'poster_path') String? posterPath,
|
||||||
|
@JsonKey(name: 'backdrop_path') String? backdropPath,
|
||||||
|
@JsonKey(name: 'release_date') String? releaseDate,
|
||||||
|
@Default(<TmdbGenre>[]) List<TmdbGenre> genres,
|
||||||
|
@JsonKey(fromJson: _intOrNull) int? runtime,
|
||||||
|
String? tagline,
|
||||||
|
String? overview,
|
||||||
|
String? status,
|
||||||
|
String? homepage,
|
||||||
|
@JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
|
||||||
|
@JsonKey(name: 'vote_count', fromJson: _intOrNull) int? voteCount,
|
||||||
|
@JsonKey(fromJson: _intOrNull) int? revenue,
|
||||||
|
@JsonKey(fromJson: _intOrNull) int? budget,
|
||||||
|
}) = _TmdbMediaDetail;
|
||||||
|
|
||||||
|
|
||||||
|
factory TmdbMediaDetail.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbMediaDetailFromJson({
|
||||||
|
...json,
|
||||||
|
'title': json['title'] ?? json['name'] ?? '',
|
||||||
|
'release_date': json['release_date'] ?? json['first_air_date'],
|
||||||
|
'runtime':
|
||||||
|
json['runtime'] ??
|
||||||
|
(json['episode_run_time'] is List &&
|
||||||
|
(json['episode_run_time'] as List).isNotEmpty
|
||||||
|
? (json['episode_run_time'] as List).first
|
||||||
|
: null),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbImage with _$TmdbImage {
|
||||||
|
const TmdbImage._();
|
||||||
|
|
||||||
|
const factory TmdbImage({
|
||||||
|
@JsonKey(name: 'file_path', fromJson: _strOrEmpty)
|
||||||
|
@Default('')
|
||||||
|
String filePath,
|
||||||
|
@JsonKey(fromJson: _intOrZero) @Default(0) int width,
|
||||||
|
@JsonKey(fromJson: _intOrZero) @Default(0) int height,
|
||||||
|
@JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
|
||||||
|
@JsonKey(name: 'iso_639_1') String? iso639,
|
||||||
|
}) = _TmdbImage;
|
||||||
|
|
||||||
|
factory TmdbImage.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbImageFromJson(json);
|
||||||
|
|
||||||
|
bool get isLanguageNeutral => iso639 == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbImageSet with _$TmdbImageSet {
|
||||||
|
const factory TmdbImageSet({
|
||||||
|
@Default(<TmdbImage>[]) List<TmdbImage> backdrops,
|
||||||
|
@Default(<TmdbImage>[]) List<TmdbImage> posters,
|
||||||
|
@Default(<TmdbImage>[]) List<TmdbImage> logos,
|
||||||
|
}) = _TmdbImageSet;
|
||||||
|
|
||||||
|
factory TmdbImageSet.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbImageSetFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbNetwork with _$TmdbNetwork {
|
||||||
|
const factory TmdbNetwork({
|
||||||
|
@JsonKey(fromJson: _intRequired) required int id,
|
||||||
|
@JsonKey(fromJson: _strOrEmpty) @Default('') String name,
|
||||||
|
@JsonKey(name: 'logo_path') String? logoPath,
|
||||||
|
@JsonKey(name: 'origin_country') String? originCountry,
|
||||||
|
}) = _TmdbNetwork;
|
||||||
|
|
||||||
|
factory TmdbNetwork.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbNetworkFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TmdbEnrichedDetail with _$TmdbEnrichedDetail {
|
||||||
|
const factory TmdbEnrichedDetail({
|
||||||
|
required TmdbMediaDetail detail,
|
||||||
|
TmdbVideosRes? videos,
|
||||||
|
TmdbCreditsRes? credits,
|
||||||
|
TmdbRecommendationsRes? recommendations,
|
||||||
|
TmdbImageSet? images,
|
||||||
|
|
||||||
|
|
||||||
|
String? imdbId,
|
||||||
|
@Default(<TmdbNetwork>[]) List<TmdbNetwork> networks,
|
||||||
|
}) = _TmdbEnrichedDetail;
|
||||||
|
|
||||||
|
factory TmdbEnrichedDetail.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TmdbEnrichedDetailFromJson({
|
||||||
|
'detail': json,
|
||||||
|
'videos': json['videos'],
|
||||||
|
'credits': json['credits'],
|
||||||
|
'recommendations': json['recommendations'],
|
||||||
|
'images': json['images'],
|
||||||
|
'imdbId': _imdbIdOf(json['external_ids']),
|
||||||
|
'networks': json['networks'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String? _imdbIdOf(Object? externalIds) {
|
||||||
|
if (externalIds is! Map) return null;
|
||||||
|
final v = externalIds['imdb_id'];
|
||||||
|
if (v is! String) return null;
|
||||||
|
final trimmed = v.trim();
|
||||||
|
return trimmed.isEmpty ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _intRequired(Object? v) => (v as num).toInt();
|
||||||
|
int _intOrZero(Object? v) => (v as num?)?.toInt() ?? 0;
|
||||||
|
int? _intOrNull(Object? v) => (v as num?)?.toInt();
|
||||||
|
double? _doubleOrNull(Object? v) => (v as num?)?.toDouble();
|
||||||
|
bool _boolOrFalse(Object? v) => (v as bool?) ?? false;
|
||||||
|
String _strOrEmpty(Object? v) => v?.toString() ?? '';
|
||||||
Generated
+5162
File diff suppressed because it is too large
Load Diff
Generated
+384
@@ -0,0 +1,384 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tmdb.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_TmdbCastMember _$TmdbCastMemberFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbCastMember(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
character: json['character'] as String?,
|
||||||
|
profilePath: json['profile_path'] as String?,
|
||||||
|
order: json['order'] == null ? 0 : _intOrZero(json['order']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbCastMemberToJson(_TmdbCastMember instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'character': instance.character,
|
||||||
|
'profile_path': instance.profilePath,
|
||||||
|
'order': instance.order,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbCrewMember _$TmdbCrewMemberFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbCrewMember(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
job: json['job'] as String?,
|
||||||
|
department: json['department'] as String?,
|
||||||
|
profilePath: json['profile_path'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbCrewMemberToJson(_TmdbCrewMember instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'job': instance.job,
|
||||||
|
'department': instance.department,
|
||||||
|
'profile_path': instance.profilePath,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbCreditsRes _$TmdbCreditsResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbCreditsRes(
|
||||||
|
cast:
|
||||||
|
(json['cast'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbCastMember.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbCastMember>[],
|
||||||
|
crew:
|
||||||
|
(json['crew'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbCrewMember.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbCrewMember>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbCreditsResToJson(_TmdbCreditsRes instance) =>
|
||||||
|
<String, dynamic>{'cast': instance.cast, 'crew': instance.crew};
|
||||||
|
|
||||||
|
_TmdbRecommendation _$TmdbRecommendationFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbRecommendation(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
title: json['title'] as String,
|
||||||
|
posterPath: json['poster_path'] as String?,
|
||||||
|
backdropPath: json['backdrop_path'] as String?,
|
||||||
|
overview: json['overview'] as String?,
|
||||||
|
voteAverage: _doubleOrNull(json['vote_average']),
|
||||||
|
mediaType: json['media_type'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbRecommendationToJson(_TmdbRecommendation instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'poster_path': instance.posterPath,
|
||||||
|
'backdrop_path': instance.backdropPath,
|
||||||
|
'overview': instance.overview,
|
||||||
|
'vote_average': instance.voteAverage,
|
||||||
|
'media_type': instance.mediaType,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbRecommendationsRes _$TmdbRecommendationsResFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _TmdbRecommendationsRes(
|
||||||
|
results:
|
||||||
|
(json['results'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbRecommendation.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbRecommendation>[],
|
||||||
|
totalResults: json['total_results'] == null
|
||||||
|
? 0
|
||||||
|
: _intOrZero(json['total_results']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbRecommendationsResToJson(
|
||||||
|
_TmdbRecommendationsRes instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'results': instance.results,
|
||||||
|
'total_results': instance.totalResults,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbEpisode _$TmdbEpisodeFromJson(Map<String, dynamic> json) => _TmdbEpisode(
|
||||||
|
episodeNumber: _intRequired(json['episode_number']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
overview: json['overview'] as String?,
|
||||||
|
stillPath: json['still_path'] as String?,
|
||||||
|
airDate: json['air_date'] as String?,
|
||||||
|
voteAverage: _doubleOrNull(json['vote_average']),
|
||||||
|
runtime: _intOrNull(json['runtime']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbEpisodeToJson(_TmdbEpisode instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'episode_number': instance.episodeNumber,
|
||||||
|
'name': instance.name,
|
||||||
|
'overview': instance.overview,
|
||||||
|
'still_path': instance.stillPath,
|
||||||
|
'air_date': instance.airDate,
|
||||||
|
'vote_average': instance.voteAverage,
|
||||||
|
'runtime': instance.runtime,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbSeasonDetail _$TmdbSeasonDetailFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbSeasonDetail(
|
||||||
|
seasonNumber: _intRequired(json['season_number']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
overview: json['overview'] as String?,
|
||||||
|
posterPath: json['poster_path'] as String?,
|
||||||
|
episodes:
|
||||||
|
(json['episodes'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbEpisode.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbEpisode>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbSeasonDetailToJson(_TmdbSeasonDetail instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'season_number': instance.seasonNumber,
|
||||||
|
'name': instance.name,
|
||||||
|
'overview': instance.overview,
|
||||||
|
'poster_path': instance.posterPath,
|
||||||
|
'episodes': instance.episodes,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbVideo _$TmdbVideoFromJson(Map<String, dynamic> json) => _TmdbVideo(
|
||||||
|
id: json['id'] == null ? '' : _strOrEmpty(json['id']),
|
||||||
|
key: json['key'] == null ? '' : _strOrEmpty(json['key']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
site: json['site'] == null ? '' : _strOrEmpty(json['site']),
|
||||||
|
type: json['type'] == null ? '' : _strOrEmpty(json['type']),
|
||||||
|
size: json['size'] == null ? 0 : _intOrZero(json['size']),
|
||||||
|
official: json['official'] == null ? false : _boolOrFalse(json['official']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbVideoToJson(_TmdbVideo instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'key': instance.key,
|
||||||
|
'name': instance.name,
|
||||||
|
'site': instance.site,
|
||||||
|
'type': instance.type,
|
||||||
|
'size': instance.size,
|
||||||
|
'official': instance.official,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbVideosRes _$TmdbVideosResFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbVideosRes(
|
||||||
|
results:
|
||||||
|
(json['results'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbVideo.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbVideo>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbVideosResToJson(_TmdbVideosRes instance) =>
|
||||||
|
<String, dynamic>{'results': instance.results};
|
||||||
|
|
||||||
|
_TmdbPersonDetail _$TmdbPersonDetailFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbPersonDetail(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
biography: json['biography'] as String?,
|
||||||
|
birthday: json['birthday'] as String?,
|
||||||
|
deathday: json['deathday'] as String?,
|
||||||
|
placeOfBirth: json['place_of_birth'] as String?,
|
||||||
|
profilePath: json['profile_path'] as String?,
|
||||||
|
knownForDepartment: json['known_for_department'] as String?,
|
||||||
|
popularity: _doubleOrNull(json['popularity']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbPersonDetailToJson(_TmdbPersonDetail instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'biography': instance.biography,
|
||||||
|
'birthday': instance.birthday,
|
||||||
|
'deathday': instance.deathday,
|
||||||
|
'place_of_birth': instance.placeOfBirth,
|
||||||
|
'profile_path': instance.profilePath,
|
||||||
|
'known_for_department': instance.knownForDepartment,
|
||||||
|
'popularity': instance.popularity,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbPersonCredit _$TmdbPersonCreditFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbPersonCredit(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
title: json['title'] as String,
|
||||||
|
character: json['character'] as String?,
|
||||||
|
posterPath: json['poster_path'] as String?,
|
||||||
|
releaseDate: json['release_date'] as String?,
|
||||||
|
voteAverage: _doubleOrNull(json['vote_average']),
|
||||||
|
mediaType: json['media_type'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbPersonCreditToJson(_TmdbPersonCredit instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'character': instance.character,
|
||||||
|
'poster_path': instance.posterPath,
|
||||||
|
'release_date': instance.releaseDate,
|
||||||
|
'vote_average': instance.voteAverage,
|
||||||
|
'media_type': instance.mediaType,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbPersonCreditsRes _$TmdbPersonCreditsResFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _TmdbPersonCreditsRes(
|
||||||
|
cast:
|
||||||
|
(json['cast'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbPersonCredit.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbPersonCredit>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbPersonCreditsResToJson(
|
||||||
|
_TmdbPersonCreditsRes instance,
|
||||||
|
) => <String, dynamic>{'cast': instance.cast};
|
||||||
|
|
||||||
|
_TmdbGenre _$TmdbGenreFromJson(Map<String, dynamic> json) => _TmdbGenre(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbGenreToJson(_TmdbGenre instance) =>
|
||||||
|
<String, dynamic>{'id': instance.id, 'name': instance.name};
|
||||||
|
|
||||||
|
_TmdbMediaDetail _$TmdbMediaDetailFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbMediaDetail(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
title: json['title'] == null ? '' : _strOrEmpty(json['title']),
|
||||||
|
posterPath: json['poster_path'] as String?,
|
||||||
|
backdropPath: json['backdrop_path'] as String?,
|
||||||
|
releaseDate: json['release_date'] as String?,
|
||||||
|
genres:
|
||||||
|
(json['genres'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbGenre.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbGenre>[],
|
||||||
|
runtime: _intOrNull(json['runtime']),
|
||||||
|
tagline: json['tagline'] as String?,
|
||||||
|
overview: json['overview'] as String?,
|
||||||
|
status: json['status'] as String?,
|
||||||
|
homepage: json['homepage'] as String?,
|
||||||
|
voteAverage: _doubleOrNull(json['vote_average']),
|
||||||
|
voteCount: _intOrNull(json['vote_count']),
|
||||||
|
revenue: _intOrNull(json['revenue']),
|
||||||
|
budget: _intOrNull(json['budget']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbMediaDetailToJson(_TmdbMediaDetail instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'poster_path': instance.posterPath,
|
||||||
|
'backdrop_path': instance.backdropPath,
|
||||||
|
'release_date': instance.releaseDate,
|
||||||
|
'genres': instance.genres,
|
||||||
|
'runtime': instance.runtime,
|
||||||
|
'tagline': instance.tagline,
|
||||||
|
'overview': instance.overview,
|
||||||
|
'status': instance.status,
|
||||||
|
'homepage': instance.homepage,
|
||||||
|
'vote_average': instance.voteAverage,
|
||||||
|
'vote_count': instance.voteCount,
|
||||||
|
'revenue': instance.revenue,
|
||||||
|
'budget': instance.budget,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbImage _$TmdbImageFromJson(Map<String, dynamic> json) => _TmdbImage(
|
||||||
|
filePath: json['file_path'] == null ? '' : _strOrEmpty(json['file_path']),
|
||||||
|
width: json['width'] == null ? 0 : _intOrZero(json['width']),
|
||||||
|
height: json['height'] == null ? 0 : _intOrZero(json['height']),
|
||||||
|
voteAverage: _doubleOrNull(json['vote_average']),
|
||||||
|
iso639: json['iso_639_1'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbImageToJson(_TmdbImage instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'file_path': instance.filePath,
|
||||||
|
'width': instance.width,
|
||||||
|
'height': instance.height,
|
||||||
|
'vote_average': instance.voteAverage,
|
||||||
|
'iso_639_1': instance.iso639,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbImageSet _$TmdbImageSetFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbImageSet(
|
||||||
|
backdrops:
|
||||||
|
(json['backdrops'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbImage.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbImage>[],
|
||||||
|
posters:
|
||||||
|
(json['posters'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbImage.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbImage>[],
|
||||||
|
logos:
|
||||||
|
(json['logos'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbImage.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbImage>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbImageSetToJson(_TmdbImageSet instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'backdrops': instance.backdrops,
|
||||||
|
'posters': instance.posters,
|
||||||
|
'logos': instance.logos,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbNetwork _$TmdbNetworkFromJson(Map<String, dynamic> json) => _TmdbNetwork(
|
||||||
|
id: _intRequired(json['id']),
|
||||||
|
name: json['name'] == null ? '' : _strOrEmpty(json['name']),
|
||||||
|
logoPath: json['logo_path'] as String?,
|
||||||
|
originCountry: json['origin_country'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbNetworkToJson(_TmdbNetwork instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'logo_path': instance.logoPath,
|
||||||
|
'origin_country': instance.originCountry,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TmdbEnrichedDetail _$TmdbEnrichedDetailFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TmdbEnrichedDetail(
|
||||||
|
detail: TmdbMediaDetail.fromJson(json['detail'] as Map<String, dynamic>),
|
||||||
|
videos: json['videos'] == null
|
||||||
|
? null
|
||||||
|
: TmdbVideosRes.fromJson(json['videos'] as Map<String, dynamic>),
|
||||||
|
credits: json['credits'] == null
|
||||||
|
? null
|
||||||
|
: TmdbCreditsRes.fromJson(json['credits'] as Map<String, dynamic>),
|
||||||
|
recommendations: json['recommendations'] == null
|
||||||
|
? null
|
||||||
|
: TmdbRecommendationsRes.fromJson(
|
||||||
|
json['recommendations'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
images: json['images'] == null
|
||||||
|
? null
|
||||||
|
: TmdbImageSet.fromJson(json['images'] as Map<String, dynamic>),
|
||||||
|
imdbId: json['imdbId'] as String?,
|
||||||
|
networks:
|
||||||
|
(json['networks'] as List<dynamic>?)
|
||||||
|
?.map((e) => TmdbNetwork.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const <TmdbNetwork>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TmdbEnrichedDetailToJson(_TmdbEnrichedDetail instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'detail': instance.detail,
|
||||||
|
'videos': instance.videos,
|
||||||
|
'credits': instance.credits,
|
||||||
|
'recommendations': instance.recommendations,
|
||||||
|
'images': instance.images,
|
||||||
|
'imdbId': instance.imdbId,
|
||||||
|
'networks': instance.networks,
|
||||||
|
};
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import 'trakt_ids.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class TraktCalendarEntry {
|
||||||
|
|
||||||
|
final DateTime firstAired;
|
||||||
|
|
||||||
|
|
||||||
|
final String type;
|
||||||
|
|
||||||
|
final String? showTitle;
|
||||||
|
final int? showYear;
|
||||||
|
final TraktIds? showIds;
|
||||||
|
final int? episodeSeason;
|
||||||
|
final int? episodeNumber;
|
||||||
|
final String? episodeTitle;
|
||||||
|
final TraktIds? episodeIds;
|
||||||
|
|
||||||
|
final String? movieTitle;
|
||||||
|
final int? movieYear;
|
||||||
|
final TraktIds? movieIds;
|
||||||
|
|
||||||
|
|
||||||
|
final String? posterUrl;
|
||||||
|
|
||||||
|
const TraktCalendarEntry({
|
||||||
|
required this.firstAired,
|
||||||
|
required this.type,
|
||||||
|
this.showTitle,
|
||||||
|
this.showYear,
|
||||||
|
this.showIds,
|
||||||
|
this.episodeSeason,
|
||||||
|
this.episodeNumber,
|
||||||
|
this.episodeTitle,
|
||||||
|
this.episodeIds,
|
||||||
|
this.movieTitle,
|
||||||
|
this.movieYear,
|
||||||
|
this.movieIds,
|
||||||
|
this.posterUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
TraktCalendarEntry withPoster(String? url) => TraktCalendarEntry(
|
||||||
|
firstAired: firstAired,
|
||||||
|
type: type,
|
||||||
|
showTitle: showTitle,
|
||||||
|
showYear: showYear,
|
||||||
|
showIds: showIds,
|
||||||
|
episodeSeason: episodeSeason,
|
||||||
|
episodeNumber: episodeNumber,
|
||||||
|
episodeTitle: episodeTitle,
|
||||||
|
episodeIds: episodeIds,
|
||||||
|
movieTitle: movieTitle,
|
||||||
|
movieYear: movieYear,
|
||||||
|
movieIds: movieIds,
|
||||||
|
posterUrl: url,
|
||||||
|
);
|
||||||
|
|
||||||
|
TraktCalendarEntry withTitle(String title) => TraktCalendarEntry(
|
||||||
|
firstAired: firstAired,
|
||||||
|
type: type,
|
||||||
|
showTitle: type == 'movie' ? showTitle : title,
|
||||||
|
showYear: showYear,
|
||||||
|
showIds: showIds,
|
||||||
|
episodeSeason: episodeSeason,
|
||||||
|
episodeNumber: episodeNumber,
|
||||||
|
episodeTitle: episodeTitle,
|
||||||
|
episodeIds: episodeIds,
|
||||||
|
movieTitle: type == 'movie' ? title : movieTitle,
|
||||||
|
movieYear: movieYear,
|
||||||
|
movieIds: movieIds,
|
||||||
|
posterUrl: posterUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
int? get tmdbId => type == 'movie' ? movieIds?.tmdb : showIds?.tmdb;
|
||||||
|
|
||||||
|
|
||||||
|
String get mediaType => type == 'movie' ? 'movie' : 'tv';
|
||||||
|
|
||||||
|
|
||||||
|
String get displayTitle =>
|
||||||
|
type == 'movie' ? (movieTitle ?? '') : (showTitle ?? '');
|
||||||
|
|
||||||
|
|
||||||
|
String get displaySubtitle {
|
||||||
|
if (type == 'movie') return movieYear?.toString() ?? '';
|
||||||
|
final parts = <String>[];
|
||||||
|
if (episodeSeason != null && episodeNumber != null) {
|
||||||
|
parts.add(
|
||||||
|
'S${episodeSeason.toString().padLeft(2, '0')}'
|
||||||
|
'E${episodeNumber.toString().padLeft(2, '0')}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (episodeTitle != null && episodeTitle!.isNotEmpty) {
|
||||||
|
parts.add(episodeTitle!);
|
||||||
|
}
|
||||||
|
return parts.join(' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static TraktCalendarEntry? fromShowJson(Map<String, dynamic> json) {
|
||||||
|
final aired = _parseDate(json['first_aired']);
|
||||||
|
if (aired == null) return null;
|
||||||
|
|
||||||
|
final show = json['show'];
|
||||||
|
final episode = json['episode'];
|
||||||
|
if (show is! Map<String, dynamic>) return null;
|
||||||
|
|
||||||
|
return TraktCalendarEntry(
|
||||||
|
firstAired: aired,
|
||||||
|
type: 'show',
|
||||||
|
showTitle: show['title'] as String?,
|
||||||
|
showYear: (show['year'] as num?)?.toInt(),
|
||||||
|
showIds: TraktIds.fromJson(show['ids']),
|
||||||
|
episodeSeason: episode is Map<String, dynamic>
|
||||||
|
? (episode['season'] as num?)?.toInt()
|
||||||
|
: null,
|
||||||
|
episodeNumber: episode is Map<String, dynamic>
|
||||||
|
? (episode['number'] as num?)?.toInt()
|
||||||
|
: null,
|
||||||
|
episodeTitle: episode is Map<String, dynamic>
|
||||||
|
? episode['title'] as String?
|
||||||
|
: null,
|
||||||
|
episodeIds: episode is Map<String, dynamic>
|
||||||
|
? TraktIds.fromJson(episode['ids'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static TraktCalendarEntry? fromMovieJson(Map<String, dynamic> json) {
|
||||||
|
final releasedStr = json['released'] as String?;
|
||||||
|
final aired = releasedStr != null && releasedStr.isNotEmpty
|
||||||
|
? DateTime.tryParse(releasedStr)
|
||||||
|
: null;
|
||||||
|
if (aired == null) return null;
|
||||||
|
|
||||||
|
final movie = json['movie'];
|
||||||
|
if (movie is! Map<String, dynamic>) return null;
|
||||||
|
|
||||||
|
return TraktCalendarEntry(
|
||||||
|
firstAired: aired,
|
||||||
|
type: 'movie',
|
||||||
|
movieTitle: movie['title'] as String?,
|
||||||
|
movieYear: (movie['year'] as num?)?.toInt(),
|
||||||
|
movieIds: TraktIds.fromJson(movie['ids']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime? _parseDate(Object? raw) {
|
||||||
|
if (raw is! String || raw.isEmpty) return null;
|
||||||
|
return DateTime.tryParse(raw);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class TraktIds {
|
||||||
|
final int? trakt;
|
||||||
|
final String? imdb;
|
||||||
|
final int? tmdb;
|
||||||
|
final int? tvdb;
|
||||||
|
final String? slug;
|
||||||
|
|
||||||
|
const TraktIds({this.trakt, this.imdb, this.tmdb, this.tvdb, this.slug});
|
||||||
|
|
||||||
|
factory TraktIds.fromJson(Object? raw) {
|
||||||
|
if (raw is! Map) return const TraktIds();
|
||||||
|
final json = raw.cast<String, dynamic>();
|
||||||
|
return TraktIds(
|
||||||
|
trakt: (json['trakt'] as num?)?.toInt(),
|
||||||
|
imdb: json['imdb'] as String?,
|
||||||
|
tmdb: (json['tmdb'] as num?)?.toInt(),
|
||||||
|
tvdb: (json['tvdb'] as num?)?.toInt(),
|
||||||
|
slug: json['slug'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool get hasReliableId =>
|
||||||
|
(trakt != null && trakt! > 0) ||
|
||||||
|
(imdb != null && imdb!.isNotEmpty) ||
|
||||||
|
(tmdb != null && tmdb! > 0) ||
|
||||||
|
(tvdb != null && tvdb! > 0);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final out = <String, dynamic>{};
|
||||||
|
if (trakt != null && trakt! > 0) out['trakt'] = trakt;
|
||||||
|
if (imdb != null && imdb!.isNotEmpty) out['imdb'] = imdb;
|
||||||
|
if (tmdb != null && tmdb! > 0) out['tmdb'] = tmdb;
|
||||||
|
if (tvdb != null && tvdb! > 0) out['tvdb'] = tvdb;
|
||||||
|
if (slug != null && slug!.isNotEmpty) out['slug'] = slug;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import 'trakt_ids.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class TraktPlaybackEntry {
|
||||||
|
|
||||||
|
final double progress;
|
||||||
|
final DateTime? pausedAt;
|
||||||
|
|
||||||
|
|
||||||
|
final String type;
|
||||||
|
|
||||||
|
final String? movieTitle;
|
||||||
|
final int? movieYear;
|
||||||
|
final TraktIds? movieIds;
|
||||||
|
|
||||||
|
final String? showTitle;
|
||||||
|
final int? showYear;
|
||||||
|
final TraktIds? showIds;
|
||||||
|
|
||||||
|
final int? episodeSeason;
|
||||||
|
final int? episodeNumber;
|
||||||
|
final String? episodeTitle;
|
||||||
|
final TraktIds? episodeIds;
|
||||||
|
|
||||||
|
const TraktPlaybackEntry({
|
||||||
|
required this.progress,
|
||||||
|
required this.type,
|
||||||
|
this.pausedAt,
|
||||||
|
this.movieTitle,
|
||||||
|
this.movieYear,
|
||||||
|
this.movieIds,
|
||||||
|
this.showTitle,
|
||||||
|
this.showYear,
|
||||||
|
this.showIds,
|
||||||
|
this.episodeSeason,
|
||||||
|
this.episodeNumber,
|
||||||
|
this.episodeTitle,
|
||||||
|
this.episodeIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
static TraktPlaybackEntry? fromJson(Map<String, dynamic> json) {
|
||||||
|
final movie = json['movie'];
|
||||||
|
final show = json['show'];
|
||||||
|
final episode = json['episode'];
|
||||||
|
final hasMovie = movie is Map<String, dynamic>;
|
||||||
|
final hasShow = show is Map<String, dynamic>;
|
||||||
|
if (!hasMovie && !hasShow) return null;
|
||||||
|
|
||||||
|
final rawType = json['type'] as String? ?? '';
|
||||||
|
return TraktPlaybackEntry(
|
||||||
|
progress: (json['progress'] as num?)?.toDouble() ?? 0,
|
||||||
|
pausedAt: _parseDate(json['paused_at']),
|
||||||
|
type: rawType.isNotEmpty ? rawType : (hasMovie ? 'movie' : 'episode'),
|
||||||
|
movieTitle: hasMovie ? movie['title'] as String? : null,
|
||||||
|
movieYear: hasMovie ? (movie['year'] as num?)?.toInt() : null,
|
||||||
|
movieIds: hasMovie ? TraktIds.fromJson(movie['ids']) : null,
|
||||||
|
showTitle: hasShow ? show['title'] as String? : null,
|
||||||
|
showYear: hasShow ? (show['year'] as num?)?.toInt() : null,
|
||||||
|
showIds: hasShow ? TraktIds.fromJson(show['ids']) : null,
|
||||||
|
episodeSeason: episode is Map<String, dynamic>
|
||||||
|
? (episode['season'] as num?)?.toInt()
|
||||||
|
: null,
|
||||||
|
episodeNumber: episode is Map<String, dynamic>
|
||||||
|
? (episode['number'] as num?)?.toInt()
|
||||||
|
: null,
|
||||||
|
episodeTitle: episode is Map<String, dynamic>
|
||||||
|
? episode['title'] as String?
|
||||||
|
: null,
|
||||||
|
episodeIds: episode is Map<String, dynamic>
|
||||||
|
? TraktIds.fromJson(episode['ids'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime? _parseDate(Object? raw) {
|
||||||
|
if (raw is! String || raw.isEmpty) return null;
|
||||||
|
return DateTime.tryParse(raw);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
enum TraktResponseClass {
|
||||||
|
|
||||||
|
success,
|
||||||
|
|
||||||
|
|
||||||
|
duplicate,
|
||||||
|
|
||||||
|
|
||||||
|
invalid,
|
||||||
|
|
||||||
|
|
||||||
|
rateLimited,
|
||||||
|
|
||||||
|
|
||||||
|
unauthorized,
|
||||||
|
|
||||||
|
|
||||||
|
transient,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TraktScrobbleOutcome {
|
||||||
|
final TraktResponseClass cls;
|
||||||
|
|
||||||
|
|
||||||
|
final Duration? retryAfter;
|
||||||
|
|
||||||
|
const TraktScrobbleOutcome(this.cls, {this.retryAfter});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TraktResponseClass classifyTraktStatus(int statusCode) {
|
||||||
|
if (statusCode >= 200 && statusCode < 300) return TraktResponseClass.success;
|
||||||
|
switch (statusCode) {
|
||||||
|
case 401:
|
||||||
|
return TraktResponseClass.unauthorized;
|
||||||
|
case 409:
|
||||||
|
return TraktResponseClass.duplicate;
|
||||||
|
case 422:
|
||||||
|
return TraktResponseClass.invalid;
|
||||||
|
case 429:
|
||||||
|
return TraktResponseClass.rateLimited;
|
||||||
|
default:
|
||||||
|
return TraktResponseClass.transient;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import 'trakt_ids.dart';
|
||||||
|
|
||||||
|
|
||||||
|
enum TraktMediaKind { movie, episode }
|
||||||
|
|
||||||
|
|
||||||
|
class TraktScrobbleTarget {
|
||||||
|
final TraktMediaKind kind;
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final int? year;
|
||||||
|
final TraktIds ids;
|
||||||
|
|
||||||
|
final int? season;
|
||||||
|
final int? number;
|
||||||
|
final TraktIds episodeIds;
|
||||||
|
|
||||||
|
const TraktScrobbleTarget._({
|
||||||
|
required this.kind,
|
||||||
|
required this.title,
|
||||||
|
required this.year,
|
||||||
|
required this.ids,
|
||||||
|
this.season,
|
||||||
|
this.number,
|
||||||
|
this.episodeIds = const TraktIds(),
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TraktScrobbleTarget.movie({
|
||||||
|
required String title,
|
||||||
|
required int? year,
|
||||||
|
required TraktIds ids,
|
||||||
|
}) {
|
||||||
|
return TraktScrobbleTarget._(
|
||||||
|
kind: TraktMediaKind.movie,
|
||||||
|
title: title,
|
||||||
|
year: year,
|
||||||
|
ids: ids,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory TraktScrobbleTarget.episode({
|
||||||
|
required String showTitle,
|
||||||
|
required int? showYear,
|
||||||
|
required TraktIds showIds,
|
||||||
|
required int? season,
|
||||||
|
required int? number,
|
||||||
|
required TraktIds episodeIds,
|
||||||
|
}) {
|
||||||
|
return TraktScrobbleTarget._(
|
||||||
|
kind: TraktMediaKind.episode,
|
||||||
|
title: showTitle,
|
||||||
|
year: showYear,
|
||||||
|
ids: showIds,
|
||||||
|
season: season,
|
||||||
|
number: number,
|
||||||
|
episodeIds: episodeIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Map<String, dynamic> toBody({
|
||||||
|
required double progress,
|
||||||
|
String? appVersion,
|
||||||
|
String? appDate,
|
||||||
|
}) {
|
||||||
|
final body = <String, dynamic>{};
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
case TraktMediaKind.movie:
|
||||||
|
body['movie'] = _mediaNode(title, year, ids);
|
||||||
|
case TraktMediaKind.episode:
|
||||||
|
body['show'] = _mediaNode(title, year, ids);
|
||||||
|
final episode = <String, dynamic>{'season': season, 'number': number};
|
||||||
|
final epIds = episodeIds.toJson();
|
||||||
|
if (epIds.isNotEmpty) episode['ids'] = epIds;
|
||||||
|
body['episode'] = episode;
|
||||||
|
}
|
||||||
|
|
||||||
|
body['progress'] = progress.toDouble();
|
||||||
|
if (appVersion != null && appVersion.isNotEmpty) {
|
||||||
|
body['app_version'] = appVersion;
|
||||||
|
}
|
||||||
|
if (appDate != null && appDate.isNotEmpty) body['app_date'] = appDate;
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _mediaNode(
|
||||||
|
String title,
|
||||||
|
int? year,
|
||||||
|
TraktIds ids,
|
||||||
|
) {
|
||||||
|
final node = <String, dynamic>{'title': title};
|
||||||
|
if (year != null && year > 0) node['year'] = year;
|
||||||
|
node['ids'] = ids.toJson();
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
|
||||||
|
enum TraktSyncJobType {
|
||||||
|
scrobbleStart('scrobble_start'),
|
||||||
|
scrobblePause('scrobble_pause'),
|
||||||
|
scrobbleStop('scrobble_stop'),
|
||||||
|
historyAdd('history_add'),
|
||||||
|
historyRemove('history_remove');
|
||||||
|
|
||||||
|
const TraktSyncJobType(this.wire);
|
||||||
|
|
||||||
|
|
||||||
|
final String wire;
|
||||||
|
|
||||||
|
bool get isHistory => this == historyAdd || this == historyRemove;
|
||||||
|
|
||||||
|
static TraktSyncJobType fromWire(String wire) {
|
||||||
|
return TraktSyncJobType.values.firstWhere(
|
||||||
|
(e) => e.wire == wire,
|
||||||
|
orElse: () => TraktSyncJobType.scrobbleStart,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TraktSyncJob {
|
||||||
|
final String id;
|
||||||
|
final TraktSyncJobType type;
|
||||||
|
final Map<String, dynamic> body;
|
||||||
|
final int retryCount;
|
||||||
|
final DateTime? nextRetryAt;
|
||||||
|
final bool exhausted;
|
||||||
|
|
||||||
|
const TraktSyncJob({
|
||||||
|
required this.id,
|
||||||
|
required this.type,
|
||||||
|
required this.body,
|
||||||
|
this.retryCount = 0,
|
||||||
|
this.nextRetryAt,
|
||||||
|
this.exhausted = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
TraktSyncJob copyWith({
|
||||||
|
int? retryCount,
|
||||||
|
DateTime? nextRetryAt,
|
||||||
|
bool clearNextRetryAt = false,
|
||||||
|
bool? exhausted,
|
||||||
|
}) {
|
||||||
|
return TraktSyncJob(
|
||||||
|
id: id,
|
||||||
|
type: type,
|
||||||
|
body: body,
|
||||||
|
retryCount: retryCount ?? this.retryCount,
|
||||||
|
nextRetryAt: clearNextRetryAt ? null : (nextRetryAt ?? this.nextRetryAt),
|
||||||
|
exhausted: exhausted ?? this.exhausted,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'type': type.wire,
|
||||||
|
'body': body,
|
||||||
|
'retryCount': retryCount,
|
||||||
|
'nextRetryAt': nextRetryAt?.toIso8601String(),
|
||||||
|
'exhausted': exhausted,
|
||||||
|
};
|
||||||
|
|
||||||
|
factory TraktSyncJob.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TraktSyncJob(
|
||||||
|
id: json['id'] as String,
|
||||||
|
type: TraktSyncJobType.fromWire(json['type'] as String? ?? ''),
|
||||||
|
body: (json['body'] as Map?)?.cast<String, dynamic>() ?? const {},
|
||||||
|
retryCount: (json['retryCount'] as num?)?.toInt() ?? 0,
|
||||||
|
nextRetryAt: _parseDate(json['nextRetryAt']),
|
||||||
|
exhausted: json['exhausted'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime? _parseDate(Object? raw) {
|
||||||
|
if (raw is! String || raw.isEmpty) return null;
|
||||||
|
return DateTime.tryParse(raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'trakt_token.freezed.dart';
|
||||||
|
part 'trakt_token.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TraktToken with _$TraktToken {
|
||||||
|
const factory TraktToken({
|
||||||
|
@JsonKey(name: 'access_token') @Default('') String accessToken,
|
||||||
|
@JsonKey(name: 'refresh_token') @Default('') String refreshToken,
|
||||||
|
@JsonKey(name: 'expires_in') @Default(0) int expiresIn,
|
||||||
|
@JsonKey(name: 'created_at') @Default(0) int createdAt,
|
||||||
|
@JsonKey(name: 'token_type') @Default('') String tokenType,
|
||||||
|
@Default('') String scope,
|
||||||
|
}) = _TraktToken;
|
||||||
|
|
||||||
|
factory TraktToken.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TraktTokenFromJson(json);
|
||||||
|
}
|
||||||
+292
@@ -0,0 +1,292 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'trakt_token.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$TraktToken {
|
||||||
|
|
||||||
|
@JsonKey(name: 'access_token') String get accessToken;@JsonKey(name: 'refresh_token') String get refreshToken;@JsonKey(name: 'expires_in') int get expiresIn;@JsonKey(name: 'created_at') int get createdAt;@JsonKey(name: 'token_type') String get tokenType; String get scope;
|
||||||
|
/// Create a copy of TraktToken
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TraktTokenCopyWith<TraktToken> get copyWith => _$TraktTokenCopyWithImpl<TraktToken>(this as TraktToken, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this TraktToken to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is TraktToken&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.tokenType, tokenType) || other.tokenType == tokenType)&&(identical(other.scope, scope) || other.scope == scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,expiresIn,createdAt,tokenType,scope);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TraktToken(accessToken: $accessToken, refreshToken: $refreshToken, expiresIn: $expiresIn, createdAt: $createdAt, tokenType: $tokenType, scope: $scope)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $TraktTokenCopyWith<$Res> {
|
||||||
|
factory $TraktTokenCopyWith(TraktToken value, $Res Function(TraktToken) _then) = _$TraktTokenCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(name: 'access_token') String accessToken,@JsonKey(name: 'refresh_token') String refreshToken,@JsonKey(name: 'expires_in') int expiresIn,@JsonKey(name: 'created_at') int createdAt,@JsonKey(name: 'token_type') String tokenType, String scope
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$TraktTokenCopyWithImpl<$Res>
|
||||||
|
implements $TraktTokenCopyWith<$Res> {
|
||||||
|
_$TraktTokenCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final TraktToken _self;
|
||||||
|
final $Res Function(TraktToken) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TraktToken
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = null,Object? refreshToken = null,Object? expiresIn = null,Object? createdAt = null,Object? tokenType = null,Object? scope = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,expiresIn: null == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,tokenType: null == tokenType ? _self.tokenType : tokenType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,scope: null == scope ? _self.scope : scope // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [TraktToken].
|
||||||
|
extension TraktTokenPatterns on TraktToken {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _TraktToken value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktToken() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _TraktToken value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktToken():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _TraktToken value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktToken() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'access_token') String accessToken, @JsonKey(name: 'refresh_token') String refreshToken, @JsonKey(name: 'expires_in') int expiresIn, @JsonKey(name: 'created_at') int createdAt, @JsonKey(name: 'token_type') String tokenType, String scope)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktToken() when $default != null:
|
||||||
|
return $default(_that.accessToken,_that.refreshToken,_that.expiresIn,_that.createdAt,_that.tokenType,_that.scope);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'access_token') String accessToken, @JsonKey(name: 'refresh_token') String refreshToken, @JsonKey(name: 'expires_in') int expiresIn, @JsonKey(name: 'created_at') int createdAt, @JsonKey(name: 'token_type') String tokenType, String scope) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktToken():
|
||||||
|
return $default(_that.accessToken,_that.refreshToken,_that.expiresIn,_that.createdAt,_that.tokenType,_that.scope);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'access_token') String accessToken, @JsonKey(name: 'refresh_token') String refreshToken, @JsonKey(name: 'expires_in') int expiresIn, @JsonKey(name: 'created_at') int createdAt, @JsonKey(name: 'token_type') String tokenType, String scope)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktToken() when $default != null:
|
||||||
|
return $default(_that.accessToken,_that.refreshToken,_that.expiresIn,_that.createdAt,_that.tokenType,_that.scope);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _TraktToken implements TraktToken {
|
||||||
|
const _TraktToken({@JsonKey(name: 'access_token') this.accessToken = '', @JsonKey(name: 'refresh_token') this.refreshToken = '', @JsonKey(name: 'expires_in') this.expiresIn = 0, @JsonKey(name: 'created_at') this.createdAt = 0, @JsonKey(name: 'token_type') this.tokenType = '', this.scope = ''});
|
||||||
|
factory _TraktToken.fromJson(Map<String, dynamic> json) => _$TraktTokenFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey(name: 'access_token') final String accessToken;
|
||||||
|
@override@JsonKey(name: 'refresh_token') final String refreshToken;
|
||||||
|
@override@JsonKey(name: 'expires_in') final int expiresIn;
|
||||||
|
@override@JsonKey(name: 'created_at') final int createdAt;
|
||||||
|
@override@JsonKey(name: 'token_type') final String tokenType;
|
||||||
|
@override@JsonKey() final String scope;
|
||||||
|
|
||||||
|
/// Create a copy of TraktToken
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$TraktTokenCopyWith<_TraktToken> get copyWith => __$TraktTokenCopyWithImpl<_TraktToken>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$TraktTokenToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TraktToken&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.tokenType, tokenType) || other.tokenType == tokenType)&&(identical(other.scope, scope) || other.scope == scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,expiresIn,createdAt,tokenType,scope);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TraktToken(accessToken: $accessToken, refreshToken: $refreshToken, expiresIn: $expiresIn, createdAt: $createdAt, tokenType: $tokenType, scope: $scope)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$TraktTokenCopyWith<$Res> implements $TraktTokenCopyWith<$Res> {
|
||||||
|
factory _$TraktTokenCopyWith(_TraktToken value, $Res Function(_TraktToken) _then) = __$TraktTokenCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(name: 'access_token') String accessToken,@JsonKey(name: 'refresh_token') String refreshToken,@JsonKey(name: 'expires_in') int expiresIn,@JsonKey(name: 'created_at') int createdAt,@JsonKey(name: 'token_type') String tokenType, String scope
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$TraktTokenCopyWithImpl<$Res>
|
||||||
|
implements _$TraktTokenCopyWith<$Res> {
|
||||||
|
__$TraktTokenCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _TraktToken _self;
|
||||||
|
final $Res Function(_TraktToken) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TraktToken
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = null,Object? refreshToken = null,Object? expiresIn = null,Object? createdAt = null,Object? tokenType = null,Object? scope = null,}) {
|
||||||
|
return _then(_TraktToken(
|
||||||
|
accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,expiresIn: null == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,tokenType: null == tokenType ? _self.tokenType : tokenType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,scope: null == scope ? _self.scope : scope // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'trakt_token.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_TraktToken _$TraktTokenFromJson(Map<String, dynamic> json) => _TraktToken(
|
||||||
|
accessToken: json['access_token'] as String? ?? '',
|
||||||
|
refreshToken: json['refresh_token'] as String? ?? '',
|
||||||
|
expiresIn: (json['expires_in'] as num?)?.toInt() ?? 0,
|
||||||
|
createdAt: (json['created_at'] as num?)?.toInt() ?? 0,
|
||||||
|
tokenType: json['token_type'] as String? ?? '',
|
||||||
|
scope: json['scope'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TraktTokenToJson(_TraktToken instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'access_token': instance.accessToken,
|
||||||
|
'refresh_token': instance.refreshToken,
|
||||||
|
'expires_in': instance.expiresIn,
|
||||||
|
'created_at': instance.createdAt,
|
||||||
|
'token_type': instance.tokenType,
|
||||||
|
'scope': instance.scope,
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'trakt_user.freezed.dart';
|
||||||
|
part 'trakt_user.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TraktUser with _$TraktUser {
|
||||||
|
const factory TraktUser({@Default('') String username}) = _TraktUser;
|
||||||
|
|
||||||
|
factory TraktUser.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TraktUserFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class TraktUserSettings with _$TraktUserSettings {
|
||||||
|
const factory TraktUserSettings({TraktUser? user}) = _TraktUserSettings;
|
||||||
|
|
||||||
|
factory TraktUserSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TraktUserSettingsFromJson(json);
|
||||||
|
}
|
||||||
+564
@@ -0,0 +1,564 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'trakt_user.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$TraktUser {
|
||||||
|
|
||||||
|
String get username;
|
||||||
|
/// Create a copy of TraktUser
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TraktUserCopyWith<TraktUser> get copyWith => _$TraktUserCopyWithImpl<TraktUser>(this as TraktUser, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this TraktUser to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is TraktUser&&(identical(other.username, username) || other.username == username));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,username);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TraktUser(username: $username)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $TraktUserCopyWith<$Res> {
|
||||||
|
factory $TraktUserCopyWith(TraktUser value, $Res Function(TraktUser) _then) = _$TraktUserCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String username
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$TraktUserCopyWithImpl<$Res>
|
||||||
|
implements $TraktUserCopyWith<$Res> {
|
||||||
|
_$TraktUserCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final TraktUser _self;
|
||||||
|
final $Res Function(TraktUser) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TraktUser
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? username = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
username: null == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [TraktUser].
|
||||||
|
extension TraktUserPatterns on TraktUser {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _TraktUser value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUser() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _TraktUser value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUser():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _TraktUser value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUser() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String username)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUser() when $default != null:
|
||||||
|
return $default(_that.username);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String username) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUser():
|
||||||
|
return $default(_that.username);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String username)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUser() when $default != null:
|
||||||
|
return $default(_that.username);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _TraktUser implements TraktUser {
|
||||||
|
const _TraktUser({this.username = ''});
|
||||||
|
factory _TraktUser.fromJson(Map<String, dynamic> json) => _$TraktUserFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey() final String username;
|
||||||
|
|
||||||
|
/// Create a copy of TraktUser
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$TraktUserCopyWith<_TraktUser> get copyWith => __$TraktUserCopyWithImpl<_TraktUser>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$TraktUserToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TraktUser&&(identical(other.username, username) || other.username == username));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,username);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TraktUser(username: $username)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$TraktUserCopyWith<$Res> implements $TraktUserCopyWith<$Res> {
|
||||||
|
factory _$TraktUserCopyWith(_TraktUser value, $Res Function(_TraktUser) _then) = __$TraktUserCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String username
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$TraktUserCopyWithImpl<$Res>
|
||||||
|
implements _$TraktUserCopyWith<$Res> {
|
||||||
|
__$TraktUserCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _TraktUser _self;
|
||||||
|
final $Res Function(_TraktUser) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TraktUser
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? username = null,}) {
|
||||||
|
return _then(_TraktUser(
|
||||||
|
username: null == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$TraktUserSettings {
|
||||||
|
|
||||||
|
TraktUser? get user;
|
||||||
|
/// Create a copy of TraktUserSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TraktUserSettingsCopyWith<TraktUserSettings> get copyWith => _$TraktUserSettingsCopyWithImpl<TraktUserSettings>(this as TraktUserSettings, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this TraktUserSettings to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is TraktUserSettings&&(identical(other.user, user) || other.user == user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,user);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TraktUserSettings(user: $user)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $TraktUserSettingsCopyWith<$Res> {
|
||||||
|
factory $TraktUserSettingsCopyWith(TraktUserSettings value, $Res Function(TraktUserSettings) _then) = _$TraktUserSettingsCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
TraktUser? user
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$TraktUserCopyWith<$Res>? get user;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$TraktUserSettingsCopyWithImpl<$Res>
|
||||||
|
implements $TraktUserSettingsCopyWith<$Res> {
|
||||||
|
_$TraktUserSettingsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final TraktUserSettings _self;
|
||||||
|
final $Res Function(TraktUserSettings) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TraktUserSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? user = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
user: freezed == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TraktUser?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of TraktUserSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TraktUserCopyWith<$Res>? get user {
|
||||||
|
if (_self.user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $TraktUserCopyWith<$Res>(_self.user!, (value) {
|
||||||
|
return _then(_self.copyWith(user: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [TraktUserSettings].
|
||||||
|
extension TraktUserSettingsPatterns on TraktUserSettings {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _TraktUserSettings value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUserSettings() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _TraktUserSettings value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUserSettings():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _TraktUserSettings value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUserSettings() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( TraktUser? user)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUserSettings() when $default != null:
|
||||||
|
return $default(_that.user);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( TraktUser? user) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUserSettings():
|
||||||
|
return $default(_that.user);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( TraktUser? user)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TraktUserSettings() when $default != null:
|
||||||
|
return $default(_that.user);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _TraktUserSettings implements TraktUserSettings {
|
||||||
|
const _TraktUserSettings({this.user});
|
||||||
|
factory _TraktUserSettings.fromJson(Map<String, dynamic> json) => _$TraktUserSettingsFromJson(json);
|
||||||
|
|
||||||
|
@override final TraktUser? user;
|
||||||
|
|
||||||
|
/// Create a copy of TraktUserSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$TraktUserSettingsCopyWith<_TraktUserSettings> get copyWith => __$TraktUserSettingsCopyWithImpl<_TraktUserSettings>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$TraktUserSettingsToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TraktUserSettings&&(identical(other.user, user) || other.user == user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,user);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TraktUserSettings(user: $user)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$TraktUserSettingsCopyWith<$Res> implements $TraktUserSettingsCopyWith<$Res> {
|
||||||
|
factory _$TraktUserSettingsCopyWith(_TraktUserSettings value, $Res Function(_TraktUserSettings) _then) = __$TraktUserSettingsCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
TraktUser? user
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $TraktUserCopyWith<$Res>? get user;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$TraktUserSettingsCopyWithImpl<$Res>
|
||||||
|
implements _$TraktUserSettingsCopyWith<$Res> {
|
||||||
|
__$TraktUserSettingsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _TraktUserSettings _self;
|
||||||
|
final $Res Function(_TraktUserSettings) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TraktUserSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? user = freezed,}) {
|
||||||
|
return _then(_TraktUserSettings(
|
||||||
|
user: freezed == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TraktUser?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of TraktUserSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TraktUserCopyWith<$Res>? get user {
|
||||||
|
if (_self.user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $TraktUserCopyWith<$Res>(_self.user!, (value) {
|
||||||
|
return _then(_self.copyWith(user: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'trakt_user.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_TraktUser _$TraktUserFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TraktUser(username: json['username'] as String? ?? '');
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TraktUserToJson(_TraktUser instance) =>
|
||||||
|
<String, dynamic>{'username': instance.username};
|
||||||
|
|
||||||
|
_TraktUserSettings _$TraktUserSettingsFromJson(Map<String, dynamic> json) =>
|
||||||
|
_TraktUserSettings(
|
||||||
|
user: json['user'] == null
|
||||||
|
? null
|
||||||
|
: TraktUser.fromJson(json['user'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TraktUserSettingsToJson(_TraktUserSettings instance) =>
|
||||||
|
<String, dynamic>{'user': instance.user};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import '../contracts/error.dart';
|
||||||
|
|
||||||
|
class DomainError implements Exception {
|
||||||
|
final ErrorCode code;
|
||||||
|
final String message;
|
||||||
|
final bool retryable;
|
||||||
|
final Object? details;
|
||||||
|
|
||||||
|
DomainError(this.code, this.message, {this.retryable = false, this.details});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'DomainError(${code.value}): $message';
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import '../../contracts/trakt/trakt_response.dart';
|
||||||
|
import '../../contracts/trakt/trakt_sync_job.dart';
|
||||||
|
|
||||||
|
|
||||||
|
abstract class AuthedTraktGateway {
|
||||||
|
|
||||||
|
Future<bool> isConnected();
|
||||||
|
|
||||||
|
|
||||||
|
Future<TraktScrobbleOutcome> scrobble(
|
||||||
|
TraktSyncJobType type,
|
||||||
|
Map<String, dynamic> body,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Future<TraktScrobbleOutcome> syncHistory(
|
||||||
|
TraktSyncJobType type,
|
||||||
|
Map<String, dynamic> body,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fetchPlayback();
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fetchCalendarShows();
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fetchCalendarMovies();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import '../../contracts/danmaku.dart';
|
||||||
|
|
||||||
|
abstract class DanmakuGateway {
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<DanmakuMatch>> match(
|
||||||
|
String baseUrl,
|
||||||
|
String fileName,
|
||||||
|
int durationSec,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<DanmakuMatch>> searchEpisodes(
|
||||||
|
String baseUrl,
|
||||||
|
String anime,
|
||||||
|
int? episode,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<DanmakuComment>> fetchComments(
|
||||||
|
String baseUrl,
|
||||||
|
int episodeId, {
|
||||||
|
int chConvert = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<DanmakuBangumiEpisode>> getBangumiEpisodes(
|
||||||
|
String baseUrl,
|
||||||
|
int animeId,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import '../../contracts/danmaku.dart';
|
||||||
|
|
||||||
|
|
||||||
|
abstract class DanmakuSourcesStore {
|
||||||
|
|
||||||
|
Future<List<DanmakuSource>> list();
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> replaceAll(List<DanmakuSource> sources);
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import '../../contracts/cancellation.dart';
|
||||||
|
import '../../contracts/library.dart';
|
||||||
|
import '../../contracts/playback.dart';
|
||||||
|
import '../../contracts/server.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class AuthedRequestContext {
|
||||||
|
final String baseUrl;
|
||||||
|
final String token;
|
||||||
|
final String userId;
|
||||||
|
|
||||||
|
const AuthedRequestContext({
|
||||||
|
required this.baseUrl,
|
||||||
|
required this.token,
|
||||||
|
required this.userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EmbyAuthResult {
|
||||||
|
final String accessToken;
|
||||||
|
final String userId;
|
||||||
|
final String userName;
|
||||||
|
|
||||||
|
const EmbyAuthResult({
|
||||||
|
required this.accessToken,
|
||||||
|
required this.userId,
|
||||||
|
required this.userName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract class EmbyGateway {
|
||||||
|
Future<ProbePublicInfoRes> probePublicInfo(String baseUrl);
|
||||||
|
|
||||||
|
Future<EmbyAuthResult> authenticateByName({
|
||||||
|
required String baseUrl,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> changePassword({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required String currentPassword,
|
||||||
|
required String newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<EmbyRawLibraries> getUserViews(AuthedRequestContext ctx);
|
||||||
|
|
||||||
|
Future<LibraryLatestRes> getLatestItems(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String parentId, {
|
||||||
|
int? limit,
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<LibraryResumeRes> getResumeItems(
|
||||||
|
AuthedRequestContext ctx, {
|
||||||
|
int? limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> getLibraryItems({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
String? parentId,
|
||||||
|
String? includeItemTypes,
|
||||||
|
List<String>? genreIds,
|
||||||
|
String? searchTerm,
|
||||||
|
String? anyProviderIdEquals,
|
||||||
|
List<String>? personIds,
|
||||||
|
String? fields,
|
||||||
|
String? enableImageTypes,
|
||||||
|
bool? groupProgramsBySeries,
|
||||||
|
int? imageTypeLimit,
|
||||||
|
int? startIndex,
|
||||||
|
int? limit,
|
||||||
|
bool? recursive,
|
||||||
|
String? sortBy,
|
||||||
|
SortOrder? sortOrder,
|
||||||
|
String? filters,
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> getFavoriteItems({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required String includeItemTypes,
|
||||||
|
required String sortBy,
|
||||||
|
SortOrder? sortOrder,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<ItemCountsRes> getItemCounts(AuthedRequestContext ctx);
|
||||||
|
|
||||||
|
Future<EmbyRawItem> getItemDetail(AuthedRequestContext ctx, String itemId);
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> getSeriesNextUp(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String seriesId, {
|
||||||
|
int? limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawSeason>> getSeriesSeasons(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String seriesId, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> getSeriesEpisodes(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String seriesId,
|
||||||
|
String seasonId, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> getSimilarItems(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String itemId, {
|
||||||
|
int? limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> getAdditionalParts(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String itemId,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> getSpecialFeatures(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String itemId,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<bool> setFavoriteStatus({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required String itemId,
|
||||||
|
required bool isFavorite,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<bool> setPlayedStatus({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required String itemId,
|
||||||
|
required bool played,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<bool> hideFromResume({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required String itemId,
|
||||||
|
required bool hide,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> reportPlaybackStarted({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required PlaybackReportPayload payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> reportPlaybackProgress({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required PlaybackReportPayload payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> reportPlaybackStopped({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required PlaybackReportPayload payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchPlaybackInfo({
|
||||||
|
required AuthedRequestContext ctx,
|
||||||
|
required String itemId,
|
||||||
|
String? mediaSourceId,
|
||||||
|
int startTimeTicks = 0,
|
||||||
|
bool isPlayback = false,
|
||||||
|
required Map<String, dynamic> deviceProfile,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
abstract class ScriptWidgetRemoteGateway {
|
||||||
|
Future<String> fetchText(String url);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import '../../contracts/script_widget.dart';
|
||||||
|
|
||||||
|
abstract class ScriptWidgetRepository {
|
||||||
|
Future<List<InstalledScriptWidget>> listWidgets();
|
||||||
|
|
||||||
|
Future<InstalledScriptWidget?> findWidget(String widgetId);
|
||||||
|
|
||||||
|
Future<void> upsertWidget(InstalledScriptWidget widget);
|
||||||
|
|
||||||
|
Future<void> deleteWidget(String widgetId);
|
||||||
|
|
||||||
|
Future<List<ScriptWidgetSubscription>> listSubscriptions();
|
||||||
|
|
||||||
|
Future<void> upsertSubscription(ScriptWidgetSubscription subscription);
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> replaceAllWidgets(List<InstalledScriptWidget> widgets);
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> replaceAllSubscriptions(
|
||||||
|
List<ScriptWidgetSubscription> subscriptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import '../../contracts/script_widget.dart';
|
||||||
|
|
||||||
|
abstract class ScriptWidgetRuntime {
|
||||||
|
Future<ScriptWidgetManifest> inspectWidget(String scriptSource);
|
||||||
|
|
||||||
|
Future<ScriptWidgetManifest> loadWidget(String scriptSource);
|
||||||
|
|
||||||
|
bool isWidgetLoaded(String widgetId);
|
||||||
|
|
||||||
|
Future<Object?> invokeModuleFunction(
|
||||||
|
String widgetId,
|
||||||
|
String functionName,
|
||||||
|
Map<String, Object?> params,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> unloadWidget(String widgetId);
|
||||||
|
|
||||||
|
Future<void> dispose();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import '../../contracts/server.dart';
|
||||||
|
|
||||||
|
abstract class ServerRepository {
|
||||||
|
Future<List<EmbyServer>> list();
|
||||||
|
Future<EmbyServer?> findById(String id);
|
||||||
|
Future<EmbyServer?> findByBaseUrl(String baseUrl);
|
||||||
|
Future<EmbyServer> create(EmbyServer server);
|
||||||
|
Future<EmbyServer> update(EmbyServer server);
|
||||||
|
Future<void> deleteById(String id);
|
||||||
|
Future<void> touchConnectedAt(String id, String updatedAt);
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> replaceAll(List<EmbyServer> servers);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
|
||||||
|
library;
|
||||||
|
|
||||||
|
|
||||||
|
class ServerSyncSnapshot {
|
||||||
|
final String? blob;
|
||||||
|
|
||||||
|
const ServerSyncSnapshot({this.blob});
|
||||||
|
|
||||||
|
bool get isEmpty => blob == null || blob!.trim().isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ServerSyncGateway {
|
||||||
|
|
||||||
|
|
||||||
|
Future<ServerSyncSnapshot> pull({
|
||||||
|
required String baseUrl,
|
||||||
|
required String accessToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> push({
|
||||||
|
required String baseUrl,
|
||||||
|
required String accessToken,
|
||||||
|
required String blob,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import '../../contracts/auth.dart';
|
||||||
|
|
||||||
|
abstract class SessionRepository {
|
||||||
|
Future<SessionData> set(SessionData session);
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> setMany(List<SessionData> sessions);
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> replaceAll(List<SessionData> sessions, {String? activeServerId});
|
||||||
|
|
||||||
|
Future<SessionData?> getByServerId(String serverId);
|
||||||
|
Future<SessionData?> getActive();
|
||||||
|
Future<String?> getActiveServerId();
|
||||||
|
Future<List<SessionData>> listAll();
|
||||||
|
Future<void> setActive(String serverId);
|
||||||
|
Future<void> clear([String? serverId]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
|
||||||
|
|
||||||
|
library;
|
||||||
|
|
||||||
|
|
||||||
|
class SmLoginResult {
|
||||||
|
final bool ok;
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
|
||||||
|
final String accessToken;
|
||||||
|
|
||||||
|
|
||||||
|
final String refreshToken;
|
||||||
|
|
||||||
|
final int expiresIn;
|
||||||
|
|
||||||
|
|
||||||
|
final bool isVip;
|
||||||
|
|
||||||
|
|
||||||
|
final int? vipExpiresAt;
|
||||||
|
|
||||||
|
|
||||||
|
final bool vipPermanent;
|
||||||
|
|
||||||
|
|
||||||
|
final bool needsVerification;
|
||||||
|
|
||||||
|
final String? message;
|
||||||
|
|
||||||
|
const SmLoginResult({
|
||||||
|
required this.ok,
|
||||||
|
this.email = '',
|
||||||
|
this.accessToken = '',
|
||||||
|
this.refreshToken = '',
|
||||||
|
this.expiresIn = 0,
|
||||||
|
this.isVip = false,
|
||||||
|
this.vipExpiresAt,
|
||||||
|
this.vipPermanent = false,
|
||||||
|
this.needsVerification = false,
|
||||||
|
this.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SmLoginResult.failure(
|
||||||
|
String message, {
|
||||||
|
bool needsVerification = false,
|
||||||
|
}) => SmLoginResult(
|
||||||
|
ok: false,
|
||||||
|
needsVerification: needsVerification,
|
||||||
|
message: message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SmTokenRefreshResult {
|
||||||
|
final bool ok;
|
||||||
|
final String accessToken;
|
||||||
|
final String refreshToken;
|
||||||
|
final int expiresIn;
|
||||||
|
|
||||||
|
|
||||||
|
final bool isVip;
|
||||||
|
final int? vipExpiresAt;
|
||||||
|
final bool vipPermanent;
|
||||||
|
|
||||||
|
|
||||||
|
final bool hasVipInfo;
|
||||||
|
|
||||||
|
|
||||||
|
final bool invalidRefreshToken;
|
||||||
|
final String? message;
|
||||||
|
|
||||||
|
const SmTokenRefreshResult({
|
||||||
|
required this.ok,
|
||||||
|
this.accessToken = '',
|
||||||
|
this.refreshToken = '',
|
||||||
|
this.expiresIn = 0,
|
||||||
|
this.isVip = false,
|
||||||
|
this.vipExpiresAt,
|
||||||
|
this.vipPermanent = false,
|
||||||
|
this.hasVipInfo = false,
|
||||||
|
this.invalidRefreshToken = false,
|
||||||
|
this.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SmTokenRefreshResult.failure(
|
||||||
|
String message, {
|
||||||
|
bool invalidRefreshToken = false,
|
||||||
|
}) => SmTokenRefreshResult(
|
||||||
|
ok: false,
|
||||||
|
invalidRefreshToken: invalidRefreshToken,
|
||||||
|
message: message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SmActionResult {
|
||||||
|
final bool ok;
|
||||||
|
final String? message;
|
||||||
|
|
||||||
|
const SmActionResult({required this.ok, this.message});
|
||||||
|
|
||||||
|
factory SmActionResult.success() => const SmActionResult(ok: true);
|
||||||
|
|
||||||
|
factory SmActionResult.failure(String message) =>
|
||||||
|
SmActionResult(ok: false, message: message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract class SmAccountGateway {
|
||||||
|
|
||||||
|
|
||||||
|
Future<SmLoginResult> login({
|
||||||
|
required String baseUrl,
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<SmActionResult> registerStart({
|
||||||
|
required String baseUrl,
|
||||||
|
required String email,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<SmLoginResult> registerVerify({
|
||||||
|
required String baseUrl,
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
required String code,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<SmActionResult> resendCode({
|
||||||
|
required String baseUrl,
|
||||||
|
required String email,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> logout({required String baseUrl, required String refreshToken});
|
||||||
|
|
||||||
|
|
||||||
|
Future<SmTokenRefreshResult> refreshAccessToken({
|
||||||
|
required String baseUrl,
|
||||||
|
required String refreshToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import '../../contracts/tmdb.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class TmdbRequestConfig {
|
||||||
|
final String apiBaseUrl;
|
||||||
|
final String imageBaseUrl;
|
||||||
|
final String accessToken;
|
||||||
|
final String apiKey;
|
||||||
|
|
||||||
|
|
||||||
|
final Future<String?> Function()? onUnauthorized;
|
||||||
|
|
||||||
|
const TmdbRequestConfig({
|
||||||
|
required this.apiBaseUrl,
|
||||||
|
required this.imageBaseUrl,
|
||||||
|
this.accessToken = '',
|
||||||
|
this.apiKey = '',
|
||||||
|
this.onUnauthorized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TmdbGateway {
|
||||||
|
Future<TmdbCreditsRes?> getCredits(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String tmdbId,
|
||||||
|
String mediaType, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbRecommendationsRes?> getRecommendations(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String tmdbId,
|
||||||
|
String mediaType, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbSeasonDetail?> getSeasonDetail(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String tmdbId,
|
||||||
|
int seasonNumber, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbImageSet?> getImages(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String tmdbId,
|
||||||
|
String mediaType, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbEnrichedDetail?> getEnrichedDetail(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String tmdbId,
|
||||||
|
String mediaType, {
|
||||||
|
required String language,
|
||||||
|
List<String> appendToResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbVideosRes?> getVideos(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String tmdbId,
|
||||||
|
String mediaType, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbPersonDetail?> getPersonDetail(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
int personId, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbPersonCreditsRes?> getPersonCredits(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
int personId, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbMediaDetail?> getMediaDetail(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String tmdbId,
|
||||||
|
String mediaType, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbRecommendationsRes?> getTrending(
|
||||||
|
TmdbRequestConfig config, {
|
||||||
|
required String language,
|
||||||
|
String mediaType,
|
||||||
|
String timeWindow,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbRecommendationsRes?> getPopularMovies(
|
||||||
|
TmdbRequestConfig config, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbRecommendationsRes?> getPopularTv(
|
||||||
|
TmdbRequestConfig config, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbRecommendationsRes?> getAiringTodayTv(
|
||||||
|
TmdbRequestConfig config, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<TmdbRecommendationsRes?> getTopRatedMovies(
|
||||||
|
TmdbRequestConfig config, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Future<TmdbRecommendationsRes?> getDiscover(
|
||||||
|
TmdbRequestConfig config,
|
||||||
|
String mediaType, {
|
||||||
|
required String language,
|
||||||
|
String sortBy = 'popularity.desc',
|
||||||
|
String? withGenres,
|
||||||
|
String? voteCountGte,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<bool> testConnection(
|
||||||
|
TmdbRequestConfig config, {
|
||||||
|
required String language,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import '../../contracts/trakt/trakt_token.dart';
|
||||||
|
import '../../contracts/trakt/trakt_user.dart';
|
||||||
|
|
||||||
|
|
||||||
|
abstract class TraktGateway {
|
||||||
|
|
||||||
|
Future<TraktToken> exchangeCode(String code);
|
||||||
|
|
||||||
|
|
||||||
|
Future<TraktToken> refreshToken(String refreshToken);
|
||||||
|
|
||||||
|
|
||||||
|
Future<TraktUser> getMe(String accessToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import '../../contracts/contracts.dart';
|
||||||
|
import '../errors.dart';
|
||||||
|
import '../ports/emby_gateway.dart';
|
||||||
|
import '../ports/server_repository.dart';
|
||||||
|
import '../ports/session_repository.dart';
|
||||||
|
|
||||||
|
class AuthByNameUseCase {
|
||||||
|
final ServerRepository serverRepository;
|
||||||
|
final SessionRepository sessionRepository;
|
||||||
|
final EmbyGateway embyGateway;
|
||||||
|
final DateTime Function() now;
|
||||||
|
|
||||||
|
AuthByNameUseCase({
|
||||||
|
required this.serverRepository,
|
||||||
|
required this.sessionRepository,
|
||||||
|
required this.embyGateway,
|
||||||
|
DateTime Function()? now,
|
||||||
|
}) : now = now ?? DateTime.now;
|
||||||
|
|
||||||
|
Future<void> execute(AuthByNameReq input) async {
|
||||||
|
final username = input.username.trim();
|
||||||
|
final password = input.password;
|
||||||
|
if (username.isEmpty || password.isEmpty) {
|
||||||
|
throw DomainError(ErrorCode.invalidParams, '用户名或密码不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
final server = await serverRepository.findById(input.serverId);
|
||||||
|
if (server == null) {
|
||||||
|
throw DomainError(
|
||||||
|
ErrorCode.storeNotFound,
|
||||||
|
'服务器不存在',
|
||||||
|
details: {'serverId': input.serverId},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final auth = await embyGateway.authenticateByName(
|
||||||
|
baseUrl: server.baseUrl,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
|
||||||
|
final nowIso = now().toUtc().toIso8601String();
|
||||||
|
await sessionRepository.set(
|
||||||
|
SessionData(
|
||||||
|
serverId: server.id,
|
||||||
|
token: auth.accessToken,
|
||||||
|
userId: auth.userId,
|
||||||
|
userName: auth.userName,
|
||||||
|
password: password,
|
||||||
|
createdAt: nowIso,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await serverRepository.touchConnectedAt(server.id, nowIso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogoutUseCase {
|
||||||
|
final SessionRepository sessionRepository;
|
||||||
|
LogoutUseCase({required this.sessionRepository});
|
||||||
|
|
||||||
|
Future<void> execute([String? serverId]) => sessionRepository.clear(serverId);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListSessionsUseCase {
|
||||||
|
final SessionRepository sessionRepository;
|
||||||
|
final ServerRepository serverRepository;
|
||||||
|
|
||||||
|
ListSessionsUseCase({
|
||||||
|
required this.sessionRepository,
|
||||||
|
required this.serverRepository,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<SessionListRes> execute() async {
|
||||||
|
final sessions = await sessionRepository.listAll();
|
||||||
|
final activeServerId = await sessionRepository.getActiveServerId();
|
||||||
|
final result = <AuthedSession>[];
|
||||||
|
|
||||||
|
for (final session in sessions) {
|
||||||
|
final server = await serverRepository.findById(session.serverId);
|
||||||
|
if (server != null) {
|
||||||
|
result.add(
|
||||||
|
AuthedSession(
|
||||||
|
serverId: session.serverId,
|
||||||
|
serverName: server.name,
|
||||||
|
serverUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
userName: session.userName,
|
||||||
|
password: session.password,
|
||||||
|
isActive: session.serverId == activeServerId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SessionListRes(sessions: result, activeServerId: activeServerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwitchSessionUseCase {
|
||||||
|
final SessionRepository sessionRepository;
|
||||||
|
|
||||||
|
SwitchSessionUseCase({required this.sessionRepository});
|
||||||
|
|
||||||
|
Future<void> execute(String serverId) async {
|
||||||
|
final session = await sessionRepository.getByServerId(serverId);
|
||||||
|
if (session == null) {
|
||||||
|
throw DomainError(
|
||||||
|
ErrorCode.storeNotFound,
|
||||||
|
'该服务器没有可用会话',
|
||||||
|
details: {'serverId': serverId},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await sessionRepository.setActive(serverId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangePasswordUseCase {
|
||||||
|
final SessionRepository sessionRepository;
|
||||||
|
final ServerRepository serverRepository;
|
||||||
|
final EmbyGateway embyGateway;
|
||||||
|
|
||||||
|
ChangePasswordUseCase({
|
||||||
|
required this.sessionRepository,
|
||||||
|
required this.serverRepository,
|
||||||
|
required this.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> execute({
|
||||||
|
required String serverId,
|
||||||
|
required String currentPassword,
|
||||||
|
required String newPassword,
|
||||||
|
}) async {
|
||||||
|
if (currentPassword.isEmpty || newPassword.isEmpty) {
|
||||||
|
throw DomainError(ErrorCode.invalidParams, '密码不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
final session = await sessionRepository.getByServerId(serverId);
|
||||||
|
if (session == null) {
|
||||||
|
throw DomainError(ErrorCode.storeNotFound, '该服务器没有可用会话');
|
||||||
|
}
|
||||||
|
final server = await serverRepository.findById(serverId);
|
||||||
|
if (server == null) {
|
||||||
|
throw DomainError(ErrorCode.storeNotFound, '服务器不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
final ctx = AuthedRequestContext(
|
||||||
|
baseUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await embyGateway.changePassword(
|
||||||
|
ctx: ctx,
|
||||||
|
currentPassword: currentPassword,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
|
||||||
|
await sessionRepository.setMany([session.copyWith(password: newPassword)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import '../../contracts/contracts.dart';
|
||||||
|
import '../errors.dart';
|
||||||
|
import '../ports/danmaku_sources_store.dart';
|
||||||
|
import '../ports/script_widget_repository.dart';
|
||||||
|
import '../ports/server_repository.dart';
|
||||||
|
import '../ports/session_repository.dart';
|
||||||
|
|
||||||
|
const int kBackupSchemaVersion = 1;
|
||||||
|
|
||||||
|
class BackupExportResult {
|
||||||
|
final String json;
|
||||||
|
final int serverCount;
|
||||||
|
final int sessionCount;
|
||||||
|
final int danmakuSourceCount;
|
||||||
|
final int scriptWidgetCount;
|
||||||
|
final int scriptWidgetSubscriptionCount;
|
||||||
|
|
||||||
|
const BackupExportResult({
|
||||||
|
required this.json,
|
||||||
|
required this.serverCount,
|
||||||
|
required this.sessionCount,
|
||||||
|
required this.danmakuSourceCount,
|
||||||
|
required this.scriptWidgetCount,
|
||||||
|
required this.scriptWidgetSubscriptionCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupImportResult {
|
||||||
|
final int serverCount;
|
||||||
|
final int sessionCount;
|
||||||
|
final int danmakuSourceCount;
|
||||||
|
final int scriptWidgetCount;
|
||||||
|
final int scriptWidgetSubscriptionCount;
|
||||||
|
|
||||||
|
const BackupImportResult({
|
||||||
|
required this.serverCount,
|
||||||
|
required this.sessionCount,
|
||||||
|
required this.danmakuSourceCount,
|
||||||
|
required this.scriptWidgetCount,
|
||||||
|
required this.scriptWidgetSubscriptionCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExportBackupUseCase {
|
||||||
|
final ServerRepository serverRepository;
|
||||||
|
final SessionRepository sessionRepository;
|
||||||
|
final DanmakuSourcesStore danmakuSourcesStore;
|
||||||
|
final ScriptWidgetRepository scriptWidgetRepository;
|
||||||
|
final DateTime Function() now;
|
||||||
|
|
||||||
|
ExportBackupUseCase({
|
||||||
|
required this.serverRepository,
|
||||||
|
required this.sessionRepository,
|
||||||
|
required this.danmakuSourcesStore,
|
||||||
|
required this.scriptWidgetRepository,
|
||||||
|
DateTime Function()? now,
|
||||||
|
}) : now = (now ?? DateTime.now);
|
||||||
|
|
||||||
|
Future<BackupExportResult> execute() async {
|
||||||
|
final servers = await serverRepository.list();
|
||||||
|
final sessions = await sessionRepository.listAll();
|
||||||
|
final danmakuSources = await danmakuSourcesStore.list();
|
||||||
|
final scriptWidgets = await scriptWidgetRepository.listWidgets();
|
||||||
|
final scriptWidgetSubscriptions =
|
||||||
|
await scriptWidgetRepository.listSubscriptions();
|
||||||
|
final bundle = BackupBundle(
|
||||||
|
version: kBackupSchemaVersion,
|
||||||
|
exportedAt: now().toUtc().toIso8601String(),
|
||||||
|
servers: servers,
|
||||||
|
sessions: sessions,
|
||||||
|
danmakuSources: danmakuSources,
|
||||||
|
scriptWidgets: scriptWidgets,
|
||||||
|
scriptWidgetSubscriptions: scriptWidgetSubscriptions,
|
||||||
|
);
|
||||||
|
final json = const JsonEncoder.withIndent(' ').convert(bundle.toJson());
|
||||||
|
return BackupExportResult(
|
||||||
|
json: json,
|
||||||
|
serverCount: servers.length,
|
||||||
|
sessionCount: sessions.length,
|
||||||
|
danmakuSourceCount: danmakuSources.length,
|
||||||
|
scriptWidgetCount: scriptWidgets.length,
|
||||||
|
scriptWidgetSubscriptionCount: scriptWidgetSubscriptions.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImportBackupUseCase {
|
||||||
|
final ServerRepository serverRepository;
|
||||||
|
final SessionRepository sessionRepository;
|
||||||
|
final DanmakuSourcesStore danmakuSourcesStore;
|
||||||
|
final ScriptWidgetRepository scriptWidgetRepository;
|
||||||
|
|
||||||
|
ImportBackupUseCase({
|
||||||
|
required this.serverRepository,
|
||||||
|
required this.sessionRepository,
|
||||||
|
required this.danmakuSourcesStore,
|
||||||
|
required this.scriptWidgetRepository,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<BackupImportResult> execute(String rawJson) async {
|
||||||
|
final BackupBundle bundle;
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(rawJson);
|
||||||
|
if (decoded is! Map<String, dynamic>) {
|
||||||
|
throw DomainError(ErrorCode.invalidParams, '备份文件格式不正确');
|
||||||
|
}
|
||||||
|
bundle = BackupBundle.fromJson(decoded);
|
||||||
|
} on DomainError {
|
||||||
|
rethrow;
|
||||||
|
} catch (e) {
|
||||||
|
throw DomainError(
|
||||||
|
ErrorCode.invalidParams,
|
||||||
|
'备份文件解析失败',
|
||||||
|
details: {'reason': e.toString()},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bundle.version != kBackupSchemaVersion) {
|
||||||
|
throw DomainError(
|
||||||
|
ErrorCode.invalidParams,
|
||||||
|
'不支持的备份文件版本:${bundle.version}',
|
||||||
|
details: {'expected': kBackupSchemaVersion, 'got': bundle.version},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final server in bundle.servers) {
|
||||||
|
await serverRepository.update(server);
|
||||||
|
}
|
||||||
|
await sessionRepository.setMany(bundle.sessions);
|
||||||
|
|
||||||
|
final mergedSources = mergeDanmakuSourcesByUrl(
|
||||||
|
local: await danmakuSourcesStore.list(),
|
||||||
|
remote: bundle.danmakuSources,
|
||||||
|
);
|
||||||
|
await danmakuSourcesStore.replaceAll(mergedSources);
|
||||||
|
|
||||||
|
for (final scriptWidget in bundle.scriptWidgets) {
|
||||||
|
await scriptWidgetRepository.upsertWidget(scriptWidget);
|
||||||
|
}
|
||||||
|
for (final subscription in bundle.scriptWidgetSubscriptions) {
|
||||||
|
await scriptWidgetRepository.upsertSubscription(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BackupImportResult(
|
||||||
|
serverCount: bundle.servers.length,
|
||||||
|
sessionCount: bundle.sessions.length,
|
||||||
|
danmakuSourceCount: mergedSources.length,
|
||||||
|
scriptWidgetCount: bundle.scriptWidgets.length,
|
||||||
|
scriptWidgetSubscriptionCount: bundle.scriptWidgetSubscriptions.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<DanmakuSource> mergeDanmakuSourcesByUrl({
|
||||||
|
required List<DanmakuSource> local,
|
||||||
|
required List<DanmakuSource> remote,
|
||||||
|
}) {
|
||||||
|
final localByUrl = <String, DanmakuSource>{
|
||||||
|
for (final s in local) s.url.trim(): s,
|
||||||
|
};
|
||||||
|
final out = <DanmakuSource>[];
|
||||||
|
final seen = <String>{};
|
||||||
|
for (final r in remote) {
|
||||||
|
final url = r.url.trim();
|
||||||
|
if (url.isEmpty || seen.contains(url)) continue;
|
||||||
|
seen.add(url);
|
||||||
|
final hit = localByUrl[url];
|
||||||
|
if (hit != null) {
|
||||||
|
out.add(hit.copyWith(name: hit.name.trim().isEmpty ? r.name : hit.name));
|
||||||
|
} else {
|
||||||
|
out.add(
|
||||||
|
DanmakuSource(
|
||||||
|
id: r.id.trim().isEmpty
|
||||||
|
? 'src_${url.hashCode.toUnsigned(32).toRadixString(16)}'
|
||||||
|
: r.id,
|
||||||
|
name: r.name,
|
||||||
|
url: url,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import '../../../shared/utils/app_logger.dart';
|
||||||
|
import '../../contracts/contracts.dart';
|
||||||
|
import '../ports/emby_gateway.dart';
|
||||||
|
import '../ports/server_repository.dart';
|
||||||
|
import '../ports/session_repository.dart';
|
||||||
|
import 'usecase_helpers.dart';
|
||||||
|
|
||||||
|
const _ucTag = 'UseCase';
|
||||||
|
|
||||||
|
|
||||||
|
Stream<GlobalSearchServerResult> _fanOutPerServer({
|
||||||
|
required SessionRepository sessionRepository,
|
||||||
|
required ServerRepository serverRepository,
|
||||||
|
required Future<List<EmbyRawItem>?> Function(AuthedRequestContext context)
|
||||||
|
fetchItems,
|
||||||
|
required String logTag,
|
||||||
|
}) {
|
||||||
|
final controller = StreamController<GlobalSearchServerResult>();
|
||||||
|
() async {
|
||||||
|
try {
|
||||||
|
final allSessions = await sessionRepository.listAll();
|
||||||
|
final allServers = await serverRepository.list();
|
||||||
|
final serverMap = {for (final s in allServers) s.id: s};
|
||||||
|
|
||||||
|
final futures = allSessions.map((session) async {
|
||||||
|
final server = serverMap[session.serverId];
|
||||||
|
if (server == null) return;
|
||||||
|
final context = AuthedRequestContext(
|
||||||
|
baseUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final items = await fetchItems(context);
|
||||||
|
if (items == null || items.isEmpty) return;
|
||||||
|
controller.add(
|
||||||
|
GlobalSearchServerResult(
|
||||||
|
serverId: server.id,
|
||||||
|
serverName: server.name,
|
||||||
|
serverUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
items: items,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.warn(_ucTag, '$logTag on ${server.name} failed', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.wait(futures);
|
||||||
|
} catch (e) {
|
||||||
|
controller.addError(e);
|
||||||
|
} finally {
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetLibraryViewsUseCase extends AuthedUseCase {
|
||||||
|
GetLibraryViewsUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<LibraryViewsRes> execute() async {
|
||||||
|
return embyGateway.getUserViews(await ctx());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetLibraryResumeUseCase extends AuthedUseCase {
|
||||||
|
GetLibraryResumeUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<LibraryResumeRes> execute() async {
|
||||||
|
return embyGateway.getResumeItems(await ctx(), limit: 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetLibraryLatestUseCase extends AuthedUseCase {
|
||||||
|
GetLibraryLatestUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<LibraryLatestRes> execute(
|
||||||
|
LibraryLatestReq input, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
return embyGateway.getLatestItems(
|
||||||
|
await ctx(),
|
||||||
|
input.parentId,
|
||||||
|
limit: input.limit ?? 16,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetLibraryItemsUseCase extends AuthedUseCase {
|
||||||
|
GetLibraryItemsUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<LibraryItemsRes> execute(LibraryItemsReq input) async {
|
||||||
|
final items = await embyGateway.getLibraryItems(
|
||||||
|
ctx: await ctx(),
|
||||||
|
parentId: input.parentId,
|
||||||
|
includeItemTypes: input.includeItemTypes,
|
||||||
|
genreIds: input.genreIds,
|
||||||
|
searchTerm: input.searchTerm,
|
||||||
|
anyProviderIdEquals: input.anyProviderIdEquals,
|
||||||
|
fields: input.fields,
|
||||||
|
enableImageTypes: input.enableImageTypes,
|
||||||
|
groupProgramsBySeries: input.groupProgramsBySeries,
|
||||||
|
imageTypeLimit: input.imageTypeLimit,
|
||||||
|
startIndex: input.startIndex,
|
||||||
|
limit: input.limit,
|
||||||
|
recursive: input.recursive,
|
||||||
|
sortBy: input.sortBy,
|
||||||
|
sortOrder: input.sortOrder,
|
||||||
|
);
|
||||||
|
return LibraryItemsRes(
|
||||||
|
parentId: input.parentId ?? '__global__',
|
||||||
|
items: items,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GetHomeBannerUseCase extends AuthedUseCase {
|
||||||
|
GetHomeBannerUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> execute({int fetchLimit = 24, int take = 8}) async {
|
||||||
|
final items = await embyGateway.getLibraryItems(
|
||||||
|
ctx: await ctx(),
|
||||||
|
includeItemTypes: 'Movie,Series',
|
||||||
|
recursive: true,
|
||||||
|
sortBy: 'DateCreated',
|
||||||
|
sortOrder: SortOrder.descending,
|
||||||
|
fields:
|
||||||
|
'BasicSyncInfo,CommunityRating,ProductionYear,Overview,Genres,ProviderIds,UserData',
|
||||||
|
enableImageTypes: 'Backdrop,Logo,Primary,Thumb',
|
||||||
|
imageTypeLimit: 2,
|
||||||
|
limit: fetchLimit,
|
||||||
|
);
|
||||||
|
return items
|
||||||
|
.where((it) => (it.BackdropImageTags ?? const []).isNotEmpty)
|
||||||
|
.take(take)
|
||||||
|
.toList(growable: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GlobalKeywordSearchUseCase extends AuthedUseCase {
|
||||||
|
GlobalKeywordSearchUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const _defaultTypes = 'Movie,Series';
|
||||||
|
|
||||||
|
Stream<GlobalSearchServerResult> executeStream(GlobalSearchReq input) {
|
||||||
|
final controller = StreamController<GlobalSearchServerResult>();
|
||||||
|
() async {
|
||||||
|
final keyword = input.keyword.trim();
|
||||||
|
if (keyword.isEmpty) {
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var sessions = await sessionRepository.listAll();
|
||||||
|
if (input.serverId != null) {
|
||||||
|
sessions = sessions
|
||||||
|
.where((s) => s.serverId == input.serverId)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
final allServers = await serverRepository.list();
|
||||||
|
final serverMap = {for (final s in allServers) s.id: s};
|
||||||
|
final types = input.includeItemTypes ?? _defaultTypes;
|
||||||
|
|
||||||
|
final futures = sessions.map((session) async {
|
||||||
|
final result = await _searchOne(
|
||||||
|
session: session,
|
||||||
|
server: serverMap[session.serverId],
|
||||||
|
keyword: keyword,
|
||||||
|
types: types,
|
||||||
|
limit: input.limitPerServer ?? 30,
|
||||||
|
);
|
||||||
|
if (result != null) controller.add(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.wait(futures);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
controller.addError(error, stackTrace);
|
||||||
|
} finally {
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GlobalSearchServerResult?> _searchOne({
|
||||||
|
required SessionData session,
|
||||||
|
required EmbyServer? server,
|
||||||
|
required String keyword,
|
||||||
|
required String types,
|
||||||
|
required int limit,
|
||||||
|
}) async {
|
||||||
|
if (server == null) return null;
|
||||||
|
final c = AuthedRequestContext(
|
||||||
|
baseUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final items = await embyGateway.getLibraryItems(
|
||||||
|
ctx: c,
|
||||||
|
searchTerm: keyword,
|
||||||
|
recursive: true,
|
||||||
|
includeItemTypes: types,
|
||||||
|
limit: limit,
|
||||||
|
sortBy: 'SortName',
|
||||||
|
sortOrder: SortOrder.ascending,
|
||||||
|
);
|
||||||
|
if (items.isEmpty) return null;
|
||||||
|
return GlobalSearchServerResult(
|
||||||
|
serverId: server.id,
|
||||||
|
serverName: server.name,
|
||||||
|
serverUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
items: items,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.warn(_ucTag, 'GlobalKeywordSearch on ${server.name} failed', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetAggregatedResumeUseCase extends AuthedUseCase {
|
||||||
|
GetAggregatedResumeUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Stream<GlobalSearchServerResult> executeStream({int? limitPerServer}) {
|
||||||
|
return _fanOutPerServer(
|
||||||
|
sessionRepository: sessionRepository,
|
||||||
|
serverRepository: serverRepository,
|
||||||
|
logTag: 'AggregatedResume',
|
||||||
|
fetchItems: (context) async {
|
||||||
|
final res = await embyGateway.getResumeItems(
|
||||||
|
context,
|
||||||
|
limit: limitPerServer ?? 20,
|
||||||
|
);
|
||||||
|
return res.Items;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetAggregatedFavoritesUseCase extends AuthedUseCase {
|
||||||
|
static const _defaultTypes = 'Movie,Series,Episode';
|
||||||
|
|
||||||
|
GetAggregatedFavoritesUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Stream<GlobalSearchServerResult> executeStream({
|
||||||
|
String includeItemTypes = _defaultTypes,
|
||||||
|
int? limitPerServer,
|
||||||
|
}) {
|
||||||
|
final types = includeItemTypes.trim().isEmpty
|
||||||
|
? _defaultTypes
|
||||||
|
: includeItemTypes;
|
||||||
|
return _fanOutPerServer(
|
||||||
|
sessionRepository: sessionRepository,
|
||||||
|
serverRepository: serverRepository,
|
||||||
|
logTag: 'AggregatedFavorites',
|
||||||
|
fetchItems: (context) async {
|
||||||
|
final items = await embyGateway.getFavoriteItems(
|
||||||
|
ctx: context,
|
||||||
|
includeItemTypes: types,
|
||||||
|
sortBy: 'SortName',
|
||||||
|
sortOrder: SortOrder.ascending,
|
||||||
|
);
|
||||||
|
return limitPerServer == null
|
||||||
|
? items
|
||||||
|
: items.take(limitPerServer).toList(growable: false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetAggregatedRecentUseCase extends AuthedUseCase {
|
||||||
|
static const _defaultTypes = 'Movie,Series';
|
||||||
|
|
||||||
|
GetAggregatedRecentUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Stream<GlobalSearchServerResult> executeStream({
|
||||||
|
String includeItemTypes = _defaultTypes,
|
||||||
|
int? limitPerServer,
|
||||||
|
}) {
|
||||||
|
return _fanOutPerServer(
|
||||||
|
sessionRepository: sessionRepository,
|
||||||
|
serverRepository: serverRepository,
|
||||||
|
logTag: 'AggregatedRecent',
|
||||||
|
fetchItems: (context) async {
|
||||||
|
return embyGateway.getLibraryItems(
|
||||||
|
ctx: context,
|
||||||
|
includeItemTypes: includeItemTypes,
|
||||||
|
fields:
|
||||||
|
'BasicSyncInfo,CollectionType,PrimaryImageAspectRatio,UserData,'
|
||||||
|
'CommunityRating,ProviderIds,ProductionYear,ChildCount,Container,'
|
||||||
|
'CanDelete,DateCreated',
|
||||||
|
sortBy: 'DateCreated',
|
||||||
|
sortOrder: SortOrder.descending,
|
||||||
|
recursive: true,
|
||||||
|
limit: limitPerServer ?? 20,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,913 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import '../../../shared/utils/app_logger.dart';
|
||||||
|
import '../../contracts/contracts.dart';
|
||||||
|
import '../ports/emby_gateway.dart';
|
||||||
|
import 'usecase_helpers.dart';
|
||||||
|
|
||||||
|
const _ucTag = 'UseCase';
|
||||||
|
|
||||||
|
class GetMediaDetailUseCase extends AuthedUseCase {
|
||||||
|
GetMediaDetailUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<MediaDetailRes> execute(MediaDetailReq input) async {
|
||||||
|
final base = await embyGateway.getItemDetail(await ctx(), input.itemId);
|
||||||
|
return MediaDetailRes(base: base);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<MediaDetailRes> executeForServer({
|
||||||
|
required String serverId,
|
||||||
|
required String itemId,
|
||||||
|
}) async {
|
||||||
|
final ctx = await requireCtxForServer(serverId);
|
||||||
|
final base = await embyGateway.getItemDetail(ctx, itemId);
|
||||||
|
return MediaDetailRes(base: base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetFavoriteUseCase extends AuthedUseCase {
|
||||||
|
SetFavoriteUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<FavoriteSetRes> execute(FavoriteSetReq input) async {
|
||||||
|
final result = await embyGateway.setFavoriteStatus(
|
||||||
|
ctx: await ctx(),
|
||||||
|
itemId: input.itemId,
|
||||||
|
isFavorite: input.isFavorite,
|
||||||
|
);
|
||||||
|
return FavoriteSetRes(itemId: input.itemId, isFavorite: result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<FavoriteSetRes> executeForServer({
|
||||||
|
required String serverId,
|
||||||
|
required FavoriteSetReq input,
|
||||||
|
}) async {
|
||||||
|
final ctx = await requireCtxForServer(serverId);
|
||||||
|
final result = await embyGateway.setFavoriteStatus(
|
||||||
|
ctx: ctx,
|
||||||
|
itemId: input.itemId,
|
||||||
|
isFavorite: input.isFavorite,
|
||||||
|
);
|
||||||
|
return FavoriteSetRes(itemId: input.itemId, isFavorite: result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetPlayedUseCase extends AuthedUseCase {
|
||||||
|
SetPlayedUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<PlayedSetRes> execute(PlayedSetReq input) async {
|
||||||
|
final result = await embyGateway.setPlayedStatus(
|
||||||
|
ctx: await ctx(),
|
||||||
|
itemId: input.itemId,
|
||||||
|
played: input.played,
|
||||||
|
);
|
||||||
|
return PlayedSetRes(itemId: input.itemId, played: result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<PlayedSetRes> executeForServer({
|
||||||
|
required String serverId,
|
||||||
|
required PlayedSetReq input,
|
||||||
|
}) async {
|
||||||
|
final ctx = await requireCtxForServer(serverId);
|
||||||
|
final result = await embyGateway.setPlayedStatus(
|
||||||
|
ctx: ctx,
|
||||||
|
itemId: input.itemId,
|
||||||
|
played: input.played,
|
||||||
|
);
|
||||||
|
return PlayedSetRes(itemId: input.itemId, played: result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HideFromResumeUseCase extends AuthedUseCase {
|
||||||
|
HideFromResumeUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<HideFromResumeRes> execute(HideFromResumeReq input) async {
|
||||||
|
final result = await embyGateway.hideFromResume(
|
||||||
|
ctx: await ctx(),
|
||||||
|
itemId: input.itemId,
|
||||||
|
hide: input.hide,
|
||||||
|
);
|
||||||
|
return HideFromResumeRes(itemId: input.itemId, hide: result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetSeriesEpisodesUseCase extends AuthedUseCase {
|
||||||
|
GetSeriesEpisodesUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<MediaEpisodesRes> execute(MediaEpisodesReq input) async {
|
||||||
|
final items = await embyGateway.getSeriesEpisodes(
|
||||||
|
await ctx(),
|
||||||
|
input.seriesId,
|
||||||
|
input.seasonId,
|
||||||
|
);
|
||||||
|
return MediaEpisodesRes(
|
||||||
|
seriesId: input.seriesId,
|
||||||
|
seasonId: input.seasonId,
|
||||||
|
items: items,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<MediaEpisodesRes> executeForServer({
|
||||||
|
required String serverId,
|
||||||
|
required MediaEpisodesReq input,
|
||||||
|
}) async {
|
||||||
|
final ctx = await requireCtxForServer(serverId);
|
||||||
|
final items = await embyGateway.getSeriesEpisodes(
|
||||||
|
ctx,
|
||||||
|
input.seriesId,
|
||||||
|
input.seasonId,
|
||||||
|
);
|
||||||
|
return MediaEpisodesRes(
|
||||||
|
seriesId: input.seriesId,
|
||||||
|
seasonId: input.seasonId,
|
||||||
|
items: items,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetSeriesSeasonsUseCase extends AuthedUseCase {
|
||||||
|
GetSeriesSeasonsUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<SeriesSeasonsRes> execute(SeriesSeasonsReq input) async {
|
||||||
|
final seasons = await embyGateway.getSeriesSeasons(
|
||||||
|
await ctx(),
|
||||||
|
input.seriesId,
|
||||||
|
);
|
||||||
|
return SeriesSeasonsRes(seriesId: input.seriesId, items: seasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<SeriesSeasonsRes> executeForServer({
|
||||||
|
required String serverId,
|
||||||
|
required SeriesSeasonsReq input,
|
||||||
|
}) async {
|
||||||
|
final ctx = await requireCtxForServer(serverId);
|
||||||
|
final seasons = await embyGateway.getSeriesSeasons(ctx, input.seriesId);
|
||||||
|
return SeriesSeasonsRes(seriesId: input.seriesId, items: seasons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetSimilarItemsUseCase extends AuthedUseCase {
|
||||||
|
GetSimilarItemsUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<SimilarItemsRes> execute(SimilarItemsReq input) async {
|
||||||
|
final items = await embyGateway.getSimilarItems(
|
||||||
|
await ctx(),
|
||||||
|
input.itemId,
|
||||||
|
limit: input.limit,
|
||||||
|
);
|
||||||
|
return SimilarItemsRes(itemId: input.itemId, items: items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetAdditionalPartsUseCase extends AuthedUseCase {
|
||||||
|
GetAdditionalPartsUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<AdditionalPartsRes> execute(AdditionalPartsReq input) async {
|
||||||
|
final items = await embyGateway.getAdditionalParts(
|
||||||
|
await ctx(),
|
||||||
|
input.itemId,
|
||||||
|
);
|
||||||
|
return AdditionalPartsRes(itemId: input.itemId, items: items);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<AdditionalPartsRes> executeForServer({
|
||||||
|
required String serverId,
|
||||||
|
required String itemId,
|
||||||
|
}) async {
|
||||||
|
final ctx = await resolveAuthedContextForServer(
|
||||||
|
sessionRepository: sessionRepository,
|
||||||
|
serverRepository: serverRepository,
|
||||||
|
serverId: serverId,
|
||||||
|
);
|
||||||
|
if (ctx == null) {
|
||||||
|
return AdditionalPartsRes(itemId: itemId, items: const []);
|
||||||
|
}
|
||||||
|
final items = await embyGateway.getAdditionalParts(ctx, itemId);
|
||||||
|
return AdditionalPartsRes(itemId: itemId, items: items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetSpecialFeaturesUseCase extends AuthedUseCase {
|
||||||
|
GetSpecialFeaturesUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<EmbyRawItem>> execute(String itemId) async {
|
||||||
|
return embyGateway.getSpecialFeatures(await ctx(), itemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CrossServerSearchUseCase extends AuthedUseCase {
|
||||||
|
CrossServerSearchUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
final Map<String, _SeriesIdentity?> _seriesIdentityCache = {};
|
||||||
|
final Map<String, _EmptyServerSearchStreak> _emptyServerSearchStreaks = {};
|
||||||
|
|
||||||
|
static const _emptyServerSkipThreshold = 3;
|
||||||
|
static const _emptyServerSkipTtl = Duration(hours: 1);
|
||||||
|
|
||||||
|
String _identityCacheKey(String serverId, String seriesPidQuery) =>
|
||||||
|
'$serverId|$seriesPidQuery';
|
||||||
|
|
||||||
|
|
||||||
|
Stream<List<CrossServerSourceCard>> executeStream(
|
||||||
|
CrossServerSearchReq input, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) {
|
||||||
|
final controller = StreamController<List<CrossServerSourceCard>>();
|
||||||
|
final sw = Stopwatch()..start();
|
||||||
|
() async {
|
||||||
|
try {
|
||||||
|
final activeServerId = await sessionRepository.getActiveServerId();
|
||||||
|
final excludedServerId = input.excludedServerId ?? activeServerId;
|
||||||
|
final allSessions = await sessionRepository.listAll();
|
||||||
|
final allServers = await serverRepository.list();
|
||||||
|
final serverMap = {for (final s in allServers) s.id: s};
|
||||||
|
|
||||||
|
final acc = <CrossServerSourceCard>[];
|
||||||
|
var probed = 0;
|
||||||
|
var skipped = 0;
|
||||||
|
final futures = <Future<void>>[];
|
||||||
|
for (final session in allSessions) {
|
||||||
|
if (session.serverId == excludedServerId) continue;
|
||||||
|
final server = serverMap[session.serverId];
|
||||||
|
if (server == null) continue;
|
||||||
|
if (_shouldSkipEmptyServer(server)) {
|
||||||
|
skipped++;
|
||||||
|
final streak = _emptyServerSearchStreaks[server.id];
|
||||||
|
AppLogger.debug(
|
||||||
|
'DetailTiming',
|
||||||
|
'xserver.server name=${server.name} skipped emptyStreak=${streak?.count ?? 0}',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
probed++;
|
||||||
|
futures.add(() async {
|
||||||
|
final serverSw = Stopwatch()..start();
|
||||||
|
final cards = await _buildCardsForServer(
|
||||||
|
server,
|
||||||
|
session,
|
||||||
|
input,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
AppLogger.debug(
|
||||||
|
'DetailTiming',
|
||||||
|
'xserver.server name=${server.name} cards=${cards.length} ms=${serverSw.elapsedMilliseconds}',
|
||||||
|
);
|
||||||
|
if (cancelToken?.isCancelled == true) return;
|
||||||
|
_recordServerSearchResult(server, cards);
|
||||||
|
if (cards.isEmpty) return;
|
||||||
|
acc.addAll(cards);
|
||||||
|
if (!controller.isClosed) {
|
||||||
|
controller.add(List.unmodifiable(acc));
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
await Future.wait(futures);
|
||||||
|
AppLogger.debug(
|
||||||
|
'DetailTiming',
|
||||||
|
'xserver.total probed=$probed skipped=$skipped cards=${acc.length} ms=${sw.elapsedMilliseconds}',
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!controller.isClosed) controller.addError(e);
|
||||||
|
} finally {
|
||||||
|
if (!controller.isClosed) await controller.close();
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldSkipEmptyServer(EmbyServer server) {
|
||||||
|
final streak = _emptyServerSearchStreaks[server.id];
|
||||||
|
final skipUntil = streak?.skipUntil;
|
||||||
|
if (skipUntil == null) return false;
|
||||||
|
if (DateTime.now().isBefore(skipUntil)) return true;
|
||||||
|
_emptyServerSearchStreaks.remove(server.id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _recordServerSearchResult(
|
||||||
|
EmbyServer server,
|
||||||
|
List<CrossServerSourceCard> cards,
|
||||||
|
) {
|
||||||
|
if (cards.isNotEmpty) {
|
||||||
|
_emptyServerSearchStreaks.remove(server.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final streak = _emptyServerSearchStreaks.putIfAbsent(
|
||||||
|
server.id,
|
||||||
|
_EmptyServerSearchStreak.new,
|
||||||
|
);
|
||||||
|
streak.count++;
|
||||||
|
if (streak.count < _emptyServerSkipThreshold) return;
|
||||||
|
streak.skipUntil = DateTime.now().add(_emptyServerSkipTtl);
|
||||||
|
AppLogger.debug(
|
||||||
|
'DetailTiming',
|
||||||
|
'xserver.server name=${server.name} emptyStreak=${streak.count} '
|
||||||
|
'skipUntil=${streak.skipUntil!.toIso8601String()}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CrossServerSourceCard>> _buildCardsForServer(
|
||||||
|
EmbyServer server,
|
||||||
|
SessionData session,
|
||||||
|
CrossServerSearchReq req, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
final ctx = AuthedRequestContext(
|
||||||
|
baseUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
if (req.anchorType == 'Movie') {
|
||||||
|
return await _buildMovieCards(
|
||||||
|
ctx,
|
||||||
|
server,
|
||||||
|
session,
|
||||||
|
req,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (req.anchorType == 'Episode') {
|
||||||
|
return await _buildEpisodeCards(
|
||||||
|
ctx,
|
||||||
|
server,
|
||||||
|
session,
|
||||||
|
req,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const [];
|
||||||
|
} on CancelledException {
|
||||||
|
return const [];
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.warn(_ucTag, 'CrossServerSearch on ${server.name} failed', e);
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CrossServerSourceCard>> _buildMovieCards(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
EmbyServer server,
|
||||||
|
SessionData session,
|
||||||
|
CrossServerSearchReq req, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
const movieFields = 'ProviderIds,UserData,MediaSources,MediaStreams';
|
||||||
|
final providerQuery = _buildProviderIdQuery(req.providerIds);
|
||||||
|
var items = <EmbyRawItem>[];
|
||||||
|
if (providerQuery != null && providerQuery.isNotEmpty) {
|
||||||
|
final providerItems = await embyGateway.getLibraryItems(
|
||||||
|
ctx: ctx,
|
||||||
|
includeItemTypes: 'Movie',
|
||||||
|
anyProviderIdEquals: providerQuery,
|
||||||
|
fields: movieFields,
|
||||||
|
limit: 10,
|
||||||
|
recursive: true,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
items = providerItems
|
||||||
|
.where((item) => _itemMatchesAnyProviderId(item, req.providerIds))
|
||||||
|
.toList(growable: false);
|
||||||
|
final droppedItemCount = providerItems.length - items.length;
|
||||||
|
if (droppedItemCount > 0) {
|
||||||
|
AppLogger.debug(
|
||||||
|
'DetailTiming',
|
||||||
|
'xserver.provider-filter server=${server.name} '
|
||||||
|
'dropped=$droppedItemCount returned=${providerItems.length}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items.isEmpty && req.itemName.isNotEmpty) {
|
||||||
|
final nameSearchItems = await embyGateway.getLibraryItems(
|
||||||
|
ctx: ctx,
|
||||||
|
includeItemTypes: 'Movie',
|
||||||
|
searchTerm: req.itemName,
|
||||||
|
fields: movieFields,
|
||||||
|
limit: 20,
|
||||||
|
recursive: true,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
items = nameSearchItems
|
||||||
|
.where(
|
||||||
|
(item) => !_itemHasConflictingProviderId(item, req.providerIds),
|
||||||
|
)
|
||||||
|
.toList(growable: false);
|
||||||
|
}
|
||||||
|
final cards = <CrossServerSourceCard>[];
|
||||||
|
if (cancelToken?.isCancelled == true) return List.unmodifiable(cards);
|
||||||
|
final detailedList = await Future.wait(
|
||||||
|
items.map(
|
||||||
|
(it) => _fetchDetailedItem(ctx, server, it, cancelToken: cancelToken),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (cancelToken?.isCancelled == true) return List.unmodifiable(cards);
|
||||||
|
for (final detailed in detailedList) {
|
||||||
|
cards.addAll(_expandToCards(server, session, detailed));
|
||||||
|
}
|
||||||
|
return List.unmodifiable(cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<EmbyRawItem> _fetchDetailedItem(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
EmbyServer server,
|
||||||
|
EmbyRawItem item, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
if (cancelToken?.isCancelled == true) return item;
|
||||||
|
try {
|
||||||
|
final full = await embyGateway.getItemDetail(ctx, item.Id);
|
||||||
|
final fullSources = full.extra['MediaSources'];
|
||||||
|
if (fullSources is List && fullSources.isNotEmpty) return full;
|
||||||
|
return item;
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.warn(
|
||||||
|
_ucTag,
|
||||||
|
'CrossServer detail fetch failed on ${server.name} item=${item.Id}',
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CrossServerSourceCard>> _buildEpisodeCards(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
EmbyServer server,
|
||||||
|
SessionData session,
|
||||||
|
CrossServerSearchReq req, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
const episodeFields = 'ProviderIds,UserData,MediaSources,MediaStreams';
|
||||||
|
|
||||||
|
final providerQuery = _buildProviderIdQuery(req.providerIds);
|
||||||
|
if (providerQuery != null && providerQuery.isNotEmpty) {
|
||||||
|
final episodes = await embyGateway.getLibraryItems(
|
||||||
|
ctx: ctx,
|
||||||
|
includeItemTypes: 'Episode',
|
||||||
|
anyProviderIdEquals: providerQuery,
|
||||||
|
fields: episodeFields,
|
||||||
|
limit: 5,
|
||||||
|
recursive: true,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
final exact = _matchEpisodeBySE(
|
||||||
|
episodes,
|
||||||
|
req.parentIndexNumber,
|
||||||
|
req.indexNumber,
|
||||||
|
);
|
||||||
|
if (exact != null) {
|
||||||
|
final detailed = await _fetchDetailedItem(
|
||||||
|
ctx,
|
||||||
|
server,
|
||||||
|
exact,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return _expandToCards(server, session, detailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final seriesProviderIds = req.seriesProviderIds;
|
||||||
|
if (seriesProviderIds == null || seriesProviderIds.isEmpty) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final identity = await _resolveSeriesIdentity(
|
||||||
|
ctx,
|
||||||
|
server.id,
|
||||||
|
seriesProviderIds,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
if (identity == null) return const [];
|
||||||
|
|
||||||
|
EmbyRawSeason? matchedSeason;
|
||||||
|
for (final s in identity.seasons) {
|
||||||
|
if (s.IndexNumber == req.parentIndexNumber) {
|
||||||
|
matchedSeason = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchedSeason == null) return const [];
|
||||||
|
|
||||||
|
final episodes = await embyGateway.getSeriesEpisodes(
|
||||||
|
ctx,
|
||||||
|
identity.remoteSeriesId,
|
||||||
|
matchedSeason.Id,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
final target = _matchEpisodeBySE(
|
||||||
|
episodes,
|
||||||
|
req.parentIndexNumber,
|
||||||
|
req.indexNumber,
|
||||||
|
);
|
||||||
|
if (target == null) return const [];
|
||||||
|
final detailed = await _fetchDetailedItem(
|
||||||
|
ctx,
|
||||||
|
server,
|
||||||
|
target,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return _expandToCards(server, session, detailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<_SeriesIdentity?> _resolveSeriesIdentity(
|
||||||
|
AuthedRequestContext ctx,
|
||||||
|
String serverId,
|
||||||
|
Map<String, String> seriesProviderIds, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
final seriesPidQuery = _buildProviderIdQuery(seriesProviderIds);
|
||||||
|
if (seriesPidQuery == null || seriesPidQuery.isEmpty) return null;
|
||||||
|
final key = _identityCacheKey(serverId, seriesPidQuery);
|
||||||
|
if (_seriesIdentityCache.containsKey(key)) {
|
||||||
|
return _seriesIdentityCache[key];
|
||||||
|
}
|
||||||
|
final seriesList = await embyGateway.getLibraryItems(
|
||||||
|
ctx: ctx,
|
||||||
|
includeItemTypes: 'Series',
|
||||||
|
anyProviderIdEquals: seriesPidQuery,
|
||||||
|
fields: 'ProviderIds',
|
||||||
|
limit: 1,
|
||||||
|
recursive: true,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
if (seriesList.isEmpty ||
|
||||||
|
!_itemMatchesAnyProviderId(seriesList.first, seriesProviderIds)) {
|
||||||
|
_seriesIdentityCache[key] = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final remoteSeriesId = seriesList.first.Id;
|
||||||
|
final seasons = await embyGateway.getSeriesSeasons(
|
||||||
|
ctx,
|
||||||
|
remoteSeriesId,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
final identity = _SeriesIdentity(
|
||||||
|
remoteSeriesId: remoteSeriesId,
|
||||||
|
seasons: seasons,
|
||||||
|
);
|
||||||
|
_seriesIdentityCache[key] = identity;
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> prewarmSeriesIdentity(
|
||||||
|
Map<String, String> seriesProviderIds, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
if (seriesProviderIds.isEmpty) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final activeServerId = await sessionRepository.getActiveServerId();
|
||||||
|
final allSessions = await sessionRepository.listAll();
|
||||||
|
final allServers = await serverRepository.list();
|
||||||
|
final serverMap = {for (final s in allServers) s.id: s};
|
||||||
|
|
||||||
|
final futures = <Future<void>>[];
|
||||||
|
for (final session in allSessions) {
|
||||||
|
if (session.serverId == activeServerId) continue;
|
||||||
|
final server = serverMap[session.serverId];
|
||||||
|
if (server == null) continue;
|
||||||
|
final ctx = AuthedRequestContext(
|
||||||
|
baseUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
);
|
||||||
|
futures.add(() async {
|
||||||
|
try {
|
||||||
|
await _resolveSeriesIdentity(
|
||||||
|
ctx,
|
||||||
|
server.id,
|
||||||
|
seriesProviderIds,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
} on CancelledException {
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.warn(
|
||||||
|
_ucTag,
|
||||||
|
'prewarmSeriesIdentity on ${server.name} failed',
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
await Future.wait(futures);
|
||||||
|
} on CancelledException {
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.warn(_ucTag, 'prewarmSeriesIdentity setup failed', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<CrossServerSourceCard> _expandToCards(
|
||||||
|
EmbyServer server,
|
||||||
|
SessionData session,
|
||||||
|
EmbyRawItem item,
|
||||||
|
) {
|
||||||
|
final raw = item.extra['MediaSources'];
|
||||||
|
if (raw is! List) return const [];
|
||||||
|
final sources = raw
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(EmbyRawMediaSource.fromJson)
|
||||||
|
.toList();
|
||||||
|
final cards = <CrossServerSourceCard>[];
|
||||||
|
for (final s in sources) {
|
||||||
|
final id = s.Id;
|
||||||
|
if (id == null) continue;
|
||||||
|
cards.add(
|
||||||
|
CrossServerSourceCard(
|
||||||
|
serverId: server.id,
|
||||||
|
serverName: server.name,
|
||||||
|
serverUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
landingItemId: item.Id,
|
||||||
|
mediaSourceId: id,
|
||||||
|
item: item,
|
||||||
|
mediaSource: s,
|
||||||
|
quality: _extractQualityFromSource(s),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EmbyRawItem? _matchEpisodeBySE(
|
||||||
|
List<EmbyRawItem> episodes,
|
||||||
|
int? parentIndex,
|
||||||
|
int? index,
|
||||||
|
) {
|
||||||
|
if (parentIndex == null || index == null) return null;
|
||||||
|
for (final it in episodes) {
|
||||||
|
final ps = (it.extra['ParentIndexNumber'] as num?)?.toInt();
|
||||||
|
if (ps == parentIndex && it.IndexNumber == index) return it;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _itemMatchesAnyProviderId(
|
||||||
|
EmbyRawItem item,
|
||||||
|
Map<String, String> requestedProviderIds,
|
||||||
|
) {
|
||||||
|
final normalizedRequestedProviderIds = normalizeProviderIds(
|
||||||
|
requestedProviderIds,
|
||||||
|
);
|
||||||
|
if (normalizedRequestedProviderIds.isEmpty) return false;
|
||||||
|
|
||||||
|
final normalizedItemProviderIds = normalizedProviderIdsOfItem(item);
|
||||||
|
for (final requestedEntry in normalizedRequestedProviderIds.entries) {
|
||||||
|
if (normalizedItemProviderIds[requestedEntry.key] ==
|
||||||
|
requestedEntry.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _itemHasConflictingProviderId(
|
||||||
|
EmbyRawItem item,
|
||||||
|
Map<String, String> requestedProviderIds,
|
||||||
|
) {
|
||||||
|
final normalizedRequestedProviderIds = normalizeProviderIds(
|
||||||
|
requestedProviderIds,
|
||||||
|
);
|
||||||
|
if (normalizedRequestedProviderIds.isEmpty) return false;
|
||||||
|
|
||||||
|
final normalizedItemProviderIds = normalizedProviderIdsOfItem(item);
|
||||||
|
for (final requestedEntry in normalizedRequestedProviderIds.entries) {
|
||||||
|
final itemProviderId = normalizedItemProviderIds[requestedEntry.key];
|
||||||
|
if (itemProviderId != null && itemProviderId != requestedEntry.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MediaQualityInfo? _extractQualityFromSource(EmbyRawMediaSource src) {
|
||||||
|
final streams = src.MediaStreams ?? const [];
|
||||||
|
if (streams.isEmpty || !streams.any((s) => s.Type == 'Video')) {
|
||||||
|
final fallback = MediaQualityInfo.fromSourceName(
|
||||||
|
src.Name,
|
||||||
|
fallbackHeight: src.Height,
|
||||||
|
container: src.Container,
|
||||||
|
);
|
||||||
|
AppLogger.debug(
|
||||||
|
'DetailQuality',
|
||||||
|
'name-fallback sourceId=${src.Id ?? 'null'} '
|
||||||
|
'name="${src.Name ?? 'null'}" -> '
|
||||||
|
'${fallback == null ? 'null' : '"${fallback.resolutionLabel} ${fallback.dynamicRangeLabel}"'}',
|
||||||
|
);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return MediaQualityInfo.fromMediaSource(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _buildProviderIdQuery(Map<String, String> providerIds) {
|
||||||
|
if (providerIds.isEmpty) return null;
|
||||||
|
final parts = <String>[];
|
||||||
|
for (final entry in providerIds.entries) {
|
||||||
|
if (entry.value.isNotEmpty) {
|
||||||
|
parts.add('${entry.key}.${entry.value}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parts.isEmpty) return null;
|
||||||
|
return parts.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SeriesIdentity {
|
||||||
|
final String remoteSeriesId;
|
||||||
|
final List<EmbyRawSeason> seasons;
|
||||||
|
const _SeriesIdentity({required this.remoteSeriesId, required this.seasons});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmptyServerSearchStreak {
|
||||||
|
int count = 0;
|
||||||
|
DateTime? skipUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String _buildAvailabilityProviderQuery({
|
||||||
|
required String tmdbId,
|
||||||
|
String? imdbId,
|
||||||
|
}) {
|
||||||
|
final parts = <String>['Tmdb.${tmdbId.trim()}'];
|
||||||
|
final imdb = imdbId?.trim() ?? '';
|
||||||
|
if (imdb.isNotEmpty) parts.add('Imdb.$imdb');
|
||||||
|
return parts.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TmdbAvailabilityUseCase extends AuthedUseCase {
|
||||||
|
TmdbAvailabilityUseCase({
|
||||||
|
required super.sessionRepository,
|
||||||
|
required super.serverRepository,
|
||||||
|
required super.embyGateway,
|
||||||
|
this.settleTimeout = const Duration(seconds: 3),
|
||||||
|
});
|
||||||
|
|
||||||
|
final Duration settleTimeout;
|
||||||
|
|
||||||
|
Stream<List<TmdbLibraryHit>> executeStream(
|
||||||
|
TmdbAvailabilityReq input, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) {
|
||||||
|
final controller = StreamController<List<TmdbLibraryHit>>();
|
||||||
|
Timer? settleTimer;
|
||||||
|
() async {
|
||||||
|
try {
|
||||||
|
final tmdbId = input.tmdbId.trim();
|
||||||
|
if (tmdbId.isEmpty) {
|
||||||
|
if (!controller.isClosed) controller.add(const []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final isMovie = input.mediaType == 'movie';
|
||||||
|
final includeItemTypes = isMovie ? 'Movie' : 'Series';
|
||||||
|
final providerQuery = _buildAvailabilityProviderQuery(
|
||||||
|
tmdbId: tmdbId,
|
||||||
|
imdbId: isMovie ? input.imdbId : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
final allSessions = await sessionRepository.listAll();
|
||||||
|
final allServers = await serverRepository.list();
|
||||||
|
final serverMap = {for (final s in allServers) s.id: s};
|
||||||
|
|
||||||
|
final acc = <TmdbLibraryHit>[];
|
||||||
|
var settled = false;
|
||||||
|
void emit() {
|
||||||
|
if (!controller.isClosed) controller.add(List.unmodifiable(acc));
|
||||||
|
}
|
||||||
|
|
||||||
|
var pending = 0;
|
||||||
|
final futures = <Future<void>>[];
|
||||||
|
for (final session in allSessions) {
|
||||||
|
final server = serverMap[session.serverId];
|
||||||
|
if (server == null) continue;
|
||||||
|
pending++;
|
||||||
|
futures.add(
|
||||||
|
_probeServer(
|
||||||
|
server,
|
||||||
|
session,
|
||||||
|
includeItemTypes,
|
||||||
|
providerQuery,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
).then((hit) {
|
||||||
|
pending--;
|
||||||
|
if (hit != null) acc.add(hit);
|
||||||
|
if (pending == 0) {
|
||||||
|
settleTimer?.cancel();
|
||||||
|
if (!settled || hit != null) emit();
|
||||||
|
} else if (settled && hit != null) {
|
||||||
|
emit();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (pending == 0) {
|
||||||
|
emit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settleTimer = Timer(settleTimeout, () {
|
||||||
|
settled = true;
|
||||||
|
emit();
|
||||||
|
});
|
||||||
|
await Future.wait(futures);
|
||||||
|
} catch (e) {
|
||||||
|
if (!controller.isClosed) controller.addError(e);
|
||||||
|
} finally {
|
||||||
|
settleTimer?.cancel();
|
||||||
|
if (!controller.isClosed) await controller.close();
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TmdbLibraryHit?> _probeServer(
|
||||||
|
EmbyServer server,
|
||||||
|
SessionData session,
|
||||||
|
String includeItemTypes,
|
||||||
|
String providerQuery, {
|
||||||
|
CancellationToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
final ctx = AuthedRequestContext(
|
||||||
|
baseUrl: server.baseUrl,
|
||||||
|
token: session.token,
|
||||||
|
userId: session.userId,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final items = await embyGateway.getLibraryItems(
|
||||||
|
ctx: ctx,
|
||||||
|
includeItemTypes: includeItemTypes,
|
||||||
|
anyProviderIdEquals: providerQuery,
|
||||||
|
fields: 'ProviderIds,ProductionYear,UserData',
|
||||||
|
limit: 1,
|
||||||
|
recursive: true,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
if (items.isEmpty) return null;
|
||||||
|
final item = items.first;
|
||||||
|
return TmdbLibraryHit(
|
||||||
|
serverId: server.id,
|
||||||
|
serverName: server.name,
|
||||||
|
itemId: item.Id,
|
||||||
|
itemType: item.Type ?? includeItemTypes,
|
||||||
|
name: item.Name,
|
||||||
|
productionYear: item.ProductionYear,
|
||||||
|
playbackPositionTicks: item.UserData?.PlaybackPositionTicks,
|
||||||
|
runTimeTicks: item.RunTimeTicks,
|
||||||
|
);
|
||||||
|
} on CancelledException {
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.warn(_ucTag, 'TmdbAvailability on ${server.name} failed', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user