Merge branch 'DonutWare:develop' into develop

This commit is contained in:
Jan 2025-06-22 18:25:26 +02:00 committed by GitHub
commit 2c51b9400d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
266 changed files with 12667 additions and 5649 deletions

2
.fvmrc
View file

@ -1,3 +1,3 @@
{ {
"flutter": "3.29.2" "flutter": "3.32.1"
} }

View file

@ -14,19 +14,87 @@ on:
types: types:
- opened - opened
- reopened - reopened
schedule:
# Run nightly at midnight UTC, but only if there were changes to develop
- cron: "0 0 * * *"
workflow_dispatch: workflow_dispatch:
inputs:
build_type:
description: "Build type (release, nightly, or auto)"
required: false
default: "auto"
type: choice
options:
- auto
- release
- nightly
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NIGHTLY_TAG: nightly
jobs: jobs:
# Check if workflow should run based on trigger conditions
check-trigger:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
build_type: ${{ steps.check.outputs.build_type }}
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- name: Check if should run and determine build type
id: check
run: |
# Determine build type based on trigger and input
if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.build_type }}" != "auto" ]]; then
BUILD_TYPE="${{ github.event.inputs.build_type }}"
elif [[ "${{ startsWith(github.ref, 'refs/tags/v') }}" == "true" ]]; then
BUILD_TYPE="release"
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
# Check if there were commits to develop in the last 24 hours
git checkout develop
COMMITS_LAST_24H=$(git log --since="24 hours ago" --oneline | wc -l)
if [[ $COMMITS_LAST_24H -gt 0 ]]; then
BUILD_TYPE="nightly"
else
echo "No commits to develop in the last 24 hours, skipping nightly build"
echo "should_run=false" >> $GITHUB_OUTPUT
exit 0
fi
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
BUILD_TYPE="nightly"
else
# For PRs and other events, build but don't release
BUILD_TYPE="development"
fi
echo "build_type=${BUILD_TYPE}" >> $GITHUB_OUTPUT
echo "should_run=true" >> $GITHUB_OUTPUT
echo "Build type determined: ${BUILD_TYPE}"
fetch-info: fetch-info:
needs: [check-trigger]
if: needs.check-trigger.outputs.should_run == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
version_name: ${{ steps.fetch.outputs.version_name }} version_name: ${{ steps.fetch.outputs.version_name }}
flutter_version: ${{ steps.fetch.outputs.flutter_version }} flutter_version: ${{ steps.fetch.outputs.flutter_version }}
nightly_version: ${{ steps.fetch.outputs.nightly_version }}
build_type: ${{ needs.check-trigger.outputs.build_type }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- name: Fetch version name - name: Fetch version name and create nightly version
id: fetch id: fetch
run: | run: |
# Extract version_name from pubspec.yaml # Extract version_name from pubspec.yaml
@ -36,29 +104,47 @@ jobs:
# Extract flutter_version from .fvmrc # Extract flutter_version from .fvmrc
FLUTTER_VERSION=$(jq -r '.flutter' .fvmrc) FLUTTER_VERSION=$(jq -r '.flutter' .fvmrc)
echo "flutter_version=${FLUTTER_VERSION}" >> "$GITHUB_OUTPUT" echo "flutter_version=${FLUTTER_VERSION}" >> "$GITHUB_OUTPUT"
# Create nightly version if needed
if [[ "${{ needs.check-trigger.outputs.build_type }}" == "nightly" ]]; then
NIGHTLY_VERSION="${VERSION_NAME}-nightly"
echo "nightly_version=${NIGHTLY_VERSION}" >> "$GITHUB_OUTPUT"
echo "Nightly version: $NIGHTLY_VERSION"
fi
# Print versions for logging
echo "Base version: $VERSION_NAME"
echo "Flutter version: $FLUTTER_VERSION"
shell: bash shell: bash
build-android: build-android:
needs: [fetch-info] needs: [fetch-info]
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
artifact_suffix: ${{ env.ARTIFACT_SUFFIX }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Determine Build Type - name: Determine Build Type
run: | run: |
if [[ "${{ startsWith(github.ref, 'refs/tags/v') }}" == "true" ]]; then if [[ "${{ needs.fetch-info.outputs.build_type }}" == "release" ]]; then
echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "ARTIFACT_SUFFIX=release-signed" >> $GITHUB_ENV echo "ARTIFACT_SUFFIX=release-signed" >> $GITHUB_ENV
echo "AAB_PATH=productionRelease" >> $GITHUB_ENV echo "AAB_PATH=productionRelease" >> $GITHUB_ENV
elif [[ "${{ needs.fetch-info.outputs.build_type }}" == "nightly" ]]; then
echo "BUILD_MODE=profile" >> $GITHUB_ENV
echo "ARTIFACT_SUFFIX=nightly" >> $GITHUB_ENV
echo "AAB_PATH=productionProfile" >> $GITHUB_ENV
else else
echo "BUILD_MODE=profile" >> $GITHUB_ENV echo "BUILD_MODE=profile" >> $GITHUB_ENV
echo "ARTIFACT_SUFFIX=production" >> $GITHUB_ENV echo "ARTIFACT_SUFFIX=production" >> $GITHUB_ENV
echo "AAB_PATH=productionProfile" >> $GITHUB_ENV echo "AAB_PATH=productionProfile" >> $GITHUB_ENV
fi fi
echo "ARTIFACT_SUFFIX=${ARTIFACT_SUFFIX}" >> $GITHUB_OUTPUT
- name: Decode Keystore for release - name: Decode Keystore for release
if: startsWith(github.ref, 'refs/tags/v') if: needs.fetch-info.outputs.build_type == 'release'
env: env:
ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }} ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }} RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
@ -84,10 +170,10 @@ jobs:
check-latest: true check-latest: true
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2.16.0 uses: subosito/flutter-action@v2.19.0
with: with:
channel: "stable" channel: "stable"
flutter-version: ${{needs.fetch-info.outputs.flutter-version}} flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
cache: true cache: true
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
@ -121,10 +207,10 @@ jobs:
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2.16.0 uses: subosito/flutter-action@v2.19.0
with: with:
channel: "stable" channel: "stable"
flutter-version: ${{needs.fetch-info.outputs.flutter-version}} flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
cache: true cache: true
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
@ -141,7 +227,7 @@ jobs:
path: windows/windows_setup.iss path: windows/windows_setup.iss
options: /O+ options: /O+
env: env:
FLADDER_VERSION: ${{needs.fetch-info.outputs.version_name}} FLADDER_VERSION: ${{ needs.fetch-info.outputs.version_name }}
- name: Archive Windows portable artifact - name: Archive Windows portable artifact
uses: actions/upload-artifact@v4.0.0 uses: actions/upload-artifact@v4.0.0
@ -164,10 +250,10 @@ jobs:
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2.16.0 uses: subosito/flutter-action@v2.19.0
with: with:
channel: "stable" channel: "stable"
flutter-version: ${{needs.fetch-info.outputs.flutter-version}} flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
cache: true cache: true
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
@ -184,7 +270,6 @@ jobs:
mkdir Payload mkdir Payload
mv Runner.app Payload/ mv Runner.app Payload/
zip -r iOS.ipa Payload zip -r iOS.ipa Payload
- name: Archive iOS IPA artifact - name: Archive iOS IPA artifact
uses: actions/upload-artifact@v4.0.0 uses: actions/upload-artifact@v4.0.0
with: with:
@ -200,10 +285,10 @@ jobs:
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2.16.0 uses: subosito/flutter-action@v2.19.0
with: with:
channel: "stable" channel: "stable"
flutter-version: ${{needs.fetch-info.outputs.flutter-version}} flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
cache: true cache: true
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
@ -214,8 +299,16 @@ jobs:
- name: Build macOS app - name: Build macOS app
run: flutter build macos --flavor production --build-number=${{ github.run_number }} run: flutter build macos --flavor production --build-number=${{ github.run_number }}
- name: Create DMG file - name: Ensure correct app name casing
run: hdiutil create -format UDZO -srcfolder build/macos/Build/Products/Release-production/fladder.app build/macos/Build/Products/Release-production/macOS.dmg run: |
APP_DIR="build/macos/Build/Products/Release-production"
mv "$APP_DIR/fladder.app" "$APP_DIR/Fladder.app"
- name: Install create-dmg
run: brew install create-dmg
- name: Create DMG with custom background
run: ./scripts/create_dmg.sh
- name: Archive macOS artifact - name: Archive macOS artifact
uses: actions/upload-artifact@v4.0.0 uses: actions/upload-artifact@v4.0.0
@ -232,10 +325,10 @@ jobs:
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2.16.0 uses: subosito/flutter-action@v2.19.0
with: with:
channel: "stable" channel: "stable"
flutter-version: ${{needs.fetch-info.outputs.flutter-version}} flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
cache: true cache: true
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
@ -271,7 +364,8 @@ jobs:
- name: Build AppImage - name: Build AppImage
run: | run: |
sed -i -E 's/(version:\s*)latest/\1${{needs.fetch-info.outputs.version_name}}/' AppImageBuilder.yml VERSION_TO_USE="${{ needs.fetch-info.outputs.version_name }}"
sed -i -E "s/(version:\\s*)latest/\\1${VERSION_TO_USE}/" AppImageBuilder.yml
wget -O appimage-builder-x86_64.AppImage https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage wget -O appimage-builder-x86_64.AppImage https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
chmod +x appimage-builder-x86_64.AppImage chmod +x appimage-builder-x86_64.AppImage
sudo mv appimage-builder-x86_64.AppImage /usr/local/bin/appimage-builder sudo mv appimage-builder-x86_64.AppImage /usr/local/bin/appimage-builder
@ -286,9 +380,8 @@ jobs:
Fladder_x86_64.AppImage.zsync Fladder_x86_64.AppImage.zsync
build-linux-flatpak: build-linux-flatpak:
name: "Flatpak"
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') if: needs.fetch-info.outputs.build_type == 'release' || needs.fetch-info.outputs.build_type == 'nightly'
needs: [fetch-info, build-linux] needs: [fetch-info, build-linux]
container: container:
image: bilelmoussaoui/flatpak-github-actions:gnome-46 image: bilelmoussaoui/flatpak-github-actions:gnome-46
@ -325,10 +418,10 @@ jobs:
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2.16.0 uses: subosito/flutter-action@v2.19.0
with: with:
channel: "stable" channel: "stable"
flutter-version: ${{needs.fetch-info.outputs.flutter-version}} flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
cache: true cache: true
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
@ -347,10 +440,11 @@ jobs:
path: build/web path: build/web
- name: Build Github pages web - name: Build Github pages web
if: startsWith(github.ref, 'refs/tags/v') if: needs.fetch-info.outputs.build_type == 'release'
run: flutter build web --base-href /${{ github.event.repository.name }}/ --release --build-number=$GITHUB_RUN_NUMBER run: flutter build web --base-href /${{ github.event.repository.name }}/ --release --build-number=$GITHUB_RUN_NUMBER
- name: Archive web pages artifact - name: Archive web pages artifact
if: needs.fetch-info.outputs.build_type == 'release'
uses: actions/upload-artifact@v4.0.0 uses: actions/upload-artifact@v4.0.0
with: with:
name: fladder-web-pages name: fladder-web-pages
@ -368,8 +462,93 @@ jobs:
- build-linux-flatpak - build-linux-flatpak
- build-web - build-web
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') if: needs.fetch-info.outputs.build_type == 'release' || needs.fetch-info.outputs.build_type == 'nightly'
permissions:
contents: write
steps: steps:
- name: Checkout repository
if: needs.fetch-info.outputs.build_type == 'nightly'
uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- name: Set version variables
id: version
run: |
if [[ "${{ needs.fetch-info.outputs.build_type }}" == "nightly" ]]; then
echo "version=${{ needs.fetch-info.outputs.nightly_version }}" >> $GITHUB_OUTPUT
else
echo "version=${{ needs.fetch-info.outputs.version_name }}" >> $GITHUB_OUTPUT
fi
- name: Delete existing nightly release
if: needs.fetch-info.outputs.build_type == 'nightly'
run: |
# Delete existing nightly release and tag with better error handling
echo "Checking for existing nightly release..."
if gh release view ${{ env.NIGHTLY_TAG }} >/dev/null 2>&1; then
echo "Deleting existing nightly release..."
gh release delete ${{ env.NIGHTLY_TAG }} --yes --cleanup-tag
else
echo "No existing nightly release found."
fi
# Clean up any orphaned tags
if git tag -l | grep -q "^${{ env.NIGHTLY_TAG }}$"; then
echo "Deleting orphaned nightly tag..."
git push origin --delete ${{ env.NIGHTLY_TAG }} || true
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate changelog
if: needs.fetch-info.outputs.build_type == 'nightly'
id: changelog
run: |
# Get the latest stable release tag for comparison
LATEST_STABLE_RELEASE=$(gh release list --limit 50 --exclude-drafts --json tagName,isPrerelease --jq '.[] | select(.isPrerelease == false) | .tagName' | head -1 || echo "")
# Create new changelog with comparison link
cat > changelog.md <<-EOF
# $(date -u "+%Y-%m-%d %H:%M:%S UTC")
This is an automated nightly build containing the latest changes from the develop branch.
**⚠️ Warning:** This is a development build and may contain bugs or incomplete features.
EOF
# Add comparison link if we have a latest stable release
if [ -n "$LATEST_STABLE_RELEASE" ]; then
cat >> changelog.md <<-EOF
## 📋 What's Changed
See all changes since the latest release: [Compare $LATEST_STABLE_RELEASE...HEAD](https://github.com/${{ github.repository }}/compare/$LATEST_STABLE_RELEASE...HEAD)
EOF
else
cat >> changelog.md <<-EOF
## 📋 What's Changed
See all changes: [View commits](https://github.com/${{ github.repository }}/commits/develop)
EOF
fi
# Add build metadata
cat >> changelog.md <<-EOF
---
📅 **Build Date:** $(date -u "+%Y-%m-%d %H:%M:%S UTC")
🔧 **Build Number:** ${{ github.run_number }}
📝 **Commit:** ${{ github.sha }}
🌿 **Branch:** develop
EOF
echo "changelog_file=changelog.md" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download Artifacts Android - name: Download Artifacts Android
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@ -378,8 +557,8 @@ jobs:
- name: Move Android - name: Move Android
run: | run: |
mv fladder-android/release-signed.apk Fladder-Android-${{needs.fetch-info.outputs.version_name}}.apk mv fladder-android/${{ needs.build-android.outputs.artifact_suffix }}.apk Fladder-Android-${{ steps.version.outputs.version }}.apk
mv fladder-android/release-signed.aab Fladder-Android-${{needs.fetch-info.outputs.version_name}}.aab mv fladder-android/${{ needs.build-android.outputs.artifact_suffix }}.aab Fladder-Android-${{ steps.version.outputs.version }}.aab
- name: Download Windows portable artifact - name: Download Windows portable artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@ -390,7 +569,7 @@ jobs:
- name: Compress Windows - name: Compress Windows
run: | run: |
cd fladder-windows-portable cd fladder-windows-portable
zip -r ../Fladder-Windows-${{needs.fetch-info.outputs.version_name}}.zip . zip -r ../Fladder-Windows-${{ steps.version.outputs.version }}.zip .
- name: Download Windows installer artifact - name: Download Windows installer artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@ -399,7 +578,7 @@ jobs:
path: fladder-windows-installer path: fladder-windows-installer
- name: Rename Windows installer - name: Rename Windows installer
run: mv fladder-windows-installer/fladder_setup.exe Fladder-Windows-${{needs.fetch-info.outputs.version_name}}-Setup.exe run: mv fladder-windows-installer/fladder_setup.exe Fladder-Windows-${{ steps.version.outputs.version }}-Setup.exe
- name: Download Artifacts iOS - name: Download Artifacts iOS
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@ -408,7 +587,7 @@ jobs:
path: fladder-iOS path: fladder-iOS
- name: Move iOS - name: Move iOS
run: mv fladder-iOS/iOS.ipa Fladder-iOS-${{needs.fetch-info.outputs.version_name}}.ipa run: mv fladder-iOS/iOS.ipa Fladder-iOS-${{ steps.version.outputs.version }}.ipa
- name: Download Artifacts macOS - name: Download Artifacts macOS
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@ -417,7 +596,7 @@ jobs:
path: fladder-macOS path: fladder-macOS
- name: Move macOS - name: Move macOS
run: mv fladder-macOS/macOS.dmg Fladder-macOS-${{needs.fetch-info.outputs.version_name}}.dmg run: mv fladder-macOS/macOS.dmg Fladder-macOS-${{ steps.version.outputs.version }}.dmg
- name: Download Artifacts Linux - name: Download Artifacts Linux
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@ -428,16 +607,18 @@ jobs:
- name: Compress Linux - name: Compress Linux
run: | run: |
cd fladder-linux cd fladder-linux
zip -r ../Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.zip . zip -r ../Fladder-Linux-${{ steps.version.outputs.version }}.zip .
- name: Download Artifacts Linux Flatpak - name: Download Artifacts Linux Flatpak
if: needs.fetch-info.outputs.build_type == 'release'
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: fladder-linux-flatpak name: fladder-linux-flatpak
path: fladder-linux-flatpak path: fladder-linux-flatpak
- name: Move Linux Flatpak - name: Move Linux Flatpak
run: mv fladder-linux-flatpak/Fladder-Linux.flatpak Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.flatpak if: needs.fetch-info.outputs.build_type == 'release'
run: mv fladder-linux-flatpak/Fladder-Linux.flatpak Fladder-Linux-${{ steps.version.outputs.version }}.flatpak
- name: Download Artifacts Linux AppImage - name: Download Artifacts Linux AppImage
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@ -445,11 +626,10 @@ jobs:
name: fladder-linux-appimage name: fladder-linux-appimage
path: fladder-linux-appimage path: fladder-linux-appimage
- name: Move linux AppImages - name: Move Linux AppImages
run: | run: |
mv fladder-linux-appimage/Fladder_x86_64.AppImage Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.AppImage mv fladder-linux-appimage/Fladder_x86_64.AppImage Fladder-Linux-${{ steps.version.outputs.version }}.AppImage
mv fladder-linux-appimage/Fladder_x86_64.AppImage.zsync Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.AppImage.zsync mv fladder-linux-appimage/Fladder_x86_64.AppImage.zsync Fladder-Linux-${{ steps.version.outputs.version }}.AppImage.zsync
- name: Download Artifacts Web - name: Download Artifacts Web
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@ -459,32 +639,50 @@ jobs:
- name: Compress Web - name: Compress Web
run: | run: |
cd fladder-web cd fladder-web
zip -r ../Fladder-Web-${{needs.fetch-info.outputs.version_name}}.zip . zip -r ../Fladder-Web-${{ steps.version.outputs.version }}.zip .
- name: Release - name: Create Release
uses: softprops/action-gh-release@v2.2.2
with:
tag_name: ${{ needs.fetch-info.outputs.build_type == 'nightly' && env.NIGHTLY_TAG || github.ref_name }}
name: ${{ needs.fetch-info.outputs.build_type == 'nightly' && format('🌙 Nightly Build - {0}', steps.version.outputs.version) || '' }}
body_path: ${{ needs.fetch-info.outputs.build_type == 'nightly' && 'changelog.md' || '' }}
draft: ${{ needs.fetch-info.outputs.build_type == 'release' }}
prerelease: ${{ needs.fetch-info.outputs.build_type == 'nightly' }}
make_latest: ${{ needs.fetch-info.outputs.build_type == 'release' }}
generate_release_notes: ${{ needs.fetch-info.outputs.build_type == 'release' }}
fail_on_unmatched_files: true
files: |
Fladder-Android-${{ steps.version.outputs.version }}.apk
Fladder-Android-${{ steps.version.outputs.version }}.aab
Fladder-Windows-${{ steps.version.outputs.version }}-Setup.exe
Fladder-Windows-${{ steps.version.outputs.version }}.zip
Fladder-iOS-${{ steps.version.outputs.version }}.ipa
Fladder-macOS-${{ steps.version.outputs.version }}.dmg
Fladder-Web-${{ steps.version.outputs.version }}.zip
Fladder-Linux-${{ steps.version.outputs.version }}.zip
Fladder-Linux-${{ steps.version.outputs.version }}.AppImage
Fladder-Linux-${{ steps.version.outputs.version }}.AppImage.zsync
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Add Flatpak to release
if: needs.fetch-info.outputs.build_type == 'release'
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
draft: true tag_name: ${{ github.ref_name }}
fail_on_unmatched_files: true
generate_release_notes: true
files: | files: |
Fladder-Android-${{needs.fetch-info.outputs.version_name}}.apk Fladder-Linux-${{ steps.version.outputs.version }}.flatpak
Fladder-Windows-${{needs.fetch-info.outputs.version_name}}-Setup.exe env:
Fladder-Windows-${{needs.fetch-info.outputs.version_name}}.zip GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Fladder-iOS-${{needs.fetch-info.outputs.version_name}}.ipa
Fladder-macOS-${{needs.fetch-info.outputs.version_name}}.dmg
Fladder-Web-${{needs.fetch-info.outputs.version_name}}.zip
Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.zip
Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.flatpak
Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.AppImage
Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.AppImage.zsync
release_web: release_web:
name: Release Web name: Release Web
needs: needs:
- fetch-info
- create_release - create_release
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') if: needs.fetch-info.outputs.build_type == 'release'
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1

5
.gitignore vendored
View file

@ -5,6 +5,8 @@
*.swp *.swp
.DS_Store .DS_Store
.atom/ .atom/
.build/
.dmg_temp/
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
@ -73,3 +75,6 @@ local.properties
# Inno Setup builds # Inno Setup builds
Output Output
# Generated localizations
lib/l10n/generated

19
.vscode/launch.json vendored
View file

@ -10,7 +10,18 @@
"type": "dart", "type": "dart",
"args": [ "args": [
"--flavor", "--flavor",
"development" "development",
]
},
{
"name": "Fladder Development HTPC (debug)",
"request": "launch",
"type": "dart",
"args": [
"--flavor",
"development",
"-a",
"--htpc",
] ]
}, },
{ {
@ -69,7 +80,8 @@
"deviceId": "chrome", "deviceId": "chrome",
"args": [ "args": [
"--web-port", "--web-port",
"9090" "9090",
"--web-experimental-hot-reload"
], ],
}, },
{ {
@ -80,7 +92,8 @@
"flutterMode": "release", "flutterMode": "release",
"args": [ "args": [
"--web-port", "--web-port",
"9090" "9090",
"--web-experimental-hot-reload"
], ],
}, },
], ],

12
.vscode/settings.json vendored
View file

@ -7,7 +7,7 @@
"LTWH", "LTWH",
"outro" "outro"
], ],
"dart.flutterSdkPath": ".fvm/versions/3.29.2", "dart.flutterSdkPath": ".fvm/versions/3.32.1",
"search.exclude": { "search.exclude": {
"**/.fvm": true "**/.fvm": true
}, },
@ -15,5 +15,13 @@
"**/.fvm": true "**/.fvm": true
}, },
"editor.detectIndentation": true, "editor.detectIndentation": true,
"dart.lineLength": 120 "dart.lineLength": 120,
"dart.analysisExcludedFolders": [
"build",
".dart_tool",
".fvm",
"packages",
"lib/jellyfin",
"lib/l10n"
]
} }

65
DEVELOPEMENT.md Normal file
View file

@ -0,0 +1,65 @@
# 🚀 Fladder Dev Setup
## 🔧 Requirements
Ensure the following tools are installed:
- [Flutter SDK](https://flutter.dev/docs/get-started/install) (latest stable)
- [Android Studio](https://developer.android.com/studio) (for Android development and emulators)
- [VS Code](https://code.visualstudio.com/) with:
- Flutter extension
- Dart extension
Verify your Flutter setup with:
```bash
flutter doctor
```
## 🚀 Quick Start
```bash
# Clone the repository
git clone https://github.com/DonutWare/Fladder.git
cd Fladder
# Install dependencies
flutter pub get
```
## 🐧 Linux Dependencies
If you're on **Linux**, install the `mpv` dependency:
```bash
sudo apt install libmpv-dev
```
## 🛠️ Running the App
1. **Connect a device** or launch an emulator.
2. In VS Code:
- Select the target device (bottom right corner).
- Press `F5` or go to **Run > Start Debugging**.
- If prompted, select **"Run Anyway"**.
## ⚙️ Code Generation
Generate build files (e.g., for `json_serializable`, `freezed`, etc.):
```bash
flutter pub run build_runner build
```
> Tip: Use `watch` for continuous builds during development:
```bash
flutter pub run build_runner watch
```
Update localization definitions:
```bash
flutter gen-l10n
```
## 🌐 Using a demo Server
You can use a fake server from Jellyfin.
https://demo.jellyfin.org/stable/web/

View file

@ -16,6 +16,7 @@ Platform-specific installation instructions can be found in this document.
- [iOS](#iosipados) - [iOS](#iosipados)
- [Sideloadly](#sideloadly) - [Sideloadly](#sideloadly)
- [Docker](#docker) - [Docker](#docker)
- [Web](#web)
## Windows ## Windows
@ -140,3 +141,10 @@ Run `docker-compose up -d` to start the container. It will be available on `http
> [!TIP] > [!TIP]
> We recommend changing the `BASE_URL` environment variable to the URL you use to access Jellyfin, as this will skip entering it when you load the web UI. > We recommend changing the `BASE_URL` environment variable to the URL you use to access Jellyfin, as this will skip entering it when you load the web UI.
## Web
You can also manually copy the web .zip build to any static file server such as Nginx, Caddy, or Apache
> [!TIP]
> You can preconfigure Fladder by placing a config file in [assets/config/config.json](https://github.com/DonutWare/Fladder/blob/develop/config/config.json)

View file

@ -53,11 +53,14 @@
<details close> <details close>
<summary>Mobile</summary> <summary>Mobile</summary>
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Dashboard.png?raw=true" alt="Fladder" width="200"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Dashboard.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Details.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Details_2.png?raw=true" alt="Fladder" width="200"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Details_2.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Favourites.png?raw=true" alt="Fladder" width="200"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Favourites.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Library.png?raw=true" alt="Fladder" width="200"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Library.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Library_Search.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Resume_Tab.png?raw=true" alt="Fladder" width="200"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Resume_Tab.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Sync.png?raw=true" alt="Fladder" width="200"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Sync.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Settings.png?raw=true" alt="Fladder" width="200">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Player.png?raw=true" alt="Fladder" width="1280"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Mobile/Player.png?raw=true" alt="Fladder" width="1280">
</details> </details>
@ -65,8 +68,14 @@
<summary>Tablet</summary> <summary>Tablet</summary>
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Dashboard.png?raw=true" alt="Fladder" width="1280"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Dashboard.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Details.png?raw=true" alt="Fladder" width="1280"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Details.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Details_2.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Favourites.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Library.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Library_Search.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Resume_Tab.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Sync.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Settings.png?raw=true" alt="Fladder" width="1280"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Settings.png?raw=true" alt="Fladder" width="1280">
<img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Sync.png?raw=true" alt="Fladder" width="1280"> <img src="https://github.com/DonutWare/Fladder/blob/develop/assets/marketing/screenshots/Tablet/Player.png?raw=true" alt="Fladder" width="1280">
</details> </details>
Web/Desktop [try out the web build!](https://DonutWare.github.io/Fladder) Web/Desktop [try out the web build!](https://DonutWare.github.io/Fladder)

View file

@ -3,12 +3,15 @@ include: package:lints/recommended.yaml
analyzer: analyzer:
exclude: exclude:
- build/** - build/**
- .dart_tool/**
- .fvm/**
- pubspec.yaml
- packages/**
- lib/jellyfin/** - lib/jellyfin/**
- lib/**/**.g.dart - lib/**/**.g.dart
- lib/**/**.freezed.dart - lib/**/**.freezed.dart
- lib/**/**.mapped.dart - lib/**/**.mapped.dart
- packages/** - lib/l10n/**
- pubspec.yaml
strong-mode: strong-mode:
implicit-casts: false implicit-casts: false
implicit-dynamic: false implicit-dynamic: false

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 732 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 930 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 4.3 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before After
Before After

View file

@ -27,6 +27,9 @@ targets:
- "**/**_page.dart" - "**/**_page.dart"
freezed|freezed: freezed|freezed:
options: options:
copy_with: false
equal: false
tostring:
generate_for: generate_for:
- "**/**.f.dart" - "**/**.f.dart"
- "**/**.g.dart" - "**/**.g.dart"
@ -34,7 +37,7 @@ targets:
options: options:
ignoreNull: true ignoreNull: true
discriminatorKey: type discriminatorKey: type
generateMethods: [decode, encode, copy, stringify] generateMethods: [copy]
chopper_generator: chopper_generator:
options: options:
header: "//Generated jellyfin api code" header: "//Generated jellyfin api code"

View file

@ -1,9 +1,18 @@
id: nl.jknaapen.fladder id: nl.jknaapen.fladder
runtime: org.gnome.Platform runtime: org.gnome.Platform
runtime-version: '46' runtime-version: '48'
sdk: org.gnome.Sdk sdk: org.gnome.Sdk
command: Fladder command: Fladder
add-extensions:
org.freedesktop.Platform.ffmpeg-full:
directory: lib/ffmpeg
version: '24.08'
add-ld-path: .
cleanup-commands:
- mkdir -p /app/lib/ffmpeg
finish-args: finish-args:
# X11/Wayland access # X11/Wayland access
- --share=ipc - --share=ipc
@ -15,6 +24,8 @@ finish-args:
- --share=network - --share=network
# File access for downloads/media # File access for downloads/media
- --filesystem=home - --filesystem=home
# File access for downloads temporary directory
- --filesystem=/tmp
# Allow access to PipeWire socket for mpv # Allow access to PipeWire socket for mpv
- --filesystem=xdg-run/pipewire-0:ro - --filesystem=xdg-run/pipewire-0:ro
# Hardware acceleration # Hardware acceleration
@ -61,42 +72,12 @@ modules:
sources: sources:
- type: git - type: git
url: https://github.com/FFmpeg/nv-codec-headers.git url: https://github.com/FFmpeg/nv-codec-headers.git
tag: n12.2.72.0 tag: n13.0.19.0
commit: c69278340ab1d5559c7d7bf0edf615dc33ddbba7 commit: e844e5b26f46bb77479f063029595293aa8f812d
x-checker-data: x-checker-data:
type: git type: git
tag-pattern: ^n([\d.]+)$ tag-pattern: ^n([\d.]+)$
- name: ffmpeg
config-opts:
- --enable-shared
- --disable-static
- --enable-gnutls
- --enable-pic
- --disable-doc
- --disable-programs
- --disable-encoders
- --disable-muxers
- --disable-devices
- --enable-vaapi
- --enable-cuvid
- --enable-libdav1d
- --enable-gpl
cleanup:
- /lib/pkgconfig
- /share
- /include
sources:
- type: git
url: https://github.com/FFmpeg/FFmpeg.git
tag: n7.1
commit: b08d7969c550a804a59511c7b83f2dd8cc0499b8
x-checker-data:
type: git
tag-pattern: ^n([\d.]+)$
- shared-modules/luajit/luajit.json - shared-modules/luajit/luajit.json
- name: libass - name: libass
config-opts: config-opts:
- --enable-shared - --enable-shared
@ -113,7 +94,6 @@ modules:
x-checker-data: x-checker-data:
type: git type: git
tag-pattern: ^([\d.]+)$ tag-pattern: ^([\d.]+)$
- name: uchardet - name: uchardet
buildsystem: cmake-ninja buildsystem: cmake-ninja
config-opts: config-opts:
@ -134,15 +114,12 @@ modules:
url: https://www.freedesktop.org/software/uchardet/releases/ url: https://www.freedesktop.org/software/uchardet/releases/
version-pattern: uchardet-(\d+\.\d+\.\d+)\.tar\.xz version-pattern: uchardet-(\d+\.\d+\.\d+)\.tar\.xz
url-template: https://www.freedesktop.org/software/uchardet/releases/uchardet-$version.tar.xz url-template: https://www.freedesktop.org/software/uchardet/releases/uchardet-$version.tar.xz
- name: libplacebo - name: libplacebo
buildsystem: meson buildsystem: meson
config-opts: config-opts:
- -Dvulkan=enabled - -Dvulkan=enabled
- -Dshaderc=enabled - -Dshaderc=enabled
cleanup: - --libdir=lib
- /include
- /lib/pkgconfig
sources: sources:
- type: git - type: git
url: https://code.videolan.org/videolan/libplacebo.git url: https://code.videolan.org/videolan/libplacebo.git
@ -151,49 +128,6 @@ modules:
x-checker-data: x-checker-data:
type: git type: git
tag-pattern: ^v([\d.]+)$ tag-pattern: ^v([\d.]+)$
modules:
- name: shaderc
buildsystem: cmake-ninja
builddir: true
config-opts:
- -DSHADERC_SKIP_COPYRIGHT_CHECK=ON
- -DSHADERC_SKIP_EXAMPLES=ON
- -DSHADERC_SKIP_TESTS=ON
- -DSPIRV_SKIP_EXECUTABLES=ON
- -DENABLE_GLSLANG_BINARIES=OFF
- -DCMAKE_BUILD_TYPE=Release
cleanup:
- /bin
- /include
- /lib/*.a
- /lib/cmake
- /lib/pkgconfig
sources:
- type: git
url: https://github.com/google/shaderc.git
tag: v2024.2
commit: 3ac03b8ad85a8e328a6182cddee8d05810bd5a2c
x-checker-data:
type: git
tag-pattern: ^v([\d.]+)$
- type: git
url: https://github.com/KhronosGroup/SPIRV-Tools.git
tag: v2023.2
commit: 44d72a9b36702f093dd20815561a56778b2d181e
dest: third_party/spirv-tools
- type: git
url: https://github.com/KhronosGroup/SPIRV-Headers.git
tag: sdk-1.3.250.1
commit: 268a061764ee69f09a477a695bf6a11ffe311b8d
dest: third_party/spirv-headers
- type: git
url: https://github.com/KhronosGroup/glslang.git
tag: 14.3.0
commit: fa9c3deb49e035a8abcabe366f26aac010f6cbfb
dest: third_party/glslang
x-checker-data:
type: git
tag-pattern: ^([\d.]+)$
- name: zenity - name: zenity
buildsystem: meson buildsystem: meson
@ -215,4 +149,4 @@ modules:
- install -Dm644 icons/fladder_icon.svg /app/share/icons/hicolor/scalable/apps/${FLATPAK_ID}.svg - install -Dm644 icons/fladder_icon.svg /app/share/icons/hicolor/scalable/apps/${FLATPAK_ID}.svg
sources: sources:
- type: dir - type: dir
path: .. path: ..

View file

@ -2,3 +2,5 @@ arb-dir: lib/l10n
template-arb-file: app_en.arb template-arb-file: app_en.arb
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart
nullable-getter: false nullable-getter: false
synthetic-package: false
output-dir: lib/l10n/generated

View file

@ -1284,8 +1284,62 @@
} }
} }
}, },
"settingsPlayerBufferSizeTitle": "حجم ذاكرة التخزين المؤقت للفيديو", "settingsPlayerBufferSizeTitle": "حجم التخزين المؤقت للفيديو",
"@settingsPlayerBufferSizeTitle": {}, "@settingsPlayerBufferSizeTitle": {},
"settingsPlayerBufferSizeDesc": "قم بتهيئة حجم ذاكرة التخزين المؤقت لتشغيل الفيديو، مما يحدد كمية البيانات التي يتم تحميلها في الذاكرة المؤقتة.", "settingsPlayerBufferSizeDesc": "قم بتهيئة حجم ذاكرة التخزين المؤقت لتشغيل الفيديو، مما يحدد كمية البيانات التي يتم تحميلها في الذاكرة المؤقتة.",
"@settingsPlayerBufferSizeDesc": {} "@settingsPlayerBufferSizeDesc": {},
"maxConcurrentDownloadsTitle": "الحد الأقصى للتنزيلات المتزامنة",
"@maxConcurrentDownloadsTitle": {},
"maxConcurrentDownloadsDesc": "يحدد العدد الأقصى للتنزيلات التي يمكن أن تتم في نفس الوقت. اضبطه على 0 لتعطيل الحد.",
"@maxConcurrentDownloadsDesc": {},
"playbackTrackSelection": "اختيار مسار التشغيل",
"@playbackTrackSelection": {},
"rememberSubtitleSelections": "تذكر اختيارات الترجمة بناءً على العنصر السابق",
"@rememberSubtitleSelections": {},
"rememberAudioSelections": "تذكر اختيارات الصوت بناءً على العنصر السابق",
"@rememberAudioSelections": {},
"rememberAudioSelectionsDesc": "حاول ضبط مسار الصوت ليكون الأقرب تطابقاً مع الفيديو الأخير.",
"@rememberAudioSelectionsDesc": {},
"rememberSubtitleSelectionsDesc": "حاول ضبط مسار الترجمة ليكون الأقرب تطابقاً مع الفيديو الأخير.",
"@rememberSubtitleSelectionsDesc": {},
"exitFladderTitle": "خروج فلادر",
"@exitFladderTitle": {},
"similarToLikedItem": "مشابه للعنصر المفضل",
"@similarToLikedItem": {},
"hasActorFromRecentlyPlayed": "يحتوي على ممثل من الأفلام المشغولة مؤخراً",
"@hasActorFromRecentlyPlayed": {},
"hasLikedActor": "يحتوي على ممثل مفضل",
"@hasLikedActor": {},
"hasDirectorFromRecentlyPlayed": "يحتوي على مخرج من الأفلام المشغولة مؤخراً",
"@hasDirectorFromRecentlyPlayed": {},
"hasLikedDirector": "يحتوي على مخرج مفضل",
"@hasLikedDirector": {},
"playbackTypeDirect": "مباشر",
"@playbackTypeDirect": {},
"latestReleases": "أحدث الإصدارات",
"@latestReleases": {},
"autoCheckForUpdates": "التحقق التلقائي من التحديثات",
"@autoCheckForUpdates": {},
"newUpdateFoundOnGithub": "تم اكتشاف تحديث جديد على Github",
"@newUpdateFoundOnGithub": {},
"similarToRecentlyPlayed": "مشابه لما تم تشغيله مؤخراً",
"@similarToRecentlyPlayed": {},
"playbackTypeOffline": "في وضع عدم الاتصال",
"@playbackTypeOffline": {},
"latest": "الأحدث",
"@latest": {},
"playbackTypeTranscode": "تحويل الصيغة",
"@playbackTypeTranscode": {},
"newReleaseFoundTitle": "تحديث {newRelease} متاح الآن!",
"@newReleaseFoundTitle": {
"placeholders": {
"newRelease": {
"type": "String"
}
}
},
"recommended": "الموصى به",
"@recommended": {},
"playbackType": "نوع التشغيل",
"@playbackType": {}
} }

1345
lib/l10n/app_cs.arb Normal file

File diff suppressed because it is too large Load diff

View file

@ -1290,5 +1290,69 @@
"example": "1" "example": "1"
} }
} }
} },
"settingsPlayerBufferSizeTitle": "Videopuffergröße",
"@settingsPlayerBufferSizeTitle": {},
"settingsPlayerBufferSizeDesc": "Konfigurieren Sie die Puffergröße für die Videowiedergabe und legen Sie fest, wie viele Daten in den Cache geladen werden.",
"@settingsPlayerBufferSizeDesc": {},
"exitFladderTitle": "Fladder Beenden",
"@exitFladderTitle": {},
"maxConcurrentDownloadsTitle": "Maximale Anzahl gleichzeitiger Downloads",
"@maxConcurrentDownloadsTitle": {},
"maxConcurrentDownloadsDesc": "Legt die maximale Anzahl gleichzeitig laufender Downloads fest. Setzen Sie den Wert auf 0, um die Begrenzung zu deaktivieren.",
"@maxConcurrentDownloadsDesc": {},
"rememberSubtitleSelections": "Untertitelspur basierend auf vorherigem Element einstellen",
"@rememberSubtitleSelections": {},
"rememberAudioSelections": "Audiospur basierend auf vorherigem Element einstellen",
"@rememberAudioSelections": {},
"similarToRecentlyPlayed": "Ähnlich wie kürzlich gespielt",
"@similarToRecentlyPlayed": {},
"similarToLikedItem": "Ähnlich wie der gewünschte Artikel",
"@similarToLikedItem": {},
"hasActorFromRecentlyPlayed": "Hat Schauspieler aus vor kurzem gespielt",
"@hasActorFromRecentlyPlayed": {},
"hasLikedActor": "Hat Schauspieler gemocht",
"@hasLikedActor": {},
"latest": "Letzte",
"@latest": {},
"recommended": "Empfohlen",
"@recommended": {},
"hasLikedDirector": "Hat Regisseur gemocht",
"@hasLikedDirector": {},
"hasDirectorFromRecentlyPlayed": "Hat Regisseur von vor kurzem gespielt",
"@hasDirectorFromRecentlyPlayed": {},
"playbackType": "Wiedergabetyp",
"@playbackType": {},
"playbackTypeDirect": "Direkt",
"@playbackTypeDirect": {},
"playbackTypeTranscode": "Transkodieren",
"@playbackTypeTranscode": {},
"playbackTypeOffline": "Offline",
"@playbackTypeOffline": {},
"latestReleases": "Aktuelle Veröffentlichungen",
"@latestReleases": {},
"newReleaseFoundTitle": "Update {newRelease} verfügbar!",
"@newReleaseFoundTitle": {
"placeholders": {
"newRelease": {
"type": "String"
}
}
},
"autoCheckForUpdates": "Regelmäßig nach Updates suchen",
"@autoCheckForUpdates": {},
"newUpdateFoundOnGithub": "Habe ein neues Update auf Github gefunden",
"@newUpdateFoundOnGithub": {},
"rememberSubtitleSelectionsDesc": "Versuchen Sie, die Untertitelspur so einzustellen, dass sie dem letzten Video am nächsten kommt.",
"@rememberSubtitleSelectionsDesc": {},
"rememberAudioSelectionsDesc": "Versuchen Sie, die Audiospur so einzustellen, dass sie dem letzten Video am nächsten kommt.",
"@rememberAudioSelectionsDesc": {},
"playbackTrackSelection": "Auswahl der Wiedergabespur",
"@playbackTrackSelection": {},
"enableBackgroundPostersDesc": "Zeigen Sie zufällige Poster in den entsprechenden Bildschirmen",
"@enableBackgroundPostersDesc": {},
"settingsEnableOsMediaControlsDesc": "Ermöglicht die Wiedergabesteuerung mithilfe von Medientasten und zeigt die aktuell wiedergegebenen Medien im Betriebssystem an",
"@settingsEnableOsMediaControlsDesc": {},
"enableBackgroundPostersTitle": "Hintergrundbilder aktivieren",
"@enableBackgroundPostersTitle": {}
} }

View file

@ -721,6 +721,7 @@
"@settingsContinue": {}, "@settingsContinue": {},
"settingsEnableOsMediaControls": "Enable OS media controls", "settingsEnableOsMediaControls": "Enable OS media controls",
"@settingsEnableOsMediaControls": {}, "@settingsEnableOsMediaControls": {},
"settingsEnableOsMediaControlsDesc": "Allow for playback control using media-keys and show current playing media in OS",
"settingsHomeBannerDescription": "Display as a slideshow, carousel, or hide the banner", "settingsHomeBannerDescription": "Display as a slideshow, carousel, or hide the banner",
"@settingsHomeBannerDescription": {}, "@settingsHomeBannerDescription": {},
"settingsHomeBannerTitle": "Home banner", "settingsHomeBannerTitle": "Home banner",
@ -1189,6 +1190,7 @@
"segmentActionAskToSkip": "Ask to skip", "segmentActionAskToSkip": "Ask to skip",
"segmentActionSkip": "Skip", "segmentActionSkip": "Skip",
"loading": "Loading", "loading": "Loading",
"exitFladderTitle": "Exit Fladder",
"castAndCrew": "Cast & Crew", "castAndCrew": "Cast & Crew",
"guestActor": "{count, plural, other{Guest Actors} one{Guest Actor}}", "guestActor": "{count, plural, other{Guest Actors} one{Guest Actor}}",
"@guestActor": { "@guestActor": {
@ -1199,5 +1201,42 @@
"example": "1" "example": "1"
} }
} }
} },
"maxConcurrentDownloadsTitle": "Max concurrent downloads",
"maxConcurrentDownloadsDesc": "Sets the maximum number of downloads that can run at the same time. Set to 0 to disable the limit.",
"playbackTrackSelection": "Playback track selection",
"@playbackTrackSelection": {},
"rememberSubtitleSelections": "Set subtitle track based on previous item",
"@rememberSubtitleSelections": {},
"rememberAudioSelections": "Set audio track based on previous item",
"@rememberAudioSelections": {},
"rememberSubtitleSelectionsDesc": "Try to set the subtitle track to the closest match to the last video.",
"@rememberSubtitleSelectionsDesc": {},
"rememberAudioSelectionsDesc": "Try to set the audio track to the closest match to the last video.",
"@rememberAudioSelectionsDesc": {},
"similarToRecentlyPlayed": "Similar to recently played",
"similarToLikedItem": "Similar to liked item",
"hasDirectorFromRecentlyPlayed": "Has director from recently played",
"hasActorFromRecentlyPlayed": "Has actor from recently played",
"hasLikedDirector": "Has liked director",
"hasLikedActor": "Has liked actor",
"latest": "Latest",
"recommended": "Recommended",
"playbackType": "Playback type",
"playbackTypeDirect": "Direct",
"playbackTypeTranscode": "Transcode",
"playbackTypeOffline": "Offline",
"latestReleases": "Latest releases",
"autoCheckForUpdates": "Periodically check for updates",
"newReleaseFoundTitle": "Update {newRelease} available!",
"@newReleaseFoundTitle": {
"placeholders": {
"newRelease": {
"type": "String"
}
}
},
"newUpdateFoundOnGithub": "Found a new update on Github",
"enableBackgroundPostersTitle": "Enable background posters",
"enableBackgroundPostersDesc": "Show random posters in applicable screens"
} }

View file

@ -37,5 +37,33 @@
"appLockPasscode": "Codice", "appLockPasscode": "Codice",
"@appLockPasscode": {}, "@appLockPasscode": {},
"audio": "Audio", "audio": "Audio",
"@audio": {} "@audio": {},
"amoledBlack": "Amoled nero",
"@amoledBlack": {},
"backgroundBlur": "Sfocatura dello sfondo",
"@backgroundBlur": {},
"backgroundOpacity": "Opacità dello sfondo",
"@backgroundOpacity": {},
"biometricsFailedCheckAgain": "Errore biometrico. Controlla le impostazioni e riprova.",
"@biometricsFailedCheckAgain": {},
"bold": "Grassetto",
"@bold": {},
"cancel": "Cancellare",
"@cancel": {},
"change": "Modifica",
"@change": {},
"clear": "Leggere",
"@clear": {},
"clearAllSettings": "Cancella tutte le impostazioni",
"@clearAllSettings": {},
"clearAllSettingsQuestion": "Cancellare tutte le impostazioni?",
"@clearAllSettingsQuestion": {},
"clearChanges": "Cancella modifiche",
"@clearChanges": {},
"clearSelection": "Cancella selezione",
"@clearSelection": {},
"close": "Vicina",
"@close": {},
"code": "Codice",
"@code": {}
} }

1082
lib/l10n/app_mr.arb Normal file

File diff suppressed because it is too large Load diff

1265
lib/l10n/app_mt.arb Normal file

File diff suppressed because it is too large Load diff

View file

@ -1284,5 +1284,63 @@
} }
}, },
"castAndCrew": "Cast en crew", "castAndCrew": "Cast en crew",
"@castAndCrew": {} "@castAndCrew": {},
"settingsPlayerBufferSizeTitle": "Video buffer grootte",
"@settingsPlayerBufferSizeTitle": {},
"settingsPlayerBufferSizeDesc": "Configureer de buffergrootte voor het afspelen van video's en bepaal de hoeveelheid vooraf geladen data.",
"@settingsPlayerBufferSizeDesc": {},
"maxConcurrentDownloadsTitle": "Maximum gelijktijdige downloads",
"@maxConcurrentDownloadsTitle": {},
"maxConcurrentDownloadsDesc": "Stelt het maximumaantal downloads in dat tegelijkertijd kan worden uitgevoerd. Stel in op 0 om de limiet uit te schakelen.",
"@maxConcurrentDownloadsDesc": {},
"rememberSubtitleSelections": "Ondertitelspoor instellen op basis van vorig item",
"@rememberSubtitleSelections": {},
"rememberSubtitleSelectionsDesc": "Probeer het ondertitelspoor in te stellen op de best overeenkomende optie van de vorige video.",
"@rememberSubtitleSelectionsDesc": {},
"playbackTrackSelection": "Selectie van afspeeltrack",
"@playbackTrackSelection": {},
"rememberAudioSelections": "Audiotrack instellen op basis van vorig item",
"@rememberAudioSelections": {},
"rememberAudioSelectionsDesc": "Probeer de audiotrack in te stellen op de best overeenkomende optie van de vorige video.",
"@rememberAudioSelectionsDesc": {},
"hasLikedActor": "Heeft dezelfde favorite acteur",
"@hasLikedActor": {},
"hasDirectorFromRecentlyPlayed": "Heeft regisseur van recent afgespeeld",
"@hasDirectorFromRecentlyPlayed": {},
"similarToLikedItem": "Vergelijkbaar met favoriet",
"@similarToLikedItem": {},
"hasActorFromRecentlyPlayed": "Heeft acteur van recent afgespeeld",
"@hasActorFromRecentlyPlayed": {},
"hasLikedDirector": "Heeft dezelfde favoriete regisseur",
"@hasLikedDirector": {},
"recommended": "Aanbevolen",
"@recommended": {},
"similarToRecentlyPlayed": "Vergelijkbaar met recent afgespeeld",
"@similarToRecentlyPlayed": {},
"latest": "Laatste",
"@latest": {},
"exitFladderTitle": "Fladder sluiten",
"@exitFladderTitle": {},
"playbackTypeDirect": "Direct",
"@playbackTypeDirect": {},
"playbackTypeOffline": "Offline",
"@playbackTypeOffline": {},
"playbackType": "Afspeel type",
"@playbackType": {},
"playbackTypeTranscode": "Transcoderen",
"@playbackTypeTranscode": {},
"newUpdateFoundOnGithub": "Nieuwe update gevonden op Github",
"@newUpdateFoundOnGithub": {},
"autoCheckForUpdates": "Regelmatig controleren op updates",
"@autoCheckForUpdates": {},
"newReleaseFoundTitle": "Update {newRelease} beschikbaar!",
"@newReleaseFoundTitle": {
"placeholders": {
"newRelease": {
"type": "String"
}
}
},
"latestReleases": "Nieuwste releases",
"@latestReleases": {}
} }

View file

@ -1153,5 +1153,37 @@
"episodeUnaired": "Niewyemitowany", "episodeUnaired": "Niewyemitowany",
"@episodeUnaired": {}, "@episodeUnaired": {},
"episodeMissing": "Brakujący", "episodeMissing": "Brakujący",
"@episodeMissing": {} "@episodeMissing": {},
"settingsPlayerBufferSizeDesc": "Konfiguruje rozmiar bufora dla odtwarzania wideo, określając jak wiele danych jest załadowanych w pamięci cache.",
"@settingsPlayerBufferSizeDesc": {},
"settingsPlayerBufferSizeTitle": "Wielkość bufora wideo",
"@settingsPlayerBufferSizeTitle": {},
"refreshPopupContentMetadata": "Metadane są odświeżane na podstawie ustawień i usług internetowych włączonych w panelu administracyjnym.",
"@refreshPopupContentMetadata": {},
"libraryPageSizeDesc": "Ustawia ilość elementów do załadowania na stronie. 0 wyłącza stronicowanie.",
"@libraryPageSizeDesc": {},
"rating": "{count, plural, other{Ocen} many{Ocen} few{Oceny} one{Ocena}}",
"@rating": {
"description": "rating",
"placeholders": {
"count": {
"type": "int",
"example": "1"
}
}
},
"pathEditDesc": "Ta lokalizacja jest ustawiana dla wszystkich użytkowników, żadne dane zsynchronizowane dotychczas nie będą już dostępne, ale pozostaną na urządzeniu.",
"@pathEditDesc": {},
"syncRemoveDataDesc": "Usunąć zsynchronizowane dane? Ta operacja jest nieodwracalna w związku z czym konieczna będzie ponowna synchronizacja.",
"@syncRemoveDataDesc": {},
"writer": "{count, plural, other{Scenarzytów} one{Scenarzysta}}",
"@writer": {
"description": "writer",
"placeholders": {
"count": {
"type": "int",
"example": "1"
}
}
}
} }

View file

@ -1246,7 +1246,7 @@
"@layoutModeDual": {}, "@layoutModeDual": {},
"copiedToClipboard": "Copiado para a área de transferência", "copiedToClipboard": "Copiado para a área de transferência",
"@copiedToClipboard": {}, "@copiedToClipboard": {},
"internetStreamingQualityDesc": "Qualidade máxima de streaming pela internet (celular)", "internetStreamingQualityDesc": "Qualidade máxima de streaming por dados móveis (celular)",
"@internetStreamingQualityDesc": {}, "@internetStreamingQualityDesc": {},
"homeStreamingQualityTitle": "Qualidade na rede local", "homeStreamingQualityTitle": "Qualidade na rede local",
"@homeStreamingQualityTitle": {}, "@homeStreamingQualityTitle": {},
@ -1289,5 +1289,67 @@
"example": "1" "example": "1"
} }
} }
} },
"hasLikedDirector": "Tem diretor favoritado",
"@hasLikedDirector": {},
"latest": "Mais recente",
"@latest": {},
"recommended": "Recomendado",
"@recommended": {},
"playbackType": "Tipo de reprodução",
"@playbackType": {},
"playbackTypeDirect": "Direta",
"@playbackTypeDirect": {},
"latestReleases": "Lançamentos mais recentes",
"@latestReleases": {},
"newReleaseFoundTitle": "Atualização {newRelease} disponível!",
"@newReleaseFoundTitle": {
"placeholders": {
"newRelease": {
"type": "String"
}
}
},
"newUpdateFoundOnGithub": "Nova atualização encontrada no Github",
"@newUpdateFoundOnGithub": {},
"enableBackgroundPostersDesc": "Mostra imagens de fundo aleatórias em telas aplicáveis",
"@enableBackgroundPostersDesc": {},
"hasActorFromRecentlyPlayed": "Tem ator de algo recentemente assistido",
"@hasActorFromRecentlyPlayed": {},
"enableBackgroundPostersTitle": "Ativar imagens de fundo",
"@enableBackgroundPostersTitle": {},
"settingsPlayerBufferSizeDesc": "Configure o tamanho do buffer para reprodução de vídeo, determinando a quantidade de dados que é carregada no cache.",
"@settingsPlayerBufferSizeDesc": {},
"playbackTypeOffline": "Offline",
"@playbackTypeOffline": {},
"autoCheckForUpdates": "Verificar periodicamente se há atualizações",
"@autoCheckForUpdates": {},
"hasLikedActor": "Tem ator favoritado",
"@hasLikedActor": {},
"exitFladderTitle": "Sair do Fladder",
"@exitFladderTitle": {},
"maxConcurrentDownloadsTitle": "Máximo de downloads simultâneos",
"@maxConcurrentDownloadsTitle": {},
"maxConcurrentDownloadsDesc": "Define o número máximo de downloads que podem ser executados ao mesmo tempo. Defina como 0 para desativar o limite.",
"@maxConcurrentDownloadsDesc": {},
"playbackTrackSelection": "Seleção de faixa",
"@playbackTrackSelection": {},
"rememberSubtitleSelections": "Definir a legenda com base no item anterior",
"@rememberSubtitleSelections": {},
"rememberAudioSelections": "Definir o áudio com base no item anterior",
"@rememberAudioSelections": {},
"rememberSubtitleSelectionsDesc": "Tentar definir a faixa de áudio com a correspondência mais próxima do último vídeo.",
"@rememberSubtitleSelectionsDesc": {},
"rememberAudioSelectionsDesc": "Tentar definir a legenda com a correspondência mais próxima do último vídeo.",
"@rememberAudioSelectionsDesc": {},
"similarToRecentlyPlayed": "Semelhante aos recentemente assistidos",
"@similarToRecentlyPlayed": {},
"similarToLikedItem": "Semelhante a itens curtidos",
"@similarToLikedItem": {},
"hasDirectorFromRecentlyPlayed": "Tem diretor de algo recentemente assistido",
"@hasDirectorFromRecentlyPlayed": {},
"settingsEnableOsMediaControlsDesc": "Permitir o controle da reprodução usando as teclas de mídia e mostrar a mídia atual em reprodução no OS",
"@settingsEnableOsMediaControlsDesc": {},
"settingsPlayerBufferSizeTitle": "Tamanho do buffer de vídeo",
"@settingsPlayerBufferSizeTitle": {}
} }

129
lib/l10n/app_ro.arb Normal file
View file

@ -0,0 +1,129 @@
{
"code": "Cod",
"@code": {},
"chapter": "{count, plural, other{Capitole} one{Capitol}}",
"@chapter": {
"description": "chapter",
"placeholders": {
"count": {
"type": "int",
"example": "1"
}
}
},
"continuePage": "Continuă - pagina {page}",
"@continuePage": {
"description": "Continue - page 1",
"placeholders": {
"page": {
"type": "int"
}
}
},
"nativeName": "Română",
"@nativeName": {},
"accept": "Acceptă",
"@accept": {},
"addAsFavorite": "Adaugă la favorite",
"@addAsFavorite": {},
"addToPlaylist": "Adaugă în lista de redare",
"@addToPlaylist": {},
"advanced": "Avansat",
"@advanced": {},
"all": "Toate",
"@all": {},
"amoledBlack": "Negru Amoled",
"@amoledBlack": {},
"appLockAutoLogin": "Conectare automată",
"@appLockAutoLogin": {},
"appLockPasscode": "Pinul de acces",
"@appLockPasscode": {},
"backgroundOpacity": "Opacitatea fundalului",
"@backgroundOpacity": {},
"bold": "Îngroșat",
"@bold": {},
"cancel": "Anulați",
"@cancel": {},
"change": "Schimbă",
"@change": {},
"clear": "",
"@clear": {},
"combined": "Combinat",
"@combined": {},
"controls": "Controale",
"@controls": {},
"dashboardContinueListening": "Continua Ascultarea",
"@dashboardContinueListening": {},
"dashboardContinueWatching": "Continua Vizionare",
"@dashboardContinueWatching": {},
"dashboardRecentlyAdded": "Adăugat recent în {name}",
"@dashboardRecentlyAdded": {
"description": "Recently added on home screen",
"placeholders": {
"name": {
"type": "String"
}
}
},
"dateAdded": "Data adăugării",
"@dateAdded": {},
"datePlayed": "Data redării",
"@datePlayed": {},
"days": "Zile",
"@days": {},
"about": "Despre",
"@about": {},
"close": "Închide",
"@close": {},
"dashboardContinueReading": "Continua Cititul",
"@dashboardContinueReading": {},
"autoPlay": "Redare automată",
"@autoPlay": {},
"active": "Activ",
"@active": {},
"appLockBiometrics": "Biometrice",
"@appLockBiometrics": {},
"actor": "{count, plural, other{Actori} one{Actor}}",
"@actor": {
"description": "actor",
"placeholders": {
"count": {
"type": "int",
"example": "1"
}
}
},
"appLockTitle": "Selectează modul de conectare pentru {userName}",
"@appLockTitle": {
"description": "Pop-up to pick a login method",
"placeholders": {
"userName": {
"type": "String"
}
}
},
"dateLastContentAdded": "Data ultimului conținut adăugat",
"@dateLastContentAdded": {},
"biometricsFailedCheckAgain": "Biometrice eșuate. Verificați setările și încercați din nou.",
"@biometricsFailedCheckAgain": {},
"communityRating": "Evaluarea comunității",
"@communityRating": {},
"dashboardContinue": "Continua",
"@dashboardContinue": {},
"dashboard": "Bord",
"@dashboard": {},
"delete": "Șterge",
"@delete": {},
"color": "Culoare",
"@color": {},
"addToCollection": "Adaugă la colecție",
"@addToCollection": {},
"audio": "Audio",
"@audio": {},
"backgroundBlur": "Blur de fundal",
"@backgroundBlur": {},
"collectionFolder": "Dosar de colectare",
"@collectionFolder": {},
"nextUp": "Următorul",
"@nextUp": {}
}

1351
lib/l10n/app_ru.arb Normal file

File diff suppressed because it is too large Load diff

137
lib/l10n/app_sk.arb Normal file
View file

@ -0,0 +1,137 @@
{
"active": "Aktívne",
"@active": {},
"amoledBlack": "Amoled čierna",
"@amoledBlack": {},
"actor": "{count, plural, other{Herci} one{Herec/Herečka}}",
"@actor": {
"description": "actor",
"placeholders": {
"count": {
"type": "int",
"example": "1"
}
}
},
"addAsFavorite": "Pridať ako obľúbené",
"@addAsFavorite": {},
"addToCollection": "Pridať do kolekcie",
"@addToCollection": {},
"addToPlaylist": "Pridať do playlistu",
"@addToPlaylist": {},
"advanced": "Pokročilé",
"@advanced": {},
"all": "Všetko",
"@all": {},
"appLockAutoLogin": "Automatické prihlásenie",
"@appLockAutoLogin": {},
"appLockBiometrics": "Biometrika",
"@appLockBiometrics": {},
"appLockPasscode": "Prístupový kód",
"@appLockPasscode": {},
"accept": "Prijať",
"@accept": {},
"about": "O appke",
"@about": {},
"appLockTitle": "Nastavenie metódy prihlásenia pre {userName}",
"@appLockTitle": {
"description": "Pop-up to pick a login method",
"placeholders": {
"userName": {
"type": "String"
}
}
},
"ascending": "Vzostupne",
"@ascending": {},
"audio": "Zvuk",
"@audio": {},
"autoPlay": "Automatické prehrávanie",
"@autoPlay": {},
"backgroundBlur": "Rozmazanie pozadia",
"@backgroundBlur": {},
"backgroundOpacity": "Priehľadnosť pozadia",
"@backgroundOpacity": {},
"biometricsFailedCheckAgain": "Biometria zlyhala. Skontrolujte nastavenia a skúste to znova.",
"@biometricsFailedCheckAgain": {},
"bold": "Hrubý",
"@bold": {},
"cancel": "Zrušiť",
"@cancel": {},
"change": "Zmeniť",
"@change": {},
"clearAllSettings": "Vyčistiť všetky nastavenia",
"@clearAllSettings": {},
"clearAllSettingsQuestion": "Vynulovať všetky nastavenia?",
"@clearAllSettingsQuestion": {},
"clearChanges": "Vynulovanie zmien",
"@clearChanges": {},
"clearSelection": "Vynulovať výber",
"@clearSelection": {},
"close": "Zatvoriť",
"@close": {},
"code": "Kód",
"@code": {},
"collectionFolder": "Zložka kolekcie",
"@collectionFolder": {},
"color": "Farba",
"@color": {},
"combined": "Kombinované",
"@combined": {},
"communityRating": "Hodnotenie komunity",
"@communityRating": {},
"continuePage": "Pokračovať - strana {page}",
"@continuePage": {
"description": "Continue - page 1",
"placeholders": {
"page": {
"type": "int"
}
}
},
"controls": "Ovládanie",
"@controls": {},
"dashboard": "Ovládací panel",
"@dashboard": {},
"dashboardContinue": "Pokračovať",
"@dashboardContinue": {},
"dashboardContinueListening": "Pokračovať v počúvaní",
"@dashboardContinueListening": {},
"dashboardContinueReading": "Pokračovať v čítaní",
"@dashboardContinueReading": {},
"dashboardContinueWatching": "Pokračovať v sledovaní",
"@dashboardContinueWatching": {},
"nextUp": "Ďalej",
"@nextUp": {},
"dashboardRecentlyAdded": "Nedávno pridané v {name}",
"@dashboardRecentlyAdded": {
"description": "Recently added on home screen",
"placeholders": {
"name": {
"type": "String"
}
}
},
"dateAdded": "Dátum pridania",
"@dateAdded": {},
"dateLastContentAdded": "Dátum posledného pridania obsahu",
"@dateLastContentAdded": {},
"datePlayed": "Dátum prehrania",
"@datePlayed": {},
"days": "Dni",
"@days": {},
"delete": "Odstrániť",
"@delete": {},
"clear": "Vyčistiť",
"@clear": {},
"chapter": "{count, plural, other{Chapters} one{Chapter}}",
"@chapter": {
"description": "chapter",
"placeholders": {
"count": {
"type": "int",
"example": "1"
}
}
}
}

View file

@ -76,7 +76,7 @@
"@mediaSegmentRecap": {}, "@mediaSegmentRecap": {},
"addAsFavorite": "பிடித்ததாகச் சேர்க்கவும்", "addAsFavorite": "பிடித்ததாகச் சேர்க்கவும்",
"@addAsFavorite": {}, "@addAsFavorite": {},
"actor": "{count, plural, other{கூத்தர்கள்} one {கூத்தர்}}", "actor": "{count, plural, other{நடிகர்கள்} one {நடிகர்}}",
"@actor": { "@actor": {
"description": "actor", "description": "actor",
"placeholders": { "placeholders": {
@ -94,7 +94,7 @@
"@advanced": {}, "@advanced": {},
"all": "அனைத்தும்", "all": "அனைத்தும்",
"@all": {}, "@all": {},
"appLockAutoLogin": "ஆட்டோ உள்நுழைவு", "appLockAutoLogin": "தானாக உள்நுழை",
"@appLockAutoLogin": {}, "@appLockAutoLogin": {},
"appLockBiometrics": "பயோமெட்ரிக்ச்", "appLockBiometrics": "பயோமெட்ரிக்ச்",
"@appLockBiometrics": {}, "@appLockBiometrics": {},
@ -111,7 +111,7 @@
}, },
"ascending": "ஏறுதல்", "ascending": "ஏறுதல்",
"@ascending": {}, "@ascending": {},
"audio": "ஒலி தேர்வு", "audio": "ஆடியோ",
"@audio": {}, "@audio": {},
"autoPlay": "தானாக விளையாடும்", "autoPlay": "தானாக விளையாடும்",
"@autoPlay": {}, "@autoPlay": {},
@ -1209,5 +1209,83 @@
"errorLogs": "பிழை பதிவுகள்", "errorLogs": "பிழை பதிவுகள்",
"@errorLogs": {}, "@errorLogs": {},
"external": "வெளிப்புற", "external": "வெளிப்புற",
"@external": {} "@external": {},
"settingsLayoutSizesTitle": "தளவமைப்பு அளவுகள்",
"@settingsLayoutSizesTitle": {},
"settingsLayoutSizesDesc": "சாளர அளவின் அடிப்படையில் பயன்பாடு எந்த தளவமைப்பு அளவுகளைப் பயன்படுத்தலாம் என்பதைத் தேர்வுசெய்க",
"@settingsLayoutSizesDesc": {},
"settingsLayoutModesTitle": "தளவமைப்பு முறைகள்",
"@settingsLayoutModesTitle": {},
"tablet": "டேப்லெட்",
"@tablet": {},
"mediaSegmentActions": "ஊடக பிரிவு செயல்கள்",
"@mediaSegmentActions": {},
"segmentActionNone": "எதுவுமில்லை",
"@segmentActionNone": {},
"segmentActionAskToSkip": "தவிர்க்கச் சொல்லுங்கள்",
"@segmentActionAskToSkip": {},
"segmentActionSkip": "தவிர்",
"@segmentActionSkip": {},
"castAndCrew": "நடிகர்கள் & குழுவினர்",
"@castAndCrew": {},
"downloadFile": "பதிவிறக்கம் {type}",
"@downloadFile": {
"placeholders": {
"type": {
"type": "String"
}
}
},
"copyStreamUrl": "ச்ட்ரீம் முகவரி ஐ நகலெடுக்கவும்",
"@copyStreamUrl": {},
"copiedToClipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது",
"@copiedToClipboard": {},
"internetStreamingQualityTitle": "இணைய தகுதி",
"@internetStreamingQualityTitle": {},
"internetStreamingQualityDesc": "இணையத்தில் அதிகபட்ச ச்ட்ரீமிங் தகுதி (மொபைல்)",
"@internetStreamingQualityDesc": {},
"homeStreamingQualityTitle": "வீட்டின் தகுதி",
"@homeStreamingQualityTitle": {},
"homeStreamingQualityDesc": "வீட்டு நெட்வொர்க்குடன் இணைக்கப்படும்போது அதிகபட்ச ச்ட்ரீமிங் தகுதி",
"@homeStreamingQualityDesc": {},
"qualityOptionsTitle": "தரமான விருப்பங்கள்",
"@qualityOptionsTitle": {},
"loading": "ஏற்றுகிறது",
"@loading": {},
"episodeAvailable": "கிடைக்கிறது",
"@episodeAvailable": {},
"episodeUnaired": "UNIRED",
"@episodeUnaired": {},
"episodeMissing": "இல்லை",
"@episodeMissing": {},
"settingsPlayerBufferSizeTitle": "வீடியோ இடையக அளவு",
"@settingsPlayerBufferSizeTitle": {},
"settingsPlayerBufferSizeDesc": "வீடியோ பிளேபேக்கிற்கான இடையக அளவை உள்ளமைக்கவும், தற்காலிக சேமிப்பில் எவ்வளவு தரவு ஏற்றப்படுகிறது என்பதை தீர்மானிக்கவும்.",
"@settingsPlayerBufferSizeDesc": {},
"settingsLayoutModesDesc": "பயன்பாடு ஒற்றை அல்லது இரட்டை-பேனல் தளவமைப்புகளைப் பயன்படுத்தலாமா என்பதைக் கட்டுப்படுத்தவும்",
"@settingsLayoutModesDesc": {},
"phone": "தொலைபேசி",
"@phone": {},
"desktop": "டெச்க்டாப்",
"@desktop": {},
"layoutModeSingle": "ஒற்றை",
"@layoutModeSingle": {},
"layoutModeDual": "இருமம்",
"@layoutModeDual": {},
"qualityOptionsOriginal": "அசல்",
"@qualityOptionsOriginal": {},
"qualityOptionsAuto": "தானி",
"@qualityOptionsAuto": {},
"version": "பதிப்பு",
"@version": {},
"guestActor": "{count, plural, other{கௌரவ நடிகர்கள்} one{கௌரவ நடிகர்}}",
"@guestActor": {
"description": "Guest actors",
"placeholders": {
"count": {
"type": "int",
"example": "1"
}
}
}
} }

View file

@ -1295,5 +1295,65 @@
"settingsPlayerBufferSizeTitle": "Розмір буфера відео", "settingsPlayerBufferSizeTitle": "Розмір буфера відео",
"@settingsPlayerBufferSizeTitle": {}, "@settingsPlayerBufferSizeTitle": {},
"settingsPlayerBufferSizeDesc": "Налаштуйте розмір буфера для відтворення відео, визначивши, скільки даних буде завантажено в кеш.", "settingsPlayerBufferSizeDesc": "Налаштуйте розмір буфера для відтворення відео, визначивши, скільки даних буде завантажено в кеш.",
"@settingsPlayerBufferSizeDesc": {} "@settingsPlayerBufferSizeDesc": {},
"maxConcurrentDownloadsTitle": "Максимум одночасних завантажень",
"@maxConcurrentDownloadsTitle": {},
"maxConcurrentDownloadsDesc": "Встановлює максимальну кількість завантажень, які можуть виконуватися одночасно. Встановіть значення 0, щоб вимкнути обмеження.",
"@maxConcurrentDownloadsDesc": {},
"playbackTrackSelection": "Вибір доріжки відтворення",
"@playbackTrackSelection": {},
"rememberSubtitleSelectionsDesc": "Спробувати встановити доріжку субтитрів, яка найкраще відповідає останньому відео.",
"@rememberSubtitleSelectionsDesc": {},
"rememberAudioSelections": "Встановити аудіодоріжку на основі попереднього елемента",
"@rememberAudioSelections": {},
"rememberAudioSelectionsDesc": "Спробувати встановити аудіодоріжку, яка найкраще відповідає останньому відео.",
"@rememberAudioSelectionsDesc": {},
"rememberSubtitleSelections": "Встановити доріжку субтитрів на основі попереднього елемента",
"@rememberSubtitleSelections": {},
"similarToRecentlyPlayed": "Схоже на нещодавно відтворене",
"@similarToRecentlyPlayed": {},
"similarToLikedItem": "Схоже на вподобаний елемент",
"@similarToLikedItem": {},
"hasActorFromRecentlyPlayed": "Має актора з нещодавно відтвореного",
"@hasActorFromRecentlyPlayed": {},
"latest": "Останнє",
"@latest": {},
"hasLikedDirector": "Має вподобаного режисера",
"@hasLikedDirector": {},
"hasLikedActor": "Має вподобаного актора",
"@hasLikedActor": {},
"recommended": "Рекомендовано",
"@recommended": {},
"hasDirectorFromRecentlyPlayed": "Має режисера з нещодавно відтвореного",
"@hasDirectorFromRecentlyPlayed": {},
"exitFladderTitle": "Вийти з Fladder",
"@exitFladderTitle": {},
"playbackTypeDirect": "Пряме",
"@playbackTypeDirect": {},
"playbackType": "Тип відтворення",
"@playbackType": {},
"playbackTypeTranscode": "Перекодування",
"@playbackTypeTranscode": {},
"playbackTypeOffline": "Офлайн",
"@playbackTypeOffline": {},
"latestReleases": "Останні релізи",
"@latestReleases": {},
"autoCheckForUpdates": "Періодично перевіряти наявність оновлень",
"@autoCheckForUpdates": {},
"newReleaseFoundTitle": "Доступне оновлення {newRelease}!",
"@newReleaseFoundTitle": {
"placeholders": {
"newRelease": {
"type": "String"
}
}
},
"newUpdateFoundOnGithub": "Знайдено нове оновлення на Github",
"@newUpdateFoundOnGithub": {},
"enableBackgroundPostersTitle": "Увімкнути фонові постери",
"@enableBackgroundPostersTitle": {},
"settingsEnableOsMediaControlsDesc": "Дозволяє керувати відтворенням за допомогою медіа-клавіш та показувати поточний відтворюваний медіафайл в ОС",
"@settingsEnableOsMediaControlsDesc": {},
"enableBackgroundPostersDesc": "Показувати випадкові постери на відповідних екранах",
"@enableBackgroundPostersDesc": {}
} }

View file

@ -892,23 +892,23 @@
"@video": {}, "@video": {},
"videoScaling": "视频缩放", "videoScaling": "视频缩放",
"@videoScaling": {}, "@videoScaling": {},
"videoScalingContain": "包含", "videoScalingContain": "适应窗口",
"@videoScalingContain": {}, "@videoScalingContain": {},
"videoScalingCover": "覆盖", "videoScalingCover": "覆盖填充",
"@videoScalingCover": {}, "@videoScalingCover": {},
"videoScalingFill": "填充", "videoScalingFill": "拉伸填充",
"@videoScalingFill": {}, "@videoScalingFill": {},
"videoScalingFillScreenDesc": "填充导航栏和状态栏", "videoScalingFillScreenDesc": "延伸至导航栏和状态栏区域",
"@videoScalingFillScreenDesc": {}, "@videoScalingFillScreenDesc": {},
"videoScalingFillScreenNotif": "全屏覆盖视频适应,在水平旋转中", "videoScalingFillScreenNotif": "全屏模式将覆盖视频适配设置",
"@videoScalingFillScreenNotif": {}, "@videoScalingFillScreenNotif": {},
"videoScalingFillScreenTitle": "填满屏幕", "videoScalingFillScreenTitle": "全屏填充",
"@videoScalingFillScreenTitle": {}, "@videoScalingFillScreenTitle": {},
"videoScalingFitHeight": "适应高度", "videoScalingFitHeight": "适应高度",
"@videoScalingFitHeight": {}, "@videoScalingFitHeight": {},
"videoScalingFitWidth": "适应宽度", "videoScalingFitWidth": "适应宽度",
"@videoScalingFitWidth": {}, "@videoScalingFitWidth": {},
"videoScalingScaleDown": "缩小", "videoScalingScaleDown": "自适应缩小",
"@videoScalingScaleDown": {}, "@videoScalingScaleDown": {},
"viewPhotos": "查看照片", "viewPhotos": "查看照片",
"@viewPhotos": {}, "@viewPhotos": {},
@ -946,13 +946,13 @@
} }
} }
}, },
"syncStatusComplete": "完成", "syncStatusComplete": "同步完成",
"@syncStatusComplete": {}, "@syncStatusComplete": {},
"syncNoFolderSetup": "未设置同步文件夹", "syncNoFolderSetup": "未设置同步文件夹",
"@syncNoFolderSetup": {}, "@syncNoFolderSetup": {},
"syncStatusNotFound": "未找到", "syncStatusNotFound": "资源未找到",
"@syncStatusNotFound": {}, "@syncStatusNotFound": {},
"syncStatusPaused": "已暂停", "syncStatusPaused": "同步已暂停",
"@syncStatusPaused": {}, "@syncStatusPaused": {},
"syncOverlayDeleting": "正在删除已同步的项目", "syncOverlayDeleting": "正在删除已同步的项目",
"@syncOverlayDeleting": {}, "@syncOverlayDeleting": {},
@ -964,7 +964,7 @@
} }
} }
}, },
"syncStatusCanceled": "已取消", "syncStatusCanceled": "用户已取消",
"@syncStatusCanceled": {}, "@syncStatusCanceled": {},
"settingsHomeBannerInformationDesc": "主页横幅中显示的信息", "settingsHomeBannerInformationDesc": "主页横幅中显示的信息",
"@settingsHomeBannerInformationDesc": {}, "@settingsHomeBannerInformationDesc": {},
@ -972,21 +972,21 @@
"@settingsHomeBannerInformationTitle": {}, "@settingsHomeBannerInformationTitle": {},
"syncStatusEnqueued": "已添加到队列", "syncStatusEnqueued": "已添加到队列",
"@syncStatusEnqueued": {}, "@syncStatusEnqueued": {},
"syncStatusRunning": "运行中", "syncStatusRunning": "同步中",
"@syncStatusRunning": {}, "@syncStatusRunning": {},
"syncStatusFailed": "失败", "syncStatusFailed": "同步失败",
"@syncStatusFailed": {}, "@syncStatusFailed": {},
"syncStatusWaitingToRetry": "等待重试", "syncStatusWaitingToRetry": "等待自动重试",
"@syncStatusWaitingToRetry": {}, "@syncStatusWaitingToRetry": {},
"syncStatusSynced": "已同步", "syncStatusSynced": "已完成同步",
"@syncStatusSynced": {}, "@syncStatusSynced": {},
"syncStatusPartially": "部分", "syncStatusPartially": "部分同步成功",
"@syncStatusPartially": {}, "@syncStatusPartially": {},
"syncOverlaySyncing": "正在同步项目详情", "syncOverlaySyncing": "正在同步项目详情",
"@syncOverlaySyncing": {}, "@syncOverlaySyncing": {},
"syncSelectDownloadsFolder": "选择下载文件夹", "syncSelectDownloadsFolder": "选择下载文件夹",
"@syncSelectDownloadsFolder": {}, "@syncSelectDownloadsFolder": {},
"syncRemoveUnableToDeleteItem": "无法移除已同步的项目,出了点问题", "syncRemoveUnableToDeleteItem": "出现了一些问题,导致无法移除已同步的项目",
"@syncRemoveUnableToDeleteItem": {}, "@syncRemoveUnableToDeleteItem": {},
"syncAddItemForSyncing": "已添加 {item} 进行同步", "syncAddItemForSyncing": "已添加 {item} 进行同步",
"@syncAddItemForSyncing": { "@syncAddItemForSyncing": {
@ -1086,9 +1086,9 @@
"@settingsAutoNextDesc": {}, "@settingsAutoNextDesc": {},
"autoNextOffStaticDesc": "播放时间还剩 30 秒时显示接下来预览窗", "autoNextOffStaticDesc": "播放时间还剩 30 秒时显示接下来预览窗",
"@autoNextOffStaticDesc": {}, "@autoNextOffStaticDesc": {},
"autoNextOffSmartTitle": "自动的", "autoNextOffSmartTitle": "智能模式",
"@autoNextOffSmartTitle": {}, "@autoNextOffSmartTitle": {},
"autoNextOffStaticTitle": "静态的", "autoNextOffStaticTitle": "固定模式",
"@autoNextOffStaticTitle": {}, "@autoNextOffStaticTitle": {},
"playbackRate": "播放速率", "playbackRate": "播放速率",
"@playbackRate": {}, "@playbackRate": {},
@ -1286,5 +1286,65 @@
"settingsPlayerBufferSizeDesc": "配置视频播放的缓冲区大小,确定加载到缓存中的数据量。", "settingsPlayerBufferSizeDesc": "配置视频播放的缓冲区大小,确定加载到缓存中的数据量。",
"@settingsPlayerBufferSizeDesc": {}, "@settingsPlayerBufferSizeDesc": {},
"settingsPlayerBufferSizeTitle": "视频缓冲区大小", "settingsPlayerBufferSizeTitle": "视频缓冲区大小",
"@settingsPlayerBufferSizeTitle": {} "@settingsPlayerBufferSizeTitle": {},
"maxConcurrentDownloadsTitle": "最大并发下载",
"@maxConcurrentDownloadsTitle": {},
"maxConcurrentDownloadsDesc": "设置可同时运行的最大下载数量。设置为 0 则禁用限制。",
"@maxConcurrentDownloadsDesc": {},
"playbackTrackSelection": "播放轨道选择",
"@playbackTrackSelection": {},
"rememberAudioSelections": "根据上一项设置音轨",
"@rememberAudioSelections": {},
"rememberAudioSelectionsDesc": "尝试将音轨设置为与上一个视频最接近的匹配。",
"@rememberAudioSelectionsDesc": {},
"rememberSubtitleSelections": "根据上一项设置字幕轨",
"@rememberSubtitleSelections": {},
"rememberSubtitleSelectionsDesc": "尝试将字幕轨设置为与上一个视频最接近的匹配。",
"@rememberSubtitleSelectionsDesc": {},
"similarToLikedItem": "类似的喜欢项目",
"@similarToLikedItem": {},
"exitFladderTitle": "退出 Fladder",
"@exitFladderTitle": {},
"similarToRecentlyPlayed": "类似的最近播放",
"@similarToRecentlyPlayed": {},
"hasActorFromRecentlyPlayed": "最近播出的演员",
"@hasActorFromRecentlyPlayed": {},
"hasLikedDirector": "喜欢的导演",
"@hasLikedDirector": {},
"hasLikedActor": "喜欢的演员",
"@hasLikedActor": {},
"latest": "最新的",
"@latest": {},
"recommended": "建议",
"@recommended": {},
"hasDirectorFromRecentlyPlayed": "最近播出的导演",
"@hasDirectorFromRecentlyPlayed": {},
"playbackType": "播放类型",
"@playbackType": {},
"playbackTypeDirect": "直接播放",
"@playbackTypeDirect": {},
"playbackTypeTranscode": "转码",
"@playbackTypeTranscode": {},
"playbackTypeOffline": "离线",
"@playbackTypeOffline": {},
"latestReleases": "最新版本",
"@latestReleases": {},
"autoCheckForUpdates": "定期检查更新",
"@autoCheckForUpdates": {},
"newReleaseFoundTitle": "更新 {newRelease} 可用!",
"@newReleaseFoundTitle": {
"placeholders": {
"newRelease": {
"type": "String"
}
}
},
"newUpdateFoundOnGithub": "在 Github 上发现新的更新",
"@newUpdateFoundOnGithub": {},
"settingsEnableOsMediaControlsDesc": "允许使用媒体键控制播放并在操作系统中显示当前正在播放的媒体",
"@settingsEnableOsMediaControlsDesc": {},
"enableBackgroundPostersTitle": "启用背景海报",
"@enableBackgroundPostersTitle": {},
"enableBackgroundPostersDesc": "在适用的屏幕上显示随机海报",
"@enableBackgroundPostersDesc": {}
} }

View file

@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
@ -18,9 +17,11 @@ import 'package:smtc_windows/smtc_windows.dart' if (dart.library.html) 'package:
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:fladder/l10n/generated/app_localizations.dart';
import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/account_model.dart';
import 'package:fladder/models/settings/home_settings_model.dart'; import 'package:fladder/models/settings/arguments_model.dart';
import 'package:fladder/models/syncing/i_synced_item.dart'; import 'package:fladder/models/syncing/i_synced_item.dart';
import 'package:fladder/providers/arguments_provider.dart';
import 'package:fladder/providers/crash_log_provider.dart'; import 'package:fladder/providers/crash_log_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/shared_provider.dart';
@ -31,7 +32,7 @@ import 'package:fladder/routes/auto_router.dart';
import 'package:fladder/routes/auto_router.gr.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/screens/login/lock_screen.dart'; import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/theme.dart'; import 'package:fladder/theme.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/application_info.dart'; import 'package:fladder/util/application_info.dart';
import 'package:fladder/util/fladder_config.dart'; import 'package:fladder/util/fladder_config.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
@ -52,7 +53,7 @@ Future<Map<String, dynamic>> loadConfig() async {
return jsonDecode(configString); return jsonDecode(configString);
} }
void main() async { void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
final crashProvider = CrashLogNotifier(); final crashProvider = CrashLogNotifier();
@ -96,6 +97,7 @@ void main() async {
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences), sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
applicationInfoProvider.overrideWith((ref) => applicationInfo), applicationInfoProvider.overrideWith((ref) => applicationInfo),
crashLogProvider.overrideWith((ref) => crashProvider), crashLogProvider.overrideWith((ref) => crashProvider),
argumentsStateProvider.overrideWith((ref) => ArgumentsModel.fromArguments(args)),
syncProvider.overrideWith((ref) => SyncNotifier( syncProvider.overrideWith((ref) => SyncNotifier(
ref, ref,
!kIsWeb !kIsWeb
@ -108,13 +110,7 @@ void main() async {
)) ))
], ],
child: AdaptiveLayoutBuilder( child: AdaptiveLayoutBuilder(
fallBack: ViewSize.tablet, child: (context) => const Main(),
layoutPoints: [
LayoutPoints(start: 0, end: 599, type: ViewSize.phone),
LayoutPoints(start: 600, end: 1919, type: ViewSize.tablet),
LayoutPoints(start: 1920, end: 3180, type: ViewSize.desktop),
],
child: const Main(),
), ),
), ),
); );
@ -241,6 +237,10 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
windowManager.waitUntilReadyToShow(windowOptions, () async { windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show(); await windowManager.show();
await windowManager.focus(); await windowManager.focus();
final startupArguments = ref.read(argumentsStateProvider);
if (startupArguments.htpcMode && !(await windowManager.isFullScreen())) {
await windowManager.setFullScreen(true);
}
}); });
} else { } else {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
@ -288,13 +288,15 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
), ),
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
builder: (context, child) => Localizations.override( locale: language,
context: context, localeResolutionCallback: (locale, supportedLocales) {
locale: AppLocalizations.supportedLocales.firstWhere( if (locale == null || !supportedLocales.contains(locale)) {
(element) => element.languageCode == language.languageCode, return const Locale('en');
orElse: () => const Locale('en', "GB"), }
), return locale;
child: LocalizationContextWrapper(child: ScaffoldMessenger(child: child ?? Container())), },
builder: (context, child) => LocalizationContextWrapper(
child: ScaffoldMessenger(child: child ?? Container()),
), ),
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
darkTheme: darkTheme.copyWith( darkTheme: darkTheme.copyWith(
@ -304,6 +306,7 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
colorScheme: darkTheme.colorScheme.copyWith( colorScheme: darkTheme.colorScheme.copyWith(
surface: amoledOverwrite, surface: amoledOverwrite,
surfaceContainerHighest: amoledOverwrite, surfaceContainerHighest: amoledOverwrite,
surfaceContainerLow: amoledOverwrite,
), ),
), ),
themeMode: themeMode, themeMode: themeMode,

View file

@ -4,19 +4,19 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/credentials_model.dart'; import 'package:fladder/models/credentials_model.dart';
import 'package:fladder/models/library_filters_model.dart'; import 'package:fladder/models/library_filters_model.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
part 'account_model.freezed.dart'; part 'account_model.freezed.dart';
part 'account_model.g.dart'; part 'account_model.g.dart';
@freezed @Freezed(copyWith: true)
class AccountModel with _$AccountModel { class AccountModel with _$AccountModel {
const AccountModel._(); const AccountModel._();
@ -34,6 +34,7 @@ class AccountModel with _$AccountModel {
@Default([]) List<LibraryFiltersModel> savedFilters, @Default([]) List<LibraryFiltersModel> savedFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration, @JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false) UserConfiguration? userConfiguration,
}) = _AccountModel; }) = _AccountModel;
factory AccountModel.fromJson(Map<String, dynamic> json) => _$AccountModelFromJson(json); factory AccountModel.fromJson(Map<String, dynamic> json) => _$AccountModelFromJson(json);

View file

@ -37,6 +37,9 @@ mixin _$AccountModel {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? get serverConfiguration => ServerConfiguration? get serverConfiguration =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? get userConfiguration =>
throw _privateConstructorUsedError;
/// Serializes this AccountModel to a JSON map. /// Serializes this AccountModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -68,7 +71,9 @@ abstract class $AccountModelCopyWith<$Res> {
List<LibraryFiltersModel> savedFilters, List<LibraryFiltersModel> savedFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration}); ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? userConfiguration});
} }
/// @nodoc /// @nodoc
@ -99,6 +104,7 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
Object? savedFilters = null, Object? savedFilters = null,
Object? policy = freezed, Object? policy = freezed,
Object? serverConfiguration = freezed, Object? serverConfiguration = freezed,
Object? userConfiguration = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
name: null == name name: null == name
@ -153,6 +159,10 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
? _value.serverConfiguration ? _value.serverConfiguration
: serverConfiguration // ignore: cast_nullable_to_non_nullable : serverConfiguration // ignore: cast_nullable_to_non_nullable
as ServerConfiguration?, as ServerConfiguration?,
userConfiguration: freezed == userConfiguration
? _value.userConfiguration
: userConfiguration // ignore: cast_nullable_to_non_nullable
as UserConfiguration?,
) as $Val); ) as $Val);
} }
} }
@ -179,7 +189,9 @@ abstract class _$$AccountModelImplCopyWith<$Res>
List<LibraryFiltersModel> savedFilters, List<LibraryFiltersModel> savedFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration}); ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? userConfiguration});
} }
/// @nodoc /// @nodoc
@ -208,6 +220,7 @@ class __$$AccountModelImplCopyWithImpl<$Res>
Object? savedFilters = null, Object? savedFilters = null,
Object? policy = freezed, Object? policy = freezed,
Object? serverConfiguration = freezed, Object? serverConfiguration = freezed,
Object? userConfiguration = freezed,
}) { }) {
return _then(_$AccountModelImpl( return _then(_$AccountModelImpl(
name: null == name name: null == name
@ -262,6 +275,10 @@ class __$$AccountModelImplCopyWithImpl<$Res>
? _value.serverConfiguration ? _value.serverConfiguration
: serverConfiguration // ignore: cast_nullable_to_non_nullable : serverConfiguration // ignore: cast_nullable_to_non_nullable
as ServerConfiguration?, as ServerConfiguration?,
userConfiguration: freezed == userConfiguration
? _value.userConfiguration
: userConfiguration // ignore: cast_nullable_to_non_nullable
as UserConfiguration?,
)); ));
} }
} }
@ -283,7 +300,9 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
final List<LibraryFiltersModel> savedFilters = const [], final List<LibraryFiltersModel> savedFilters = const [],
@JsonKey(includeFromJson: false, includeToJson: false) this.policy, @JsonKey(includeFromJson: false, includeToJson: false) this.policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
this.serverConfiguration}) this.serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
this.userConfiguration})
: _latestItemsExcludes = latestItemsExcludes, : _latestItemsExcludes = latestItemsExcludes,
_searchQueryHistory = searchQueryHistory, _searchQueryHistory = searchQueryHistory,
_savedFilters = savedFilters, _savedFilters = savedFilters,
@ -346,10 +365,13 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
final ServerConfiguration? serverConfiguration; final ServerConfiguration? serverConfiguration;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
final UserConfiguration? userConfiguration;
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, savedFilters: $savedFilters, policy: $policy, serverConfiguration: $serverConfiguration)'; return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, savedFilters: $savedFilters, policy: $policy, serverConfiguration: $serverConfiguration, userConfiguration: $userConfiguration)';
} }
@override @override
@ -369,56 +391,10 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
..add(DiagnosticsProperty('quickConnectState', quickConnectState)) ..add(DiagnosticsProperty('quickConnectState', quickConnectState))
..add(DiagnosticsProperty('savedFilters', savedFilters)) ..add(DiagnosticsProperty('savedFilters', savedFilters))
..add(DiagnosticsProperty('policy', policy)) ..add(DiagnosticsProperty('policy', policy))
..add(DiagnosticsProperty('serverConfiguration', serverConfiguration)); ..add(DiagnosticsProperty('serverConfiguration', serverConfiguration))
..add(DiagnosticsProperty('userConfiguration', userConfiguration));
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AccountModelImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.id, id) || other.id == id) &&
(identical(other.avatar, avatar) || other.avatar == avatar) &&
(identical(other.lastUsed, lastUsed) ||
other.lastUsed == lastUsed) &&
(identical(other.authMethod, authMethod) ||
other.authMethod == authMethod) &&
(identical(other.localPin, localPin) ||
other.localPin == localPin) &&
(identical(other.credentials, credentials) ||
other.credentials == credentials) &&
const DeepCollectionEquality()
.equals(other._latestItemsExcludes, _latestItemsExcludes) &&
const DeepCollectionEquality()
.equals(other._searchQueryHistory, _searchQueryHistory) &&
(identical(other.quickConnectState, quickConnectState) ||
other.quickConnectState == quickConnectState) &&
const DeepCollectionEquality()
.equals(other._savedFilters, _savedFilters) &&
(identical(other.policy, policy) || other.policy == policy) &&
(identical(other.serverConfiguration, serverConfiguration) ||
other.serverConfiguration == serverConfiguration));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
name,
id,
avatar,
lastUsed,
authMethod,
localPin,
credentials,
const DeepCollectionEquality().hash(_latestItemsExcludes),
const DeepCollectionEquality().hash(_searchQueryHistory),
quickConnectState,
const DeepCollectionEquality().hash(_savedFilters),
policy,
serverConfiguration);
/// Create a copy of AccountModel /// Create a copy of AccountModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -451,7 +427,9 @@ abstract class _AccountModel extends AccountModel {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
final UserPolicy? policy, final UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
final ServerConfiguration? serverConfiguration}) = _$AccountModelImpl; final ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
final UserConfiguration? userConfiguration}) = _$AccountModelImpl;
const _AccountModel._() : super._(); const _AccountModel._() : super._();
factory _AccountModel.fromJson(Map<String, dynamic> json) = factory _AccountModel.fromJson(Map<String, dynamic> json) =
@ -485,6 +463,9 @@ abstract class _AccountModel extends AccountModel {
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? get serverConfiguration; ServerConfiguration? get serverConfiguration;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? get userConfiguration;
/// Create a copy of AccountModel /// Create a copy of AccountModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.

View file

@ -108,34 +108,11 @@ class BoxSetModelMapper extends SubClassMapperBase<BoxSetModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static BoxSetModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<BoxSetModel>(map);
}
static BoxSetModel fromJson(String json) {
return ensureInitialized().decodeJson<BoxSetModel>(json);
}
} }
mixin BoxSetModelMappable { mixin BoxSetModelMappable {
String toJson() {
return BoxSetModelMapper.ensureInitialized()
.encodeJson<BoxSetModel>(this as BoxSetModel);
}
Map<String, dynamic> toMap() {
return BoxSetModelMapper.ensureInitialized()
.encodeMap<BoxSetModel>(this as BoxSetModel);
}
BoxSetModelCopyWith<BoxSetModel, BoxSetModel, BoxSetModel> get copyWith => BoxSetModelCopyWith<BoxSetModel, BoxSetModel, BoxSetModel> get copyWith =>
_BoxSetModelCopyWithImpl(this as BoxSetModel, $identity, $identity); _BoxSetModelCopyWithImpl(this as BoxSetModel, $identity, $identity);
@override
String toString() {
return BoxSetModelMapper.ensureInitialized()
.stringifyValue(this as BoxSetModel);
}
} }
extension BoxSetModelValueCopy<$R, $Out> extension BoxSetModelValueCopy<$R, $Out>

View file

@ -17,6 +17,8 @@ extension CollectionTypeExtension on CollectionType {
Set<FladderItemType> get itemKinds { Set<FladderItemType> get itemKinds {
switch (this) { switch (this) {
case CollectionType.music:
return {FladderItemType.musicAlbum};
case CollectionType.movies: case CollectionType.movies:
return {FladderItemType.movie}; return {FladderItemType.movie};
case CollectionType.tvshows: case CollectionType.tvshows:
@ -30,6 +32,8 @@ extension CollectionTypeExtension on CollectionType {
IconData getIconType(bool outlined) { IconData getIconType(bool outlined) {
switch (this) { switch (this) {
case CollectionType.music:
return outlined ? IconsaxPlusLinear.music_square : IconsaxPlusBold.music_square;
case CollectionType.movies: case CollectionType.movies:
return outlined ? IconsaxPlusLinear.video_horizontal : IconsaxPlusBold.video_horizontal; return outlined ? IconsaxPlusLinear.video_horizontal : IconsaxPlusBold.video_horizontal;
case CollectionType.tvshows: case CollectionType.tvshows:
@ -48,4 +52,16 @@ extension CollectionTypeExtension on CollectionType {
return IconsaxPlusLinear.information; return IconsaxPlusLinear.information;
} }
} }
double? get aspectRatio => switch (this) {
CollectionType.music ||
CollectionType.homevideos ||
CollectionType.boxsets ||
CollectionType.photos ||
CollectionType.livetv ||
CollectionType.playlists =>
0.8,
CollectionType.folders => 1.3,
_ => null,
};
} }

View file

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:dart_mappable/dart_mappable.dart'; import 'package:dart_mappable/dart_mappable.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
@ -304,6 +304,15 @@ enum FladderItemType {
const FladderItemType({required this.icon, required this.selectedicon}); const FladderItemType({required this.icon, required this.selectedicon});
double get aspectRatio => switch (this) {
FladderItemType.video => 0.8,
FladderItemType.photo => 0.8,
FladderItemType.photoAlbum => 0.8,
FladderItemType.musicAlbum => 0.8,
FladderItemType.baseType => 0.8,
_ => 0.55,
};
static Set<FladderItemType> get playable => { static Set<FladderItemType> get playable => {
FladderItemType.series, FladderItemType.series,
FladderItemType.episode, FladderItemType.episode,
@ -317,27 +326,25 @@ enum FladderItemType {
FladderItemType.video, FladderItemType.video,
}; };
String label(BuildContext context) { String label(BuildContext context) => switch (this) {
return switch (this) { FladderItemType.baseType => context.localized.mediaTypeBase,
FladderItemType.baseType => context.localized.mediaTypeBase, FladderItemType.audio => context.localized.audio,
FladderItemType.audio => context.localized.audio, FladderItemType.collectionFolder => context.localized.collectionFolder,
FladderItemType.collectionFolder => context.localized.collectionFolder, FladderItemType.musicAlbum => context.localized.musicAlbum,
FladderItemType.musicAlbum => context.localized.musicAlbum, FladderItemType.musicVideo => context.localized.video,
FladderItemType.musicVideo => context.localized.video, FladderItemType.video => context.localized.video,
FladderItemType.video => context.localized.video, FladderItemType.movie => context.localized.mediaTypeMovie,
FladderItemType.movie => context.localized.mediaTypeMovie, FladderItemType.series => context.localized.mediaTypeSeries,
FladderItemType.series => context.localized.mediaTypeSeries, FladderItemType.season => context.localized.mediaTypeSeason,
FladderItemType.season => context.localized.mediaTypeSeason, FladderItemType.episode => context.localized.mediaTypeEpisode,
FladderItemType.episode => context.localized.mediaTypeEpisode, FladderItemType.photo => context.localized.mediaTypePhoto,
FladderItemType.photo => context.localized.mediaTypePhoto, FladderItemType.person => context.localized.mediaTypePerson,
FladderItemType.person => context.localized.mediaTypePerson, FladderItemType.photoAlbum => context.localized.mediaTypePhotoAlbum,
FladderItemType.photoAlbum => context.localized.mediaTypePhotoAlbum, FladderItemType.folder => context.localized.mediaTypeFolder,
FladderItemType.folder => context.localized.mediaTypeFolder, FladderItemType.boxset => context.localized.mediaTypeBoxset,
FladderItemType.boxset => context.localized.mediaTypeBoxset, FladderItemType.playlist => context.localized.mediaTypePlaylist,
FladderItemType.playlist => context.localized.mediaTypePlaylist, FladderItemType.book => context.localized.mediaTypeBook,
FladderItemType.book => context.localized.mediaTypeBook, };
};
}
BaseItemKind get dtoKind => switch (this) { BaseItemKind get dtoKind => switch (this) {
FladderItemType.baseType => BaseItemKind.userrootfolder, FladderItemType.baseType => BaseItemKind.userrootfolder,

View file

@ -93,35 +93,12 @@ class ItemBaseModelMapper extends ClassMapperBase<ItemBaseModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static ItemBaseModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<ItemBaseModel>(map);
}
static ItemBaseModel fromJson(String json) {
return ensureInitialized().decodeJson<ItemBaseModel>(json);
}
} }
mixin ItemBaseModelMappable { mixin ItemBaseModelMappable {
String toJson() {
return ItemBaseModelMapper.ensureInitialized()
.encodeJson<ItemBaseModel>(this as ItemBaseModel);
}
Map<String, dynamic> toMap() {
return ItemBaseModelMapper.ensureInitialized()
.encodeMap<ItemBaseModel>(this as ItemBaseModel);
}
ItemBaseModelCopyWith<ItemBaseModel, ItemBaseModel, ItemBaseModel> ItemBaseModelCopyWith<ItemBaseModel, ItemBaseModel, ItemBaseModel>
get copyWith => _ItemBaseModelCopyWithImpl( get copyWith => _ItemBaseModelCopyWithImpl(
this as ItemBaseModel, $identity, $identity); this as ItemBaseModel, $identity, $identity);
@override
String toString() {
return ItemBaseModelMapper.ensureInitialized()
.stringifyValue(this as ItemBaseModel);
}
} }
extension ItemBaseModelValueCopy<$R, $Out> extension ItemBaseModelValueCopy<$R, $Out>

View file

@ -199,17 +199,34 @@ extension EpisodeListExtensions on List<EpisodeModel> {
} }
EpisodeModel? get nextUp { EpisodeModel? get nextUp {
final lastProgress = final episodes = where((e) => e.season > 0 && e.status == EpisodeStatus.available).toList();
lastIndexWhere((element) => element.userData.progress != 0 && element.status == EpisodeStatus.available); if (episodes.isEmpty) return null;
final lastPlayed =
lastIndexWhere((element) => element.userData.played && element.status == EpisodeStatus.available);
if (lastProgress == -1 && lastPlayed == -1) { final lastProgressIndex = episodes.lastIndexWhere((e) => e.userData.progress != 0);
return firstWhereOrNull((element) => element.status == EpisodeStatus.available); final lastPlayedIndex = episodes.lastIndexWhere((e) => e.userData.played);
} else { final lastWatchedIndex = [lastProgressIndex, lastPlayedIndex].reduce((a, b) => a > b ? a : b);
return getRange(lastProgress > lastPlayed ? lastProgress : lastPlayed + 1, length)
.firstWhereOrNull((element) => element.status == EpisodeStatus.available); if (lastWatchedIndex >= 0) {
final current = episodes[lastWatchedIndex];
if (!current.userData.played && current.userData.progress != 0) {
return current;
}
final nextIndex = lastWatchedIndex + 1;
if (nextIndex < episodes.length) {
final next = episodes[nextIndex];
if (!next.userData.played && next.userData.progress != 0) {
return next;
}
final nextUnplayed = episodes.sublist(nextIndex).firstWhereOrNull(
(e) => e.status == EpisodeStatus.available && !e.userData.played,
);
if (nextUnplayed != null) return nextUnplayed;
}
} }
return episodes.firstOrNull;
} }
bool get allPlayed { bool get allPlayed {

View file

@ -141,34 +141,11 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static EpisodeModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<EpisodeModel>(map);
}
static EpisodeModel fromJson(String json) {
return ensureInitialized().decodeJson<EpisodeModel>(json);
}
} }
mixin EpisodeModelMappable { mixin EpisodeModelMappable {
String toJson() {
return EpisodeModelMapper.ensureInitialized()
.encodeJson<EpisodeModel>(this as EpisodeModel);
}
Map<String, dynamic> toMap() {
return EpisodeModelMapper.ensureInitialized()
.encodeMap<EpisodeModel>(this as EpisodeModel);
}
EpisodeModelCopyWith<EpisodeModel, EpisodeModel, EpisodeModel> get copyWith => EpisodeModelCopyWith<EpisodeModel, EpisodeModel, EpisodeModel> get copyWith =>
_EpisodeModelCopyWithImpl(this as EpisodeModel, $identity, $identity); _EpisodeModelCopyWithImpl(this as EpisodeModel, $identity, $identity);
@override
String toString() {
return EpisodeModelMapper.ensureInitialized()
.stringifyValue(this as EpisodeModel);
}
} }
extension EpisodeModelValueCopy<$R, $Out> extension EpisodeModelValueCopy<$R, $Out>

View file

@ -108,34 +108,11 @@ class FolderModelMapper extends SubClassMapperBase<FolderModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static FolderModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<FolderModel>(map);
}
static FolderModel fromJson(String json) {
return ensureInitialized().decodeJson<FolderModel>(json);
}
} }
mixin FolderModelMappable { mixin FolderModelMappable {
String toJson() {
return FolderModelMapper.ensureInitialized()
.encodeJson<FolderModel>(this as FolderModel);
}
Map<String, dynamic> toMap() {
return FolderModelMapper.ensureInitialized()
.encodeMap<FolderModel>(this as FolderModel);
}
FolderModelCopyWith<FolderModel, FolderModel, FolderModel> get copyWith => FolderModelCopyWith<FolderModel, FolderModel, FolderModel> get copyWith =>
_FolderModelCopyWithImpl(this as FolderModel, $identity, $identity); _FolderModelCopyWithImpl(this as FolderModel, $identity, $identity);
@override
String toString() {
return FolderModelMapper.ensureInitialized()
.stringifyValue(this as FolderModel);
}
} }
extension FolderModelValueCopy<$R, $Out> extension FolderModelValueCopy<$R, $Out>

View file

@ -18,92 +18,6 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$ItemPropertiesModel { mixin _$ItemPropertiesModel {
bool get canDelete => throw _privateConstructorUsedError; bool get canDelete => throw _privateConstructorUsedError;
bool get canDownload => throw _privateConstructorUsedError; bool get canDownload => throw _privateConstructorUsedError;
/// Create a copy of ItemPropertiesModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ItemPropertiesModelCopyWith<ItemPropertiesModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ItemPropertiesModelCopyWith<$Res> {
factory $ItemPropertiesModelCopyWith(
ItemPropertiesModel value, $Res Function(ItemPropertiesModel) then) =
_$ItemPropertiesModelCopyWithImpl<$Res, ItemPropertiesModel>;
@useResult
$Res call({bool canDelete, bool canDownload});
}
/// @nodoc
class _$ItemPropertiesModelCopyWithImpl<$Res, $Val extends ItemPropertiesModel>
implements $ItemPropertiesModelCopyWith<$Res> {
_$ItemPropertiesModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ItemPropertiesModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? canDelete = null,
Object? canDownload = null,
}) {
return _then(_value.copyWith(
canDelete: null == canDelete
? _value.canDelete
: canDelete // ignore: cast_nullable_to_non_nullable
as bool,
canDownload: null == canDownload
? _value.canDownload
: canDownload // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$ItemPropertiesModelImplCopyWith<$Res>
implements $ItemPropertiesModelCopyWith<$Res> {
factory _$$ItemPropertiesModelImplCopyWith(_$ItemPropertiesModelImpl value,
$Res Function(_$ItemPropertiesModelImpl) then) =
__$$ItemPropertiesModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool canDelete, bool canDownload});
}
/// @nodoc
class __$$ItemPropertiesModelImplCopyWithImpl<$Res>
extends _$ItemPropertiesModelCopyWithImpl<$Res, _$ItemPropertiesModelImpl>
implements _$$ItemPropertiesModelImplCopyWith<$Res> {
__$$ItemPropertiesModelImplCopyWithImpl(_$ItemPropertiesModelImpl _value,
$Res Function(_$ItemPropertiesModelImpl) _then)
: super(_value, _then);
/// Create a copy of ItemPropertiesModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? canDelete = null,
Object? canDownload = null,
}) {
return _then(_$ItemPropertiesModelImpl(
canDelete: null == canDelete
? _value.canDelete
: canDelete // ignore: cast_nullable_to_non_nullable
as bool,
canDownload: null == canDownload
? _value.canDownload
: canDownload // ignore: cast_nullable_to_non_nullable
as bool,
));
}
} }
/// @nodoc /// @nodoc
@ -122,29 +36,6 @@ class _$ItemPropertiesModelImpl extends _ItemPropertiesModel {
String toString() { String toString() {
return 'ItemPropertiesModel._internal(canDelete: $canDelete, canDownload: $canDownload)'; return 'ItemPropertiesModel._internal(canDelete: $canDelete, canDownload: $canDownload)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ItemPropertiesModelImpl &&
(identical(other.canDelete, canDelete) ||
other.canDelete == canDelete) &&
(identical(other.canDownload, canDownload) ||
other.canDownload == canDownload));
}
@override
int get hashCode => Object.hash(runtimeType, canDelete, canDownload);
/// Create a copy of ItemPropertiesModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith =>
__$$ItemPropertiesModelImplCopyWithImpl<_$ItemPropertiesModelImpl>(
this, _$identity);
} }
abstract class _ItemPropertiesModel extends ItemPropertiesModel { abstract class _ItemPropertiesModel extends ItemPropertiesModel {
@ -157,11 +48,4 @@ abstract class _ItemPropertiesModel extends ItemPropertiesModel {
bool get canDelete; bool get canDelete;
@override @override
bool get canDownload; bool get canDownload;
/// Create a copy of ItemPropertiesModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith =>
throw _privateConstructorUsedError;
} }

View file

@ -14,7 +14,7 @@ import 'package:fladder/models/items/images_models.dart';
part 'item_shared_models.mapper.dart'; part 'item_shared_models.mapper.dart';
@MappableClass() @MappableClass(generateMethods: GenerateMethods.encode | GenerateMethods.decode | GenerateMethods.copy)
class UserData with UserDataMappable { class UserData with UserDataMappable {
final bool isFavourite; final bool isFavourite;
final int playCount; final int playCount;

View file

@ -92,10 +92,6 @@ mixin UserDataMappable {
UserDataCopyWith<UserData, UserData, UserData> get copyWith => UserDataCopyWith<UserData, UserData, UserData> get copyWith =>
_UserDataCopyWithImpl(this as UserData, $identity, $identity); _UserDataCopyWithImpl(this as UserData, $identity, $identity);
@override
String toString() {
return UserDataMapper.ensureInitialized().stringifyValue(this as UserData);
}
} }
extension UserDataValueCopy<$R, $Out> on ObjectCopyWith<$R, UserData, $Out> { extension UserDataValueCopy<$R, $Out> on ObjectCopyWith<$R, UserData, $Out> {

View file

@ -112,35 +112,12 @@ class ItemStreamModelMapper extends SubClassMapperBase<ItemStreamModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static ItemStreamModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<ItemStreamModel>(map);
}
static ItemStreamModel fromJson(String json) {
return ensureInitialized().decodeJson<ItemStreamModel>(json);
}
} }
mixin ItemStreamModelMappable { mixin ItemStreamModelMappable {
String toJson() {
return ItemStreamModelMapper.ensureInitialized()
.encodeJson<ItemStreamModel>(this as ItemStreamModel);
}
Map<String, dynamic> toMap() {
return ItemStreamModelMapper.ensureInitialized()
.encodeMap<ItemStreamModel>(this as ItemStreamModel);
}
ItemStreamModelCopyWith<ItemStreamModel, ItemStreamModel, ItemStreamModel> ItemStreamModelCopyWith<ItemStreamModel, ItemStreamModel, ItemStreamModel>
get copyWith => _ItemStreamModelCopyWithImpl( get copyWith => _ItemStreamModelCopyWithImpl(
this as ItemStreamModel, $identity, $identity); this as ItemStreamModel, $identity, $identity);
@override
String toString() {
return ItemStreamModelMapper.ensureInitialized()
.stringifyValue(this as ItemStreamModel);
}
} }
extension ItemStreamModelValueCopy<$R, $Out> extension ItemStreamModelValueCopy<$R, $Out>

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
@ -39,7 +40,19 @@ class MediaSegment with _$MediaSegment {
bool inRange(Duration position) => (position.compareTo(start) >= 0 && position.compareTo(end) <= 0); bool inRange(Duration position) => (position.compareTo(start) >= 0 && position.compareTo(end) <= 0);
bool forceShow(Duration position) => (position - start).inSeconds < (end - start).inSeconds * 0.20; SegmentVisibility visibility(Duration position, {bool force = false}) {
if (force) return SegmentVisibility.visible;
var difference = (position - start);
if (difference > const Duration(minutes: 1, seconds: 30)) return SegmentVisibility.hidden;
Duration clamp = ((end - start) * 0.20).clamp(Duration.zero, const Duration(minutes: 1));
return difference < clamp ? SegmentVisibility.visible : SegmentVisibility.partially;
}
}
enum SegmentVisibility {
hidden,
partially,
visible;
} }
const Map<MediaSegmentType, SegmentSkip> defaultSegmentSkipValues = { const Map<MediaSegmentType, SegmentSkip> defaultSegmentSkipValues = {

View file

@ -24,82 +24,6 @@ mixin _$MediaSegmentsModel {
/// Serializes this MediaSegmentsModel to a JSON map. /// Serializes this MediaSegmentsModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of MediaSegmentsModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MediaSegmentsModelCopyWith<MediaSegmentsModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $MediaSegmentsModelCopyWith<$Res> {
factory $MediaSegmentsModelCopyWith(
MediaSegmentsModel value, $Res Function(MediaSegmentsModel) then) =
_$MediaSegmentsModelCopyWithImpl<$Res, MediaSegmentsModel>;
@useResult
$Res call({List<MediaSegment> segments});
}
/// @nodoc
class _$MediaSegmentsModelCopyWithImpl<$Res, $Val extends MediaSegmentsModel>
implements $MediaSegmentsModelCopyWith<$Res> {
_$MediaSegmentsModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of MediaSegmentsModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? segments = null,
}) {
return _then(_value.copyWith(
segments: null == segments
? _value.segments
: segments // ignore: cast_nullable_to_non_nullable
as List<MediaSegment>,
) as $Val);
}
}
/// @nodoc
abstract class _$$MediaSegmentsModelImplCopyWith<$Res>
implements $MediaSegmentsModelCopyWith<$Res> {
factory _$$MediaSegmentsModelImplCopyWith(_$MediaSegmentsModelImpl value,
$Res Function(_$MediaSegmentsModelImpl) then) =
__$$MediaSegmentsModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({List<MediaSegment> segments});
}
/// @nodoc
class __$$MediaSegmentsModelImplCopyWithImpl<$Res>
extends _$MediaSegmentsModelCopyWithImpl<$Res, _$MediaSegmentsModelImpl>
implements _$$MediaSegmentsModelImplCopyWith<$Res> {
__$$MediaSegmentsModelImplCopyWithImpl(_$MediaSegmentsModelImpl _value,
$Res Function(_$MediaSegmentsModelImpl) _then)
: super(_value, _then);
/// Create a copy of MediaSegmentsModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? segments = null,
}) {
return _then(_$MediaSegmentsModelImpl(
segments: null == segments
? _value._segments
: segments // ignore: cast_nullable_to_non_nullable
as List<MediaSegment>,
));
}
} }
/// @nodoc /// @nodoc
@ -126,28 +50,6 @@ class _$MediaSegmentsModelImpl extends _MediaSegmentsModel {
return 'MediaSegmentsModel(segments: $segments)'; return 'MediaSegmentsModel(segments: $segments)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$MediaSegmentsModelImpl &&
const DeepCollectionEquality().equals(other._segments, _segments));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(_segments));
/// Create a copy of MediaSegmentsModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$MediaSegmentsModelImplCopyWith<_$MediaSegmentsModelImpl> get copyWith =>
__$$MediaSegmentsModelImplCopyWithImpl<_$MediaSegmentsModelImpl>(
this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$$MediaSegmentsModelImplToJson( return _$$MediaSegmentsModelImplToJson(
@ -166,13 +68,6 @@ abstract class _MediaSegmentsModel extends MediaSegmentsModel {
@override @override
List<MediaSegment> get segments; List<MediaSegment> get segments;
/// Create a copy of MediaSegmentsModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$MediaSegmentsModelImplCopyWith<_$MediaSegmentsModelImpl> get copyWith =>
throw _privateConstructorUsedError;
} }
MediaSegment _$MediaSegmentFromJson(Map<String, dynamic> json) { MediaSegment _$MediaSegmentFromJson(Map<String, dynamic> json) {
@ -187,102 +82,6 @@ mixin _$MediaSegment {
/// Serializes this MediaSegment to a JSON map. /// Serializes this MediaSegment to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of MediaSegment
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MediaSegmentCopyWith<MediaSegment> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $MediaSegmentCopyWith<$Res> {
factory $MediaSegmentCopyWith(
MediaSegment value, $Res Function(MediaSegment) then) =
_$MediaSegmentCopyWithImpl<$Res, MediaSegment>;
@useResult
$Res call({MediaSegmentType type, Duration start, Duration end});
}
/// @nodoc
class _$MediaSegmentCopyWithImpl<$Res, $Val extends MediaSegment>
implements $MediaSegmentCopyWith<$Res> {
_$MediaSegmentCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of MediaSegment
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
Object? start = null,
Object? end = null,
}) {
return _then(_value.copyWith(
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as MediaSegmentType,
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as Duration,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as Duration,
) as $Val);
}
}
/// @nodoc
abstract class _$$MediaSegmentImplCopyWith<$Res>
implements $MediaSegmentCopyWith<$Res> {
factory _$$MediaSegmentImplCopyWith(
_$MediaSegmentImpl value, $Res Function(_$MediaSegmentImpl) then) =
__$$MediaSegmentImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({MediaSegmentType type, Duration start, Duration end});
}
/// @nodoc
class __$$MediaSegmentImplCopyWithImpl<$Res>
extends _$MediaSegmentCopyWithImpl<$Res, _$MediaSegmentImpl>
implements _$$MediaSegmentImplCopyWith<$Res> {
__$$MediaSegmentImplCopyWithImpl(
_$MediaSegmentImpl _value, $Res Function(_$MediaSegmentImpl) _then)
: super(_value, _then);
/// Create a copy of MediaSegment
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
Object? start = null,
Object? end = null,
}) {
return _then(_$MediaSegmentImpl(
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as MediaSegmentType,
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as Duration,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as Duration,
));
}
} }
/// @nodoc /// @nodoc
@ -307,28 +106,6 @@ class _$MediaSegmentImpl extends _MediaSegment {
return 'MediaSegment(type: $type, start: $start, end: $end)'; return 'MediaSegment(type: $type, start: $start, end: $end)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$MediaSegmentImpl &&
(identical(other.type, type) || other.type == type) &&
(identical(other.start, start) || other.start == start) &&
(identical(other.end, end) || other.end == end));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, type, start, end);
/// Create a copy of MediaSegment
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$MediaSegmentImplCopyWith<_$MediaSegmentImpl> get copyWith =>
__$$MediaSegmentImplCopyWithImpl<_$MediaSegmentImpl>(this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$$MediaSegmentImplToJson( return _$$MediaSegmentImplToJson(
@ -353,11 +130,4 @@ abstract class _MediaSegment extends MediaSegment {
Duration get start; Duration get start;
@override @override
Duration get end; Duration get end;
/// Create a copy of MediaSegment
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$MediaSegmentImplCopyWith<_$MediaSegmentImpl> get copyWith =>
throw _privateConstructorUsedError;
} }

View file

@ -175,6 +175,20 @@ class StreamModel {
}); });
} }
class AudioAndSubStreamModel extends StreamModel {
final String language;
final String displayTitle;
AudioAndSubStreamModel({
required this.displayTitle,
required super.name,
required super.codec,
required super.isDefault,
required super.isExternal,
required super.index,
required this.language,
});
}
class VersionStreamModel { class VersionStreamModel {
final String name; final String name;
final int index; final int index;
@ -250,19 +264,17 @@ extension SortByExternalExtension<T extends StreamModel> on Iterable<T> {
} }
} }
class AudioStreamModel extends StreamModel { class AudioStreamModel extends AudioAndSubStreamModel {
final String displayTitle;
final String language;
final String channelLayout; final String channelLayout;
AudioStreamModel({ AudioStreamModel({
required this.displayTitle, required super.displayTitle,
required super.name, required super.name,
required super.codec, required super.codec,
required super.isDefault, required super.isDefault,
required super.isExternal, required super.isExternal,
required super.index, required super.index,
required this.language, required super.language,
required this.channelLayout, required this.channelLayout,
}); });
@ -292,8 +304,8 @@ class AudioStreamModel extends StreamModel {
AudioStreamModel.no({ AudioStreamModel.no({
super.name = 'Off', super.name = 'Off',
this.displayTitle = 'Off', super.displayTitle = 'Off',
this.language = '', super.language = '',
super.codec = '', super.codec = '',
this.channelLayout = '', this.channelLayout = '',
super.isDefault = false, super.isDefault = false,
@ -302,19 +314,17 @@ class AudioStreamModel extends StreamModel {
}); });
} }
class SubStreamModel extends StreamModel { class SubStreamModel extends AudioAndSubStreamModel {
String id; String id;
String title; String title;
String displayTitle;
String language;
String? url; String? url;
bool supportsExternalStream; bool supportsExternalStream;
SubStreamModel({ SubStreamModel({
required super.name, required super.name,
required this.id, required this.id,
required this.title, required this.title,
required this.displayTitle, required super.displayTitle,
required this.language, required super.language,
this.url, this.url,
required super.codec, required super.codec,
required super.isDefault, required super.isDefault,
@ -327,8 +337,8 @@ class SubStreamModel extends StreamModel {
super.name = 'Off', super.name = 'Off',
this.id = 'Off', this.id = 'Off',
this.title = 'Off', this.title = 'Off',
this.displayTitle = 'Off', super.displayTitle = 'Off',
this.language = '', super.language = '',
this.url = '', this.url = '',
super.codec = '', super.codec = '',
super.isDefault = false, super.isDefault = false,

View file

@ -147,34 +147,11 @@ class MovieModelMapper extends SubClassMapperBase<MovieModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static MovieModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<MovieModel>(map);
}
static MovieModel fromJson(String json) {
return ensureInitialized().decodeJson<MovieModel>(json);
}
} }
mixin MovieModelMappable { mixin MovieModelMappable {
String toJson() {
return MovieModelMapper.ensureInitialized()
.encodeJson<MovieModel>(this as MovieModel);
}
Map<String, dynamic> toMap() {
return MovieModelMapper.ensureInitialized()
.encodeMap<MovieModel>(this as MovieModel);
}
MovieModelCopyWith<MovieModel, MovieModel, MovieModel> get copyWith => MovieModelCopyWith<MovieModel, MovieModel, MovieModel> get copyWith =>
_MovieModelCopyWithImpl(this as MovieModel, $identity, $identity); _MovieModelCopyWithImpl(this as MovieModel, $identity, $identity);
@override
String toString() {
return MovieModelMapper.ensureInitialized()
.stringifyValue(this as MovieModel);
}
} }
extension MovieModelValueCopy<$R, $Out> extension MovieModelValueCopy<$R, $Out>

View file

@ -1,3 +1,4 @@
import 'package:dart_mappable/dart_mappable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
@ -6,8 +7,6 @@ import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/trick_play_model.dart'; import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/util/duration_extensions.dart'; import 'package:fladder/util/duration_extensions.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'overview_model.mapper.dart'; part 'overview_model.mapper.dart';
@MappableClass() @MappableClass()
@ -76,7 +75,4 @@ class OverviewModel with OverviewModelMappable {
people: Person.peopleFromDto(item.people ?? [], ref), people: Person.peopleFromDto(item.people ?? [], ref),
); );
} }
factory OverviewModel.fromMap(Map<String, dynamic> map) => OverviewModelMapper.fromMap(map);
factory OverviewModel.fromJson(String json) => OverviewModelMapper.fromJson(json);
} }

View file

@ -114,35 +114,12 @@ class OverviewModelMapper extends ClassMapperBase<OverviewModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static OverviewModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<OverviewModel>(map);
}
static OverviewModel fromJson(String json) {
return ensureInitialized().decodeJson<OverviewModel>(json);
}
} }
mixin OverviewModelMappable { mixin OverviewModelMappable {
String toJson() {
return OverviewModelMapper.ensureInitialized()
.encodeJson<OverviewModel>(this as OverviewModel);
}
Map<String, dynamic> toMap() {
return OverviewModelMapper.ensureInitialized()
.encodeMap<OverviewModel>(this as OverviewModel);
}
OverviewModelCopyWith<OverviewModel, OverviewModel, OverviewModel> OverviewModelCopyWith<OverviewModel, OverviewModel, OverviewModel>
get copyWith => _OverviewModelCopyWithImpl( get copyWith => _OverviewModelCopyWithImpl(
this as OverviewModel, $identity, $identity); this as OverviewModel, $identity, $identity);
@override
String toString() {
return OverviewModelMapper.ensureInitialized()
.stringifyValue(this as OverviewModel);
}
} }
extension OverviewModelValueCopy<$R, $Out> extension OverviewModelValueCopy<$R, $Out>

View file

@ -124,34 +124,11 @@ class PersonModelMapper extends SubClassMapperBase<PersonModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static PersonModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<PersonModel>(map);
}
static PersonModel fromJson(String json) {
return ensureInitialized().decodeJson<PersonModel>(json);
}
} }
mixin PersonModelMappable { mixin PersonModelMappable {
String toJson() {
return PersonModelMapper.ensureInitialized()
.encodeJson<PersonModel>(this as PersonModel);
}
Map<String, dynamic> toMap() {
return PersonModelMapper.ensureInitialized()
.encodeMap<PersonModel>(this as PersonModel);
}
PersonModelCopyWith<PersonModel, PersonModel, PersonModel> get copyWith => PersonModelCopyWith<PersonModel, PersonModel, PersonModel> get copyWith =>
_PersonModelCopyWithImpl(this as PersonModel, $identity, $identity); _PersonModelCopyWithImpl(this as PersonModel, $identity, $identity);
@override
String toString() {
return PersonModelMapper.ensureInitialized()
.stringifyValue(this as PersonModel);
}
} }
extension PersonModelValueCopy<$R, $Out> extension PersonModelValueCopy<$R, $Out>

View file

@ -108,35 +108,12 @@ class PhotoAlbumModelMapper extends SubClassMapperBase<PhotoAlbumModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static PhotoAlbumModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<PhotoAlbumModel>(map);
}
static PhotoAlbumModel fromJson(String json) {
return ensureInitialized().decodeJson<PhotoAlbumModel>(json);
}
} }
mixin PhotoAlbumModelMappable { mixin PhotoAlbumModelMappable {
String toJson() {
return PhotoAlbumModelMapper.ensureInitialized()
.encodeJson<PhotoAlbumModel>(this as PhotoAlbumModel);
}
Map<String, dynamic> toMap() {
return PhotoAlbumModelMapper.ensureInitialized()
.encodeMap<PhotoAlbumModel>(this as PhotoAlbumModel);
}
PhotoAlbumModelCopyWith<PhotoAlbumModel, PhotoAlbumModel, PhotoAlbumModel> PhotoAlbumModelCopyWith<PhotoAlbumModel, PhotoAlbumModel, PhotoAlbumModel>
get copyWith => _PhotoAlbumModelCopyWithImpl( get copyWith => _PhotoAlbumModelCopyWithImpl(
this as PhotoAlbumModel, $identity, $identity); this as PhotoAlbumModel, $identity, $identity);
@override
String toString() {
return PhotoAlbumModelMapper.ensureInitialized()
.stringifyValue(this as PhotoAlbumModel);
}
} }
extension PhotoAlbumModelValueCopy<$R, $Out> extension PhotoAlbumModelValueCopy<$R, $Out>
@ -359,34 +336,11 @@ class PhotoModelMapper extends SubClassMapperBase<PhotoModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static PhotoModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<PhotoModel>(map);
}
static PhotoModel fromJson(String json) {
return ensureInitialized().decodeJson<PhotoModel>(json);
}
} }
mixin PhotoModelMappable { mixin PhotoModelMappable {
String toJson() {
return PhotoModelMapper.ensureInitialized()
.encodeJson<PhotoModel>(this as PhotoModel);
}
Map<String, dynamic> toMap() {
return PhotoModelMapper.ensureInitialized()
.encodeMap<PhotoModel>(this as PhotoModel);
}
PhotoModelCopyWith<PhotoModel, PhotoModel, PhotoModel> get copyWith => PhotoModelCopyWith<PhotoModel, PhotoModel, PhotoModel> get copyWith =>
_PhotoModelCopyWithImpl(this as PhotoModel, $identity, $identity); _PhotoModelCopyWithImpl(this as PhotoModel, $identity, $identity);
@override
String toString() {
return PhotoModelMapper.ensureInitialized()
.stringifyValue(this as PhotoModel);
}
} }
extension PhotoModelValueCopy<$R, $Out> extension PhotoModelValueCopy<$R, $Out>

View file

@ -137,34 +137,11 @@ class SeasonModelMapper extends SubClassMapperBase<SeasonModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static SeasonModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<SeasonModel>(map);
}
static SeasonModel fromJson(String json) {
return ensureInitialized().decodeJson<SeasonModel>(json);
}
} }
mixin SeasonModelMappable { mixin SeasonModelMappable {
String toJson() {
return SeasonModelMapper.ensureInitialized()
.encodeJson<SeasonModel>(this as SeasonModel);
}
Map<String, dynamic> toMap() {
return SeasonModelMapper.ensureInitialized()
.encodeMap<SeasonModel>(this as SeasonModel);
}
SeasonModelCopyWith<SeasonModel, SeasonModel, SeasonModel> get copyWith => SeasonModelCopyWith<SeasonModel, SeasonModel, SeasonModel> get copyWith =>
_SeasonModelCopyWithImpl(this as SeasonModel, $identity, $identity); _SeasonModelCopyWithImpl(this as SeasonModel, $identity, $identity);
@override
String toString() {
return SeasonModelMapper.ensureInitialized()
.stringifyValue(this as SeasonModel);
}
} }
extension SeasonModelValueCopy<$R, $Out> extension SeasonModelValueCopy<$R, $Out>

View file

@ -1,5 +1,6 @@
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:dart_mappable/dart_mappable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
@ -9,8 +10,7 @@ import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/overview_model.dart'; import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/models/items/season_model.dart'; import 'package:fladder/models/items/season_model.dart';
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'series_model.mapper.dart'; part 'series_model.mapper.dart';

View file

@ -135,34 +135,11 @@ class SeriesModelMapper extends SubClassMapperBase<SeriesModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static SeriesModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<SeriesModel>(map);
}
static SeriesModel fromJson(String json) {
return ensureInitialized().decodeJson<SeriesModel>(json);
}
} }
mixin SeriesModelMappable { mixin SeriesModelMappable {
String toJson() {
return SeriesModelMapper.ensureInitialized()
.encodeJson<SeriesModel>(this as SeriesModel);
}
Map<String, dynamic> toMap() {
return SeriesModelMapper.ensureInitialized()
.encodeMap<SeriesModel>(this as SeriesModel);
}
SeriesModelCopyWith<SeriesModel, SeriesModel, SeriesModel> get copyWith => SeriesModelCopyWith<SeriesModel, SeriesModel, SeriesModel> get copyWith =>
_SeriesModelCopyWithImpl(this as SeriesModel, $identity, $identity); _SeriesModelCopyWithImpl(this as SeriesModel, $identity, $identity);
@override
String toString() {
return SeriesModelMapper.ensureInitialized()
.stringifyValue(this as SeriesModel);
}
} }
extension SeriesModelValueCopy<$R, $Out> extension SeriesModelValueCopy<$R, $Out>

View file

@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'trick_play_model.freezed.dart'; part 'trick_play_model.freezed.dart';
part 'trick_play_model.g.dart'; part 'trick_play_model.g.dart';
@freezed @Freezed(copyWith: true)
class TrickPlayModel with _$TrickPlayModel { class TrickPlayModel with _$TrickPlayModel {
factory TrickPlayModel({ factory TrickPlayModel({
required int width, required int width,

View file

@ -225,36 +225,6 @@ class _$TrickPlayModelImpl extends _TrickPlayModel {
return 'TrickPlayModel(width: $width, height: $height, tileWidth: $tileWidth, tileHeight: $tileHeight, thumbnailCount: $thumbnailCount, interval: $interval, images: $images)'; return 'TrickPlayModel(width: $width, height: $height, tileWidth: $tileWidth, tileHeight: $tileHeight, thumbnailCount: $thumbnailCount, interval: $interval, images: $images)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TrickPlayModelImpl &&
(identical(other.width, width) || other.width == width) &&
(identical(other.height, height) || other.height == height) &&
(identical(other.tileWidth, tileWidth) ||
other.tileWidth == tileWidth) &&
(identical(other.tileHeight, tileHeight) ||
other.tileHeight == tileHeight) &&
(identical(other.thumbnailCount, thumbnailCount) ||
other.thumbnailCount == thumbnailCount) &&
(identical(other.interval, interval) ||
other.interval == interval) &&
const DeepCollectionEquality().equals(other._images, _images));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
width,
height,
tileWidth,
tileHeight,
thumbnailCount,
interval,
const DeepCollectionEquality().hash(_images));
/// Create a copy of TrickPlayModel /// Create a copy of TrickPlayModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)

View file

@ -14,7 +14,7 @@ import 'package:fladder/util/map_bool_helper.dart';
part 'library_filters_model.freezed.dart'; part 'library_filters_model.freezed.dart';
part 'library_filters_model.g.dart'; part 'library_filters_model.g.dart';
@freezed @Freezed(copyWith: true)
class LibraryFiltersModel with _$LibraryFiltersModel { class LibraryFiltersModel with _$LibraryFiltersModel {
const LibraryFiltersModel._(); const LibraryFiltersModel._();
@ -40,11 +40,16 @@ class LibraryFiltersModel with _$LibraryFiltersModel {
factory LibraryFiltersModel.fromJson(Map<String, dynamic> json) => _$LibraryFiltersModelFromJson(json); factory LibraryFiltersModel.fromJson(Map<String, dynamic> json) => _$LibraryFiltersModelFromJson(json);
factory LibraryFiltersModel.fromLibrarySearch(String name, LibrarySearchModel searchModel) { factory LibraryFiltersModel.fromLibrarySearch(
String name,
LibrarySearchModel searchModel, {
bool? isFavourite,
String? id,
}) {
return LibraryFiltersModel._internal( return LibraryFiltersModel._internal(
id: Xid().toString(), id: id ?? Xid().toString(),
name: name, name: name,
isFavourite: false, isFavourite: isFavourite ?? false,
ids: searchModel.views.included.map((e) => e.id).toList(), ids: searchModel.views.included.map((e) => e.id).toList(),
genres: searchModel.genres, genres: searchModel.genres,
filters: searchModel.filters, filters: searchModel.filters,

View file

@ -436,59 +436,6 @@ class _$LibraryFiltersModelImpl extends _LibraryFiltersModel {
return 'LibraryFiltersModel._internal(id: $id, name: $name, isFavourite: $isFavourite, ids: $ids, genres: $genres, filters: $filters, studios: $studios, tags: $tags, years: $years, officialRatings: $officialRatings, types: $types, sortingOption: $sortingOption, sortOrder: $sortOrder, favourites: $favourites, hideEmptyShows: $hideEmptyShows, recursive: $recursive, groupBy: $groupBy)'; return 'LibraryFiltersModel._internal(id: $id, name: $name, isFavourite: $isFavourite, ids: $ids, genres: $genres, filters: $filters, studios: $studios, tags: $tags, years: $years, officialRatings: $officialRatings, types: $types, sortingOption: $sortingOption, sortOrder: $sortOrder, favourites: $favourites, hideEmptyShows: $hideEmptyShows, recursive: $recursive, groupBy: $groupBy)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LibraryFiltersModelImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.isFavourite, isFavourite) ||
other.isFavourite == isFavourite) &&
const DeepCollectionEquality().equals(other._ids, _ids) &&
const DeepCollectionEquality().equals(other._genres, _genres) &&
const DeepCollectionEquality().equals(other._filters, _filters) &&
const DeepCollectionEquality().equals(other._studios, _studios) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
const DeepCollectionEquality().equals(other._years, _years) &&
const DeepCollectionEquality()
.equals(other._officialRatings, _officialRatings) &&
const DeepCollectionEquality().equals(other._types, _types) &&
(identical(other.sortingOption, sortingOption) ||
other.sortingOption == sortingOption) &&
(identical(other.sortOrder, sortOrder) ||
other.sortOrder == sortOrder) &&
(identical(other.favourites, favourites) ||
other.favourites == favourites) &&
(identical(other.hideEmptyShows, hideEmptyShows) ||
other.hideEmptyShows == hideEmptyShows) &&
(identical(other.recursive, recursive) ||
other.recursive == recursive) &&
(identical(other.groupBy, groupBy) || other.groupBy == groupBy));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
name,
isFavourite,
const DeepCollectionEquality().hash(_ids),
const DeepCollectionEquality().hash(_genres),
const DeepCollectionEquality().hash(_filters),
const DeepCollectionEquality().hash(_studios),
const DeepCollectionEquality().hash(_tags),
const DeepCollectionEquality().hash(_years),
const DeepCollectionEquality().hash(_officialRatings),
const DeepCollectionEquality().hash(_types),
sortingOption,
sortOrder,
favourites,
hideEmptyShows,
recursive,
groupBy);
/// Create a copy of LibraryFiltersModel /// Create a copy of LibraryFiltersModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)

View file

@ -93,7 +93,7 @@ const _$FladderItemTypeEnumMap = {
}; };
const _$SortingOptionsEnumMap = { const _$SortingOptionsEnumMap = {
SortingOptions.name: 'name', SortingOptions.sortName: 'sortName',
SortingOptions.communityRating: 'communityRating', SortingOptions.communityRating: 'communityRating',
SortingOptions.parentalRating: 'parentalRating', SortingOptions.parentalRating: 'parentalRating',
SortingOptions.dateAdded: 'dateAdded', SortingOptions.dateAdded: 'dateAdded',

View file

@ -77,7 +77,7 @@ class LibrarySearchModel with LibrarySearchModelMappable {
FladderItemType.video: true, FladderItemType.video: true,
}, },
this.favourites = false, this.favourites = false,
this.sortingOption = SortingOptions.name, this.sortingOption = SortingOptions.sortName,
this.sortOrder = SortingOrder.ascending, this.sortOrder = SortingOrder.ascending,
this.hideEmptyShows = true, this.hideEmptyShows = true,
this.recursive = false, this.recursive = false,

View file

@ -94,7 +94,7 @@ class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
v.sortingOption; v.sortingOption;
static const Field<LibrarySearchModel, SortingOptions> _f$sortingOption = static const Field<LibrarySearchModel, SortingOptions> _f$sortingOption =
Field('sortingOption', _$sortingOption, Field('sortingOption', _$sortingOption,
opt: true, def: SortingOptions.name); opt: true, def: SortingOptions.sortName);
static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder; static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder;
static const Field<LibrarySearchModel, SortingOrder> _f$sortOrder = static const Field<LibrarySearchModel, SortingOrder> _f$sortOrder =
Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending); Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending);
@ -177,36 +177,13 @@ class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
@override @override
final Function instantiate = _instantiate; final Function instantiate = _instantiate;
static LibrarySearchModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<LibrarySearchModel>(map);
}
static LibrarySearchModel fromJson(String json) {
return ensureInitialized().decodeJson<LibrarySearchModel>(json);
}
} }
mixin LibrarySearchModelMappable { mixin LibrarySearchModelMappable {
String toJson() {
return LibrarySearchModelMapper.ensureInitialized()
.encodeJson<LibrarySearchModel>(this as LibrarySearchModel);
}
Map<String, dynamic> toMap() {
return LibrarySearchModelMapper.ensureInitialized()
.encodeMap<LibrarySearchModel>(this as LibrarySearchModel);
}
LibrarySearchModelCopyWith<LibrarySearchModel, LibrarySearchModel, LibrarySearchModelCopyWith<LibrarySearchModel, LibrarySearchModel,
LibrarySearchModel> LibrarySearchModel>
get copyWith => _LibrarySearchModelCopyWithImpl( get copyWith => _LibrarySearchModelCopyWithImpl(
this as LibrarySearchModel, $identity, $identity); this as LibrarySearchModel, $identity, $identity);
@override
String toString() {
return LibrarySearchModelMapper.ensureInitialized()
.stringifyValue(this as LibrarySearchModel);
}
} }
extension LibrarySearchModelValueCopy<$R, $Out> extension LibrarySearchModelValueCopy<$R, $Out>

View file

@ -1,14 +1,13 @@
import 'package:fladder/models/item_base_model.dart'; import 'package:flutter/material.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:flutter/material.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/util/localization_helper.dart';
enum SortingOptions { enum SortingOptions {
name([ItemSortBy.name]), sortName([ItemSortBy.sortname]),
communityRating([ItemSortBy.communityrating]), communityRating([ItemSortBy.communityrating]),
// criticsRating([ItemSortBy.criticrating]),
parentalRating([ItemSortBy.officialrating]), parentalRating([ItemSortBy.officialrating]),
dateAdded([ItemSortBy.datecreated]), dateAdded([ItemSortBy.datecreated]),
dateLastContentAdded([ItemSortBy.datelastcontentadded]), dateLastContentAdded([ItemSortBy.datelastcontentadded]),
@ -23,10 +22,10 @@ enum SortingOptions {
const SortingOptions(this.value); const SortingOptions(this.value);
final List<ItemSortBy> value; final List<ItemSortBy> value;
List<ItemSortBy> get toSortBy => [...value, ItemSortBy.name]; List<ItemSortBy> get toSortBy => [...value, ItemSortBy.sortname];
String label(BuildContext context) => switch (this) { String label(BuildContext context) => switch (this) {
name => context.localized.name, sortName => context.localized.name,
communityRating => context.localized.communityRating, communityRating => context.localized.communityRating,
parentalRating => context.localized.parentalRating, parentalRating => context.localized.parentalRating,
dateAdded => context.localized.dateAdded, dateAdded => context.localized.dateAdded,

View file

@ -1,5 +1,7 @@
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:chopper/chopper.dart'; import 'package:chopper/chopper.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -16,6 +18,7 @@ import 'package:fladder/models/items/series_model.dart';
import 'package:fladder/models/items/trick_play_model.dart'; import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/models/playback/direct_playback_model.dart'; import 'package:fladder/models/playback/direct_playback_model.dart';
import 'package:fladder/models/playback/offline_playback_model.dart'; import 'package:fladder/models/playback/offline_playback_model.dart';
import 'package:fladder/models/playback/playback_options_dialogue.dart';
import 'package:fladder/models/playback/transcode_playback_model.dart'; import 'package:fladder/models/playback/transcode_playback_model.dart';
import 'package:fladder/models/syncing/sync_item.dart'; import 'package:fladder/models/syncing/sync_item.dart';
import 'package:fladder/models/video_stream_model.dart'; import 'package:fladder/models/video_stream_model.dart';
@ -31,6 +34,7 @@ import 'package:fladder/util/bitrate_helper.dart';
import 'package:fladder/util/duration_extensions.dart'; import 'package:fladder/util/duration_extensions.dart';
import 'package:fladder/util/list_extensions.dart'; import 'package:fladder/util/list_extensions.dart';
import 'package:fladder/util/map_bool_helper.dart'; import 'package:fladder/util/map_bool_helper.dart';
import 'package:fladder/util/streams_selection.dart';
import 'package:fladder/wrappers/media_control_wrapper.dart'; import 'package:fladder/wrappers/media_control_wrapper.dart';
class Media { class Media {
@ -48,10 +52,10 @@ extension PlaybackModelExtension on PlaybackModel? {
AudioStreamModel? get defaultAudioStream => AudioStreamModel? get defaultAudioStream =>
this?.audioStreams?.firstWhereOrNull((element) => element.index == this?.mediaStreams?.defaultAudioStreamIndex); this?.audioStreams?.firstWhereOrNull((element) => element.index == this?.mediaStreams?.defaultAudioStreamIndex);
String? get label => switch (this) { String? label(BuildContext context) => switch (this) {
DirectPlaybackModel _ => PlaybackType.directStream.name, DirectPlaybackModel _ => PlaybackType.directStream.name(context),
TranscodePlaybackModel _ => PlaybackType.transcode.name, TranscodePlaybackModel _ => PlaybackType.transcode.name(context),
OfflinePlaybackModel _ => PlaybackType.offline.name, OfflinePlaybackModel _ => PlaybackType.offline.name(context),
_ => null _ => null
}; };
} }
@ -117,13 +121,14 @@ class PlaybackModelHelper {
ref.read(videoPlayerProvider).pause(); ref.read(videoPlayerProvider).pause();
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(buffering: true)); ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(buffering: true));
final currentModel = ref.read(playBackModel); final currentModel = ref.read(playBackModel);
final newModel = (await createServerPlaybackModel( final newModel = (await createPlaybackModel(
newItem,
null, null,
newItem,
oldModel: currentModel, oldModel: currentModel,
)) ?? )) ??
await createOfflinePlaybackModel( await _createOfflinePlaybackModel(
newItem, newItem,
null,
ref.read(syncProvider.notifier).getSyncedItem(newItem), ref.read(syncProvider.notifier).getSyncedItem(newItem),
oldModel: currentModel, oldModel: currentModel,
); );
@ -132,8 +137,9 @@ class PlaybackModelHelper {
return newModel; return newModel;
} }
Future<OfflinePlaybackModel?> createOfflinePlaybackModel( Future<OfflinePlaybackModel?> _createOfflinePlaybackModel(
ItemBaseModel item, ItemBaseModel item,
MediaStreamsModel? streamModel,
SyncedItem? syncedItem, { SyncedItem? syncedItem, {
PlaybackModel? oldModel, PlaybackModel? oldModel,
}) async { }) async {
@ -156,53 +162,121 @@ class PlaybackModelHelper {
); );
} }
Future<EpisodeModel?> getNextUpEpisode(String itemId) async { Future<PlaybackModel?> createPlaybackModel(
final response = await api.showsNextUpGet(parentId: itemId, fields: [ItemFields.overview]); BuildContext? context,
final episode = response.body?.items?.firstOrNull; ItemBaseModel? item, {
if (episode == null) { PlaybackModel? oldModel,
return null; List<ItemBaseModel>? libraryQueue,
bool showPlaybackOptions = false,
Duration? startPosition,
}) async {
if (item == null) return null;
final userId = ref.read(userProvider)?.id;
if (userId?.isEmpty == true) return null;
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item);
final firstItemToPlay = switch (item) {
SeriesModel _ || SeasonModel _ => (queue.whereType<EpisodeModel>().toList().nextUp),
_ => item,
};
if (firstItemToPlay == null) return null;
final fullItem = (await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id)).body;
if (fullItem == null) return null;
SyncedItem? syncedItem = ref.read(syncProvider.notifier).getSyncedItem(fullItem);
final firstItemIsSynced = syncedItem != null && syncedItem.status == SyncStatus.complete;
final options = {
PlaybackType.directStream,
PlaybackType.transcode,
if (firstItemIsSynced) PlaybackType.offline,
};
if ((showPlaybackOptions || firstItemIsSynced) && context != null) {
final playbackType = await showPlaybackTypeSelection(
context: context,
options: options,
);
if (!context.mounted) return null;
return switch (playbackType) {
PlaybackType.directStream || PlaybackType.transcode => await _createServerPlaybackModel(
fullItem,
item.streamModel,
playbackType,
oldModel: oldModel,
libraryQueue: queue,
startPosition: startPosition,
),
PlaybackType.offline => await _createOfflinePlaybackModel(
fullItem,
item.streamModel,
syncedItem,
),
null => null
};
} else { } else {
return EpisodeModel.fromBaseDto(episode, ref); return (await _createServerPlaybackModel(
fullItem,
item.streamModel,
PlaybackType.directStream,
startPosition: startPosition,
oldModel: oldModel,
libraryQueue: queue,
)) ??
await _createOfflinePlaybackModel(
fullItem,
item.streamModel,
syncedItem,
);
} }
} }
Future<PlaybackModel?> createServerPlaybackModel( Future<PlaybackModel?> _createServerPlaybackModel(
ItemBaseModel? item, ItemBaseModel item,
MediaStreamsModel? streamModel,
PlaybackType? type, { PlaybackType? type, {
PlaybackModel? oldModel, PlaybackModel? oldModel,
List<ItemBaseModel>? libraryQueue, required List<ItemBaseModel> libraryQueue,
Duration? startPosition, Duration? startPosition,
}) async { }) async {
try { try {
if (item == null) return null;
final userId = ref.read(userProvider)?.id; final userId = ref.read(userProvider)?.id;
if (userId?.isEmpty == true) return null; if (userId?.isEmpty == true) return null;
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item); final newStreamModel = streamModel ?? item.streamModel;
final firstItemToPlay = switch (item) {
SeriesModel _ || SeasonModel _ => (await getNextUpEpisode(item.id) ?? queue.first),
_ => item,
};
final fullItem = await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id);
Map<Bitrate, bool> qualityOptions = getVideoQualityOptions( Map<Bitrate, bool> qualityOptions = getVideoQualityOptions(
VideoQualitySettings( VideoQualitySettings(
maxBitRate: ref.read(videoPlayerSettingsProvider.select((value) => value.maxHomeBitrate)), maxBitRate: ref.read(videoPlayerSettingsProvider.select((value) => value.maxHomeBitrate)),
videoBitRate: firstItemToPlay.streamModel?.videoStreams.firstOrNull?.bitRate ?? 0, videoBitRate: newStreamModel?.videoStreams.firstOrNull?.bitRate ?? 0,
videoCodec: firstItemToPlay.streamModel?.videoStreams.firstOrNull?.codec, videoCodec: newStreamModel?.videoStreams.firstOrNull?.codec,
), ),
); );
final streamModel = firstItemToPlay.streamModel; final audioStreamIndex = selectAudioStream(
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberAudioSelections ?? true)),
oldModel?.mediaStreams?.currentAudioStream,
newStreamModel?.audioStreams,
newStreamModel?.defaultAudioStreamIndex);
final subStreamIndex = selectSubStream(
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberSubtitleSelections ?? true)),
oldModel?.mediaStreams?.currentSubStream,
newStreamModel?.subStreams,
newStreamModel?.defaultSubStreamIndex);
final Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost( final Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
itemId: firstItemToPlay.id, itemId: item.id,
body: PlaybackInfoDto( body: PlaybackInfoDto(
startTimeTicks: startPosition?.toRuntimeTicks, startTimeTicks: startPosition?.toRuntimeTicks,
audioStreamIndex: streamModel?.defaultAudioStreamIndex, audioStreamIndex: audioStreamIndex,
subtitleStreamIndex: streamModel?.defaultSubStreamIndex, subtitleStreamIndex: subStreamIndex,
enableTranscoding: true, enableTranscoding: true,
autoOpenLiveStream: true, autoOpenLiveStream: true,
deviceProfile: ref.read(videoProfileProvider), deviceProfile: ref.read(videoProfileProvider),
@ -210,7 +284,7 @@ class PlaybackModelHelper {
enableDirectPlay: type != PlaybackType.transcode, enableDirectPlay: type != PlaybackType.transcode,
enableDirectStream: type != PlaybackType.transcode, enableDirectStream: type != PlaybackType.transcode,
maxStreamingBitrate: qualityOptions.enabledFirst.keys.firstOrNull?.bitRate, maxStreamingBitrate: qualityOptions.enabledFirst.keys.firstOrNull?.bitRate,
mediaSourceId: streamModel?.currentVersionStream?.id, mediaSourceId: newStreamModel?.currentVersionStream?.id,
), ),
); );
@ -218,18 +292,18 @@ class PlaybackModelHelper {
if (playbackInfo == null) return null; if (playbackInfo == null) return null;
final mediaSource = playbackInfo.mediaSources?[streamModel?.versionStreamIndex ?? 0]; final mediaSource = playbackInfo.mediaSources?[newStreamModel?.versionStreamIndex ?? 0];
if (mediaSource == null) return null; if (mediaSource == null) return null;
final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(playbackInfo.mediaSources, ref).copyWith( final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(playbackInfo.mediaSources, ref).copyWith(
defaultAudioStreamIndex: streamModel?.defaultAudioStreamIndex, defaultAudioStreamIndex: audioStreamIndex,
defaultSubStreamIndex: streamModel?.defaultSubStreamIndex, defaultSubStreamIndex: subStreamIndex,
); );
final mediaSegments = await api.mediaSegmentsGet(id: item.id); final mediaSegments = await api.mediaSegmentsGet(id: item.id);
final trickPlay = (await api.getTrickPlay(item: fullItem.body, ref: ref))?.body; final trickPlay = (await api.getTrickPlay(item: item, ref: ref))?.body;
final chapters = fullItem.body?.overview.chapters ?? []; final chapters = item.overview.chapters ?? [];
final mediaPath = isValidVideoUrl(mediaSource.path ?? ""); final mediaPath = isValidVideoUrl(mediaSource.path ?? "");
@ -252,8 +326,8 @@ class PlaybackModelHelper {
final playbackUrl = joinAll([ref.read(userProvider)!.server, "Videos", mediaSource.id!, "stream?$params"]); final playbackUrl = joinAll([ref.read(userProvider)!.server, "Videos", mediaSource.id!, "stream?$params"]);
return DirectPlaybackModel( return DirectPlaybackModel(
item: fullItem.body ?? item, item: item,
queue: queue, queue: libraryQueue,
mediaSegments: mediaSegments?.body, mediaSegments: mediaSegments?.body,
chapters: chapters, chapters: chapters,
playbackInfo: playbackInfo, playbackInfo: playbackInfo,
@ -264,8 +338,8 @@ class PlaybackModelHelper {
); );
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) { } else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
return TranscodePlaybackModel( return TranscodePlaybackModel(
item: fullItem.body ?? item, item: item,
queue: queue, queue: libraryQueue,
mediaSegments: mediaSegments?.body, mediaSegments: mediaSegments?.body,
chapters: chapters, chapters: chapters,
trickPlay: trickPlay, trickPlay: trickPlay,
@ -328,8 +402,16 @@ class PlaybackModelHelper {
final currentPosition = ref.read(mediaPlaybackProvider.select((value) => value.position)); final currentPosition = ref.read(mediaPlaybackProvider.select((value) => value.position));
final audioIndex = playbackModel.mediaStreams?.defaultAudioStreamIndex; final audioIndex = selectAudioStream(
final subIndex = playbackModel.mediaStreams?.defaultSubStreamIndex; ref.read(userProvider.select((value) => value?.userConfiguration?.rememberAudioSelections ?? true)),
playbackModel.mediaStreams?.currentAudioStream,
playbackModel.audioStreams,
playbackModel.mediaStreams?.defaultAudioStreamIndex);
final subIndex = selectSubStream(
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberSubtitleSelections ?? true)),
playbackModel.mediaStreams?.currentSubStream,
playbackModel.subStreams,
playbackModel.mediaStreams?.defaultSubStreamIndex);
Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost( Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
itemId: item.id, itemId: item.id,

View file

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:fladder/models/video_stream_model.dart';
import 'package:fladder/screens/shared/adaptive_dialog.dart';
import 'package:fladder/util/localization_helper.dart';
Future<PlaybackType?> showPlaybackTypeSelection({
required BuildContext context,
required Set<PlaybackType> options,
}) async {
PlaybackType? playbackType;
await showDialogAdaptive(
context: context,
builder: (context) {
return PlaybackDialogue(
options: options,
onClose: (type) {
playbackType = type;
Navigator.of(context).pop();
},
);
},
);
return playbackType;
}
class PlaybackDialogue extends StatelessWidget {
final Set<PlaybackType> options;
final Function(PlaybackType type) onClose;
const PlaybackDialogue({required this.options, required this.onClose, super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16).add(const EdgeInsets.only(top: 16, bottom: 8)),
child: Text(
context.localized.playbackType,
style: Theme.of(context).textTheme.titleLarge,
),
),
const Divider(),
...options.map((type) => ListTile(
title: Text(type.name(context)),
leading: Icon(type.icon),
onTap: () {
onClose(type);
},
))
],
);
}
}

View file

@ -1,20 +1,66 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/util/localization_helper.dart';
sealed class NameSwitch {
const NameSwitch();
String label(BuildContext context);
}
class NextUp extends NameSwitch {
const NextUp();
@override
String label(BuildContext context) => context.localized.nextUp;
}
class Latest extends NameSwitch {
const Latest();
@override
String label(BuildContext context) => context.localized.latest;
}
class Other extends NameSwitch {
final String customLabel;
const Other(this.customLabel);
@override
String label(BuildContext context) => customLabel;
}
extension RecommendationTypeExtenstion on RecommendationType {
String label(BuildContext context) => switch (this) {
RecommendationType.similartorecentlyplayed => context.localized.similarToRecentlyPlayed,
RecommendationType.similartolikeditem => context.localized.similarToLikedItem,
RecommendationType.hasdirectorfromrecentlyplayed => context.localized.hasDirectorFromRecentlyPlayed,
RecommendationType.hasactorfromrecentlyplayed => context.localized.hasActorFromRecentlyPlayed,
RecommendationType.haslikeddirector => context.localized.hasLikedDirector,
RecommendationType.haslikedactor => context.localized.hasLikedActor,
_ => "",
};
}
class RecommendedModel { class RecommendedModel {
final String name; final NameSwitch name;
final List<ItemBaseModel> posters; final List<ItemBaseModel> posters;
final String type; final RecommendationType? type;
RecommendedModel({ RecommendedModel({
required this.name, required this.name,
required this.posters, required this.posters,
required this.type, this.type,
}); });
RecommendedModel copyWith({ RecommendedModel copyWith({
String? name, NameSwitch? name,
List<ItemBaseModel>? posters, List<ItemBaseModel>? posters,
String? type, RecommendationType? type,
}) { }) {
return RecommendedModel( return RecommendedModel(
name: name ?? this.name, name: name ?? this.name,
@ -22,4 +68,12 @@ class RecommendedModel {
type: type ?? this.type, type: type ?? this.type,
); );
} }
factory RecommendedModel.fromBaseDto(RecommendationDto e, Ref ref) {
return RecommendedModel(
name: Other(e.baselineItemName ?? ""),
posters: e.items?.map((e) => ItemBaseModel.fromBaseDto(e, ref)).toList() ?? [],
type: e.recommendationType,
);
}
} }

View file

@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'arguments_model.freezed.dart';
@freezed
class ArgumentsModel with _$ArgumentsModel {
const ArgumentsModel._();
factory ArgumentsModel({
@Default(false) bool htpcMode,
}) = _ArgumentsModel;
factory ArgumentsModel.fromArguments(List<String> arguments) {
arguments = arguments.map((e) => e.trim()).toList();
return ArgumentsModel(
htpcMode: arguments.contains('--htpc'),
);
}
}

View file

@ -0,0 +1,43 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'arguments_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$ArgumentsModel {
bool get htpcMode => throw _privateConstructorUsedError;
}
/// @nodoc
class _$ArgumentsModelImpl extends _ArgumentsModel {
_$ArgumentsModelImpl({this.htpcMode = false}) : super._();
@override
@JsonKey()
final bool htpcMode;
@override
String toString() {
return 'ArgumentsModel(htpcMode: $htpcMode)';
}
}
abstract class _ArgumentsModel extends ArgumentsModel {
factory _ArgumentsModel({final bool htpcMode}) = _$ArgumentsModelImpl;
_ArgumentsModel._() : super._();
@override
bool get htpcMode;
}

View file

@ -11,7 +11,7 @@ import 'package:fladder/util/custom_color_themes.dart';
part 'client_settings_model.freezed.dart'; part 'client_settings_model.freezed.dart';
part 'client_settings_model.g.dart'; part 'client_settings_model.g.dart';
@freezed @Freezed(copyWith: true)
class ClientSettingsModel with _$ClientSettingsModel { class ClientSettingsModel with _$ClientSettingsModel {
const ClientSettingsModel._(); const ClientSettingsModel._();
factory ClientSettingsModel({ factory ClientSettingsModel({
@ -32,7 +32,11 @@ class ClientSettingsModel with _$ClientSettingsModel {
@Default(false) bool mouseDragSupport, @Default(false) bool mouseDragSupport,
@Default(true) bool requireWifi, @Default(true) bool requireWifi,
@Default(false) bool showAllCollectionTypes, @Default(false) bool showAllCollectionTypes,
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant, @Default(2) int maxConcurrentDownloads,
@Default(DynamicSchemeVariant.rainbow) DynamicSchemeVariant schemeVariant,
@Default(true) bool backgroundPosters,
@Default(true) bool checkForUpdates,
String? lastViewedUpdate,
int? libraryPageSize, int? libraryPageSize,
}) = _ClientSettingsModel; }) = _ClientSettingsModel;

View file

@ -38,7 +38,11 @@ mixin _$ClientSettingsModel {
bool get mouseDragSupport => throw _privateConstructorUsedError; bool get mouseDragSupport => throw _privateConstructorUsedError;
bool get requireWifi => throw _privateConstructorUsedError; bool get requireWifi => throw _privateConstructorUsedError;
bool get showAllCollectionTypes => throw _privateConstructorUsedError; bool get showAllCollectionTypes => throw _privateConstructorUsedError;
int get maxConcurrentDownloads => throw _privateConstructorUsedError;
DynamicSchemeVariant get schemeVariant => throw _privateConstructorUsedError; DynamicSchemeVariant get schemeVariant => throw _privateConstructorUsedError;
bool get backgroundPosters => throw _privateConstructorUsedError;
bool get checkForUpdates => throw _privateConstructorUsedError;
String? get lastViewedUpdate => throw _privateConstructorUsedError;
int? get libraryPageSize => throw _privateConstructorUsedError; int? get libraryPageSize => throw _privateConstructorUsedError;
/// Serializes this ClientSettingsModel to a JSON map. /// Serializes this ClientSettingsModel to a JSON map.
@ -75,7 +79,11 @@ abstract class $ClientSettingsModelCopyWith<$Res> {
bool mouseDragSupport, bool mouseDragSupport,
bool requireWifi, bool requireWifi,
bool showAllCollectionTypes, bool showAllCollectionTypes,
int maxConcurrentDownloads,
DynamicSchemeVariant schemeVariant, DynamicSchemeVariant schemeVariant,
bool backgroundPosters,
bool checkForUpdates,
String? lastViewedUpdate,
int? libraryPageSize}); int? libraryPageSize});
} }
@ -111,7 +119,11 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel>
Object? mouseDragSupport = null, Object? mouseDragSupport = null,
Object? requireWifi = null, Object? requireWifi = null,
Object? showAllCollectionTypes = null, Object? showAllCollectionTypes = null,
Object? maxConcurrentDownloads = null,
Object? schemeVariant = null, Object? schemeVariant = null,
Object? backgroundPosters = null,
Object? checkForUpdates = null,
Object? lastViewedUpdate = freezed,
Object? libraryPageSize = freezed, Object? libraryPageSize = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
@ -183,10 +195,26 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel>
? _value.showAllCollectionTypes ? _value.showAllCollectionTypes
: showAllCollectionTypes // ignore: cast_nullable_to_non_nullable : showAllCollectionTypes // ignore: cast_nullable_to_non_nullable
as bool, as bool,
maxConcurrentDownloads: null == maxConcurrentDownloads
? _value.maxConcurrentDownloads
: maxConcurrentDownloads // ignore: cast_nullable_to_non_nullable
as int,
schemeVariant: null == schemeVariant schemeVariant: null == schemeVariant
? _value.schemeVariant ? _value.schemeVariant
: schemeVariant // ignore: cast_nullable_to_non_nullable : schemeVariant // ignore: cast_nullable_to_non_nullable
as DynamicSchemeVariant, as DynamicSchemeVariant,
backgroundPosters: null == backgroundPosters
? _value.backgroundPosters
: backgroundPosters // ignore: cast_nullable_to_non_nullable
as bool,
checkForUpdates: null == checkForUpdates
? _value.checkForUpdates
: checkForUpdates // ignore: cast_nullable_to_non_nullable
as bool,
lastViewedUpdate: freezed == lastViewedUpdate
? _value.lastViewedUpdate
: lastViewedUpdate // ignore: cast_nullable_to_non_nullable
as String?,
libraryPageSize: freezed == libraryPageSize libraryPageSize: freezed == libraryPageSize
? _value.libraryPageSize ? _value.libraryPageSize
: libraryPageSize // ignore: cast_nullable_to_non_nullable : libraryPageSize // ignore: cast_nullable_to_non_nullable
@ -221,7 +249,11 @@ abstract class _$$ClientSettingsModelImplCopyWith<$Res>
bool mouseDragSupport, bool mouseDragSupport,
bool requireWifi, bool requireWifi,
bool showAllCollectionTypes, bool showAllCollectionTypes,
int maxConcurrentDownloads,
DynamicSchemeVariant schemeVariant, DynamicSchemeVariant schemeVariant,
bool backgroundPosters,
bool checkForUpdates,
String? lastViewedUpdate,
int? libraryPageSize}); int? libraryPageSize});
} }
@ -255,7 +287,11 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res>
Object? mouseDragSupport = null, Object? mouseDragSupport = null,
Object? requireWifi = null, Object? requireWifi = null,
Object? showAllCollectionTypes = null, Object? showAllCollectionTypes = null,
Object? maxConcurrentDownloads = null,
Object? schemeVariant = null, Object? schemeVariant = null,
Object? backgroundPosters = null,
Object? checkForUpdates = null,
Object? lastViewedUpdate = freezed,
Object? libraryPageSize = freezed, Object? libraryPageSize = freezed,
}) { }) {
return _then(_$ClientSettingsModelImpl( return _then(_$ClientSettingsModelImpl(
@ -327,10 +363,26 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res>
? _value.showAllCollectionTypes ? _value.showAllCollectionTypes
: showAllCollectionTypes // ignore: cast_nullable_to_non_nullable : showAllCollectionTypes // ignore: cast_nullable_to_non_nullable
as bool, as bool,
maxConcurrentDownloads: null == maxConcurrentDownloads
? _value.maxConcurrentDownloads
: maxConcurrentDownloads // ignore: cast_nullable_to_non_nullable
as int,
schemeVariant: null == schemeVariant schemeVariant: null == schemeVariant
? _value.schemeVariant ? _value.schemeVariant
: schemeVariant // ignore: cast_nullable_to_non_nullable : schemeVariant // ignore: cast_nullable_to_non_nullable
as DynamicSchemeVariant, as DynamicSchemeVariant,
backgroundPosters: null == backgroundPosters
? _value.backgroundPosters
: backgroundPosters // ignore: cast_nullable_to_non_nullable
as bool,
checkForUpdates: null == checkForUpdates
? _value.checkForUpdates
: checkForUpdates // ignore: cast_nullable_to_non_nullable
as bool,
lastViewedUpdate: freezed == lastViewedUpdate
? _value.lastViewedUpdate
: lastViewedUpdate // ignore: cast_nullable_to_non_nullable
as String?,
libraryPageSize: freezed == libraryPageSize libraryPageSize: freezed == libraryPageSize
? _value.libraryPageSize ? _value.libraryPageSize
: libraryPageSize // ignore: cast_nullable_to_non_nullable : libraryPageSize // ignore: cast_nullable_to_non_nullable
@ -361,7 +413,11 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel
this.mouseDragSupport = false, this.mouseDragSupport = false,
this.requireWifi = true, this.requireWifi = true,
this.showAllCollectionTypes = false, this.showAllCollectionTypes = false,
this.schemeVariant = DynamicSchemeVariant.tonalSpot, this.maxConcurrentDownloads = 2,
this.schemeVariant = DynamicSchemeVariant.rainbow,
this.backgroundPosters = true,
this.checkForUpdates = true,
this.lastViewedUpdate,
this.libraryPageSize}) this.libraryPageSize})
: super._(); : super._();
@ -418,13 +474,24 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel
final bool showAllCollectionTypes; final bool showAllCollectionTypes;
@override @override
@JsonKey() @JsonKey()
final int maxConcurrentDownloads;
@override
@JsonKey()
final DynamicSchemeVariant schemeVariant; final DynamicSchemeVariant schemeVariant;
@override @override
@JsonKey()
final bool backgroundPosters;
@override
@JsonKey()
final bool checkForUpdates;
@override
final String? lastViewedUpdate;
@override
final int? libraryPageSize; final int? libraryPageSize;
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, schemeVariant: $schemeVariant, libraryPageSize: $libraryPageSize)'; return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, maxConcurrentDownloads: $maxConcurrentDownloads, schemeVariant: $schemeVariant, backgroundPosters: $backgroundPosters, checkForUpdates: $checkForUpdates, lastViewedUpdate: $lastViewedUpdate, libraryPageSize: $libraryPageSize)';
} }
@override @override
@ -450,78 +517,15 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel
..add(DiagnosticsProperty('requireWifi', requireWifi)) ..add(DiagnosticsProperty('requireWifi', requireWifi))
..add( ..add(
DiagnosticsProperty('showAllCollectionTypes', showAllCollectionTypes)) DiagnosticsProperty('showAllCollectionTypes', showAllCollectionTypes))
..add(
DiagnosticsProperty('maxConcurrentDownloads', maxConcurrentDownloads))
..add(DiagnosticsProperty('schemeVariant', schemeVariant)) ..add(DiagnosticsProperty('schemeVariant', schemeVariant))
..add(DiagnosticsProperty('backgroundPosters', backgroundPosters))
..add(DiagnosticsProperty('checkForUpdates', checkForUpdates))
..add(DiagnosticsProperty('lastViewedUpdate', lastViewedUpdate))
..add(DiagnosticsProperty('libraryPageSize', libraryPageSize)); ..add(DiagnosticsProperty('libraryPageSize', libraryPageSize));
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ClientSettingsModelImpl &&
(identical(other.syncPath, syncPath) ||
other.syncPath == syncPath) &&
(identical(other.position, position) ||
other.position == position) &&
(identical(other.size, size) || other.size == size) &&
(identical(other.timeOut, timeOut) || other.timeOut == timeOut) &&
(identical(other.nextUpDateCutoff, nextUpDateCutoff) ||
other.nextUpDateCutoff == nextUpDateCutoff) &&
(identical(other.themeMode, themeMode) ||
other.themeMode == themeMode) &&
(identical(other.themeColor, themeColor) ||
other.themeColor == themeColor) &&
(identical(other.amoledBlack, amoledBlack) ||
other.amoledBlack == amoledBlack) &&
(identical(other.blurPlaceHolders, blurPlaceHolders) ||
other.blurPlaceHolders == blurPlaceHolders) &&
(identical(other.blurUpcomingEpisodes, blurUpcomingEpisodes) ||
other.blurUpcomingEpisodes == blurUpcomingEpisodes) &&
(identical(other.selectedLocale, selectedLocale) ||
other.selectedLocale == selectedLocale) &&
(identical(other.enableMediaKeys, enableMediaKeys) ||
other.enableMediaKeys == enableMediaKeys) &&
(identical(other.posterSize, posterSize) ||
other.posterSize == posterSize) &&
(identical(other.pinchPosterZoom, pinchPosterZoom) ||
other.pinchPosterZoom == pinchPosterZoom) &&
(identical(other.mouseDragSupport, mouseDragSupport) ||
other.mouseDragSupport == mouseDragSupport) &&
(identical(other.requireWifi, requireWifi) ||
other.requireWifi == requireWifi) &&
(identical(other.showAllCollectionTypes, showAllCollectionTypes) ||
other.showAllCollectionTypes == showAllCollectionTypes) &&
(identical(other.schemeVariant, schemeVariant) ||
other.schemeVariant == schemeVariant) &&
(identical(other.libraryPageSize, libraryPageSize) ||
other.libraryPageSize == libraryPageSize));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([
runtimeType,
syncPath,
position,
size,
timeOut,
nextUpDateCutoff,
themeMode,
themeColor,
amoledBlack,
blurPlaceHolders,
blurUpcomingEpisodes,
selectedLocale,
enableMediaKeys,
posterSize,
pinchPosterZoom,
mouseDragSupport,
requireWifi,
showAllCollectionTypes,
schemeVariant,
libraryPageSize
]);
/// Create a copy of ClientSettingsModel /// Create a copy of ClientSettingsModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -558,7 +562,11 @@ abstract class _ClientSettingsModel extends ClientSettingsModel {
final bool mouseDragSupport, final bool mouseDragSupport,
final bool requireWifi, final bool requireWifi,
final bool showAllCollectionTypes, final bool showAllCollectionTypes,
final int maxConcurrentDownloads,
final DynamicSchemeVariant schemeVariant, final DynamicSchemeVariant schemeVariant,
final bool backgroundPosters,
final bool checkForUpdates,
final String? lastViewedUpdate,
final int? libraryPageSize}) = _$ClientSettingsModelImpl; final int? libraryPageSize}) = _$ClientSettingsModelImpl;
_ClientSettingsModel._() : super._(); _ClientSettingsModel._() : super._();
@ -601,8 +609,16 @@ abstract class _ClientSettingsModel extends ClientSettingsModel {
@override @override
bool get showAllCollectionTypes; bool get showAllCollectionTypes;
@override @override
int get maxConcurrentDownloads;
@override
DynamicSchemeVariant get schemeVariant; DynamicSchemeVariant get schemeVariant;
@override @override
bool get backgroundPosters;
@override
bool get checkForUpdates;
@override
String? get lastViewedUpdate;
@override
int? get libraryPageSize; int? get libraryPageSize;
/// Create a copy of ClientSettingsModel /// Create a copy of ClientSettingsModel

View file

@ -36,9 +36,14 @@ _$ClientSettingsModelImpl _$$ClientSettingsModelImplFromJson(
mouseDragSupport: json['mouseDragSupport'] as bool? ?? false, mouseDragSupport: json['mouseDragSupport'] as bool? ?? false,
requireWifi: json['requireWifi'] as bool? ?? true, requireWifi: json['requireWifi'] as bool? ?? true,
showAllCollectionTypes: json['showAllCollectionTypes'] as bool? ?? false, showAllCollectionTypes: json['showAllCollectionTypes'] as bool? ?? false,
maxConcurrentDownloads:
(json['maxConcurrentDownloads'] as num?)?.toInt() ?? 2,
schemeVariant: $enumDecodeNullable( schemeVariant: $enumDecodeNullable(
_$DynamicSchemeVariantEnumMap, json['schemeVariant']) ?? _$DynamicSchemeVariantEnumMap, json['schemeVariant']) ??
DynamicSchemeVariant.tonalSpot, DynamicSchemeVariant.rainbow,
backgroundPosters: json['backgroundPosters'] as bool? ?? true,
checkForUpdates: json['checkForUpdates'] as bool? ?? true,
lastViewedUpdate: json['lastViewedUpdate'] as String?,
libraryPageSize: (json['libraryPageSize'] as num?)?.toInt(), libraryPageSize: (json['libraryPageSize'] as num?)?.toInt(),
); );
@ -62,7 +67,11 @@ Map<String, dynamic> _$$ClientSettingsModelImplToJson(
'mouseDragSupport': instance.mouseDragSupport, 'mouseDragSupport': instance.mouseDragSupport,
'requireWifi': instance.requireWifi, 'requireWifi': instance.requireWifi,
'showAllCollectionTypes': instance.showAllCollectionTypes, 'showAllCollectionTypes': instance.showAllCollectionTypes,
'maxConcurrentDownloads': instance.maxConcurrentDownloads,
'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!, 'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!,
'backgroundPosters': instance.backgroundPosters,
'checkForUpdates': instance.checkForUpdates,
'lastViewedUpdate': instance.lastViewedUpdate,
'libraryPageSize': instance.libraryPageSize, 'libraryPageSize': instance.libraryPageSize,
}; };

View file

@ -2,12 +2,13 @@ import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
part 'home_settings_model.freezed.dart'; part 'home_settings_model.freezed.dart';
part 'home_settings_model.g.dart'; part 'home_settings_model.g.dart';
@freezed @Freezed(copyWith: true)
class HomeSettingsModel with _$HomeSettingsModel { class HomeSettingsModel with _$HomeSettingsModel {
factory HomeSettingsModel({ factory HomeSettingsModel({
@Default({...LayoutMode.values}) Set<LayoutMode> screenLayouts, @Default({...LayoutMode.values}) Set<LayoutMode> screenLayouts,
@ -36,42 +37,6 @@ T selectAvailableOrSmaller<T>(T value, Set<T> availableOptions, List<T> allOptio
return availableOptions.first; return availableOptions.first;
} }
enum ViewSize {
phone,
tablet,
desktop;
const ViewSize();
String label(BuildContext context) => switch (this) {
ViewSize.phone => context.localized.phone,
ViewSize.tablet => context.localized.tablet,
ViewSize.desktop => context.localized.desktop,
};
bool operator >(ViewSize other) => index > other.index;
bool operator >=(ViewSize other) => index >= other.index;
bool operator <(ViewSize other) => index < other.index;
bool operator <=(ViewSize other) => index <= other.index;
}
enum LayoutMode {
single,
dual;
const LayoutMode();
String label(BuildContext context) => switch (this) {
LayoutMode.single => context.localized.layoutModeSingle,
LayoutMode.dual => context.localized.layoutModeDual,
};
bool operator >(ViewSize other) => index > other.index;
bool operator >=(ViewSize other) => index >= other.index;
bool operator <(ViewSize other) => index < other.index;
bool operator <=(ViewSize other) => index <= other.index;
}
enum HomeBanner { enum HomeBanner {
hide, hide,
carousel, carousel,

View file

@ -205,32 +205,6 @@ class _$HomeSettingsModelImpl implements _HomeSettingsModel {
return 'HomeSettingsModel(screenLayouts: $screenLayouts, layoutStates: $layoutStates, homeBanner: $homeBanner, carouselSettings: $carouselSettings, nextUp: $nextUp)'; return 'HomeSettingsModel(screenLayouts: $screenLayouts, layoutStates: $layoutStates, homeBanner: $homeBanner, carouselSettings: $carouselSettings, nextUp: $nextUp)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HomeSettingsModelImpl &&
const DeepCollectionEquality()
.equals(other._screenLayouts, _screenLayouts) &&
const DeepCollectionEquality()
.equals(other._layoutStates, _layoutStates) &&
(identical(other.homeBanner, homeBanner) ||
other.homeBanner == homeBanner) &&
(identical(other.carouselSettings, carouselSettings) ||
other.carouselSettings == carouselSettings) &&
(identical(other.nextUp, nextUp) || other.nextUp == nextUp));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_screenLayouts),
const DeepCollectionEquality().hash(_layoutStates),
homeBanner,
carouselSettings,
nextUp);
/// Create a copy of HomeSettingsModel /// Create a copy of HomeSettingsModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)

View file

@ -12,7 +12,7 @@ import 'package:fladder/util/localization_helper.dart';
part 'video_player_settings.freezed.dart'; part 'video_player_settings.freezed.dart';
part 'video_player_settings.g.dart'; part 'video_player_settings.g.dart';
@freezed @Freezed(copyWith: true)
class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel { class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel {
const VideoPlayerSettingsModel._(); const VideoPlayerSettingsModel._();
@ -43,7 +43,10 @@ class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel {
PlayerOptions get wantedPlayer => playerOptions ?? PlayerOptions.platformDefaults; PlayerOptions get wantedPlayer => playerOptions ?? PlayerOptions.platformDefaults;
bool playerSame(VideoPlayerSettingsModel other) { bool playerSame(VideoPlayerSettingsModel other) {
return other.hardwareAccel == hardwareAccel && other.useLibass == useLibass && other.bufferSize == bufferSize && other.wantedPlayer == wantedPlayer; return other.hardwareAccel == hardwareAccel &&
other.useLibass == useLibass &&
other.bufferSize == bufferSize &&
other.wantedPlayer == wantedPlayer;
} }
@override @override

View file

@ -4,17 +4,17 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/images_models.dart'; import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/media_segments_model.dart';
import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/media_segments_model.dart';
import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/items/trick_play_model.dart'; import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/models/syncing/i_synced_item.dart'; import 'package:fladder/models/syncing/i_synced_item.dart';
@ -24,7 +24,7 @@ import 'package:fladder/util/localization_helper.dart';
part 'sync_item.freezed.dart'; part 'sync_item.freezed.dart';
@freezed @Freezed(copyWith: true)
class SyncedItem with _$SyncedItem { class SyncedItem with _$SyncedItem {
const SyncedItem._(); const SyncedItem._();

View file

@ -63,7 +63,6 @@ abstract class $SyncedItemCopyWith<$Res> {
List<SubStreamModel> subtitles, List<SubStreamModel> subtitles,
@UserDataJsonSerializer() UserData? userData}); @UserDataJsonSerializer() UserData? userData});
$MediaSegmentsModelCopyWith<$Res>? get mediaSegments;
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel; $TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
} }
@ -162,20 +161,6 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
) as $Val); ) as $Val);
} }
/// Create a copy of SyncedItem
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$MediaSegmentsModelCopyWith<$Res>? get mediaSegments {
if (_value.mediaSegments == null) {
return null;
}
return $MediaSegmentsModelCopyWith<$Res>(_value.mediaSegments!, (value) {
return _then(_value.copyWith(mediaSegments: value) as $Val);
});
}
/// Create a copy of SyncedItem /// Create a copy of SyncedItem
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@ -216,8 +201,6 @@ abstract class _$$SyncItemImplCopyWith<$Res>
List<SubStreamModel> subtitles, List<SubStreamModel> subtitles,
@UserDataJsonSerializer() UserData? userData}); @UserDataJsonSerializer() UserData? userData});
@override
$MediaSegmentsModelCopyWith<$Res>? get mediaSegments;
@override @override
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel; $TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
} }
@ -392,57 +375,6 @@ class _$SyncItemImpl extends _SyncItem {
return 'SyncedItem(id: $id, syncing: $syncing, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortName: $sortName, fileSize: $fileSize, videoFileName: $videoFileName, mediaSegments: $mediaSegments, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData)'; return 'SyncedItem(id: $id, syncing: $syncing, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortName: $sortName, fileSize: $fileSize, videoFileName: $videoFileName, mediaSegments: $mediaSegments, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SyncItemImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.syncing, syncing) || other.syncing == syncing) &&
(identical(other.parentId, parentId) ||
other.parentId == parentId) &&
(identical(other.userId, userId) || other.userId == userId) &&
(identical(other.path, path) || other.path == path) &&
(identical(other.markedForDelete, markedForDelete) ||
other.markedForDelete == markedForDelete) &&
(identical(other.sortName, sortName) ||
other.sortName == sortName) &&
(identical(other.fileSize, fileSize) ||
other.fileSize == fileSize) &&
(identical(other.videoFileName, videoFileName) ||
other.videoFileName == videoFileName) &&
(identical(other.mediaSegments, mediaSegments) ||
other.mediaSegments == mediaSegments) &&
(identical(other.fTrickPlayModel, fTrickPlayModel) ||
other.fTrickPlayModel == fTrickPlayModel) &&
(identical(other.fImages, fImages) || other.fImages == fImages) &&
const DeepCollectionEquality()
.equals(other._fChapters, _fChapters) &&
const DeepCollectionEquality()
.equals(other._subtitles, _subtitles) &&
(identical(other.userData, userData) ||
other.userData == userData));
}
@override
int get hashCode => Object.hash(
runtimeType,
id,
syncing,
parentId,
userId,
path,
markedForDelete,
sortName,
fileSize,
videoFileName,
mediaSegments,
fTrickPlayModel,
fImages,
const DeepCollectionEquality().hash(_fChapters),
const DeepCollectionEquality().hash(_subtitles),
userData);
/// Create a copy of SyncedItem /// Create a copy of SyncedItem
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)

View file

@ -6,7 +6,7 @@ import 'package:fladder/models/syncing/sync_item.dart';
part 'sync_settings_model.freezed.dart'; part 'sync_settings_model.freezed.dart';
@Freezed(toJson: false, fromJson: false) @Freezed(toJson: false, fromJson: false, copyWith: true)
class SyncSettingsModel with _$SyncSettingsModel { class SyncSettingsModel with _$SyncSettingsModel {
const SyncSettingsModel._(); const SyncSettingsModel._();

View file

@ -116,18 +116,6 @@ class _$SyncSettignsModelImpl extends _SyncSettignsModel {
return 'SyncSettingsModel(items: $items)'; return 'SyncSettingsModel(items: $items)';
} }
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SyncSettignsModelImpl &&
const DeepCollectionEquality().equals(other._items, _items));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(_items));
/// Create a copy of SyncSettingsModel /// Create a copy of SyncSettingsModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)

View file

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/chapters_model.dart';
@ -12,6 +12,7 @@ import 'package:fladder/models/items/media_segments_model.dart';
import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/syncing/sync_item.dart'; import 'package:fladder/models/syncing/sync_item.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/util/localization_helper.dart';
enum PlaybackType { enum PlaybackType {
directStream, directStream,
@ -24,16 +25,11 @@ enum PlaybackType {
PlaybackType.transcode => IconsaxPlusLinear.convert, PlaybackType.transcode => IconsaxPlusLinear.convert,
}; };
String get name { String name(BuildContext context) => switch (this) {
switch (this) { PlaybackType.directStream => context.localized.playbackTypeDirect,
case PlaybackType.directStream: PlaybackType.offline => context.localized.playbackTypeOffline,
return "Direct"; PlaybackType.transcode => context.localized.playbackTypeTranscode
case PlaybackType.offline: };
return "Offline";
case PlaybackType.transcode:
return "Transcoding";
}
}
} }
class VideoPlayback { class VideoPlayback {

Some files were not shown because too many files have changed in this diff Show more