Merge branch 'DonutWare:develop' into develop
2
.fvmrc
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"flutter": "3.29.2"
|
||||
"flutter": "3.32.1"
|
||||
}
|
||||
302
.github/workflows/build.yml
vendored
|
|
@ -14,19 +14,87 @@ on:
|
|||
types:
|
||||
- opened
|
||||
- reopened
|
||||
schedule:
|
||||
# Run nightly at midnight UTC, but only if there were changes to develop
|
||||
- cron: "0 0 * * *"
|
||||
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:
|
||||
# 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:
|
||||
needs: [check-trigger]
|
||||
if: needs.check-trigger.outputs.should_run == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version_name: ${{ steps.fetch.outputs.version_name }}
|
||||
flutter_version: ${{ steps.fetch.outputs.flutter_version }}
|
||||
nightly_version: ${{ steps.fetch.outputs.nightly_version }}
|
||||
build_type: ${{ needs.check-trigger.outputs.build_type }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch version name
|
||||
- name: Fetch version name and create nightly version
|
||||
id: fetch
|
||||
run: |
|
||||
# Extract version_name from pubspec.yaml
|
||||
|
|
@ -36,29 +104,47 @@ jobs:
|
|||
# Extract flutter_version from .fvmrc
|
||||
FLUTTER_VERSION=$(jq -r '.flutter' .fvmrc)
|
||||
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
|
||||
|
||||
build-android:
|
||||
needs: [fetch-info]
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
artifact_suffix: ${{ env.ARTIFACT_SUFFIX }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Determine Build Type
|
||||
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 "ARTIFACT_SUFFIX=release-signed" >> $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
|
||||
echo "BUILD_MODE=profile" >> $GITHUB_ENV
|
||||
echo "ARTIFACT_SUFFIX=production" >> $GITHUB_ENV
|
||||
echo "AAB_PATH=productionProfile" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "ARTIFACT_SUFFIX=${ARTIFACT_SUFFIX}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Decode Keystore for release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: needs.fetch-info.outputs.build_type == 'release'
|
||||
env:
|
||||
ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
|
||||
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
|
||||
|
|
@ -84,10 +170,10 @@ jobs:
|
|||
check-latest: true
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2.16.0
|
||||
uses: subosito/flutter-action@v2.19.0
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
|
@ -121,10 +207,10 @@ jobs:
|
|||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2.16.0
|
||||
uses: subosito/flutter-action@v2.19.0
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
|
@ -141,7 +227,7 @@ jobs:
|
|||
path: windows/windows_setup.iss
|
||||
options: /O+
|
||||
env:
|
||||
FLADDER_VERSION: ${{needs.fetch-info.outputs.version_name}}
|
||||
FLADDER_VERSION: ${{ needs.fetch-info.outputs.version_name }}
|
||||
|
||||
- name: Archive Windows portable artifact
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
|
|
@ -164,10 +250,10 @@ jobs:
|
|||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2.16.0
|
||||
uses: subosito/flutter-action@v2.19.0
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
|
@ -184,7 +270,6 @@ jobs:
|
|||
mkdir Payload
|
||||
mv Runner.app Payload/
|
||||
zip -r iOS.ipa Payload
|
||||
|
||||
- name: Archive iOS IPA artifact
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
with:
|
||||
|
|
@ -200,10 +285,10 @@ jobs:
|
|||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2.16.0
|
||||
uses: subosito/flutter-action@v2.19.0
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
|
@ -214,8 +299,16 @@ jobs:
|
|||
- name: Build macOS app
|
||||
run: flutter build macos --flavor production --build-number=${{ github.run_number }}
|
||||
|
||||
- name: Create DMG file
|
||||
run: hdiutil create -format UDZO -srcfolder build/macos/Build/Products/Release-production/fladder.app build/macos/Build/Products/Release-production/macOS.dmg
|
||||
- name: Ensure correct app name casing
|
||||
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
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
|
|
@ -232,10 +325,10 @@ jobs:
|
|||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2.16.0
|
||||
uses: subosito/flutter-action@v2.19.0
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
|
@ -271,7 +364,8 @@ jobs:
|
|||
|
||||
- name: Build AppImage
|
||||
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
|
||||
chmod +x appimage-builder-x86_64.AppImage
|
||||
sudo mv appimage-builder-x86_64.AppImage /usr/local/bin/appimage-builder
|
||||
|
|
@ -286,9 +380,8 @@ jobs:
|
|||
Fladder_x86_64.AppImage.zsync
|
||||
|
||||
build-linux-flatpak:
|
||||
name: "Flatpak"
|
||||
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]
|
||||
container:
|
||||
image: bilelmoussaoui/flatpak-github-actions:gnome-46
|
||||
|
|
@ -325,10 +418,10 @@ jobs:
|
|||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2.16.0
|
||||
uses: subosito/flutter-action@v2.19.0
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||
flutter-version: ${{needs.fetch-info.outputs.flutter_version}}
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
|
@ -347,10 +440,11 @@ jobs:
|
|||
path: build/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
|
||||
|
||||
- name: Archive web pages artifact
|
||||
if: needs.fetch-info.outputs.build_type == 'release'
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
with:
|
||||
name: fladder-web-pages
|
||||
|
|
@ -368,8 +462,93 @@ jobs:
|
|||
- build-linux-flatpak
|
||||
- build-web
|
||||
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:
|
||||
- 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
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
|
@ -378,8 +557,8 @@ jobs:
|
|||
|
||||
- name: Move Android
|
||||
run: |
|
||||
mv fladder-android/release-signed.apk Fladder-Android-${{needs.fetch-info.outputs.version_name}}.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 }}.apk Fladder-Android-${{ steps.version.outputs.version }}.apk
|
||||
mv fladder-android/${{ needs.build-android.outputs.artifact_suffix }}.aab Fladder-Android-${{ steps.version.outputs.version }}.aab
|
||||
|
||||
- name: Download Windows portable artifact
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
@ -390,7 +569,7 @@ jobs:
|
|||
- name: Compress Windows
|
||||
run: |
|
||||
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
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
@ -399,7 +578,7 @@ jobs:
|
|||
path: fladder-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
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
@ -408,7 +587,7 @@ jobs:
|
|||
path: fladder-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
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
@ -417,7 +596,7 @@ jobs:
|
|||
path: fladder-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
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
@ -428,16 +607,18 @@ jobs:
|
|||
- name: Compress Linux
|
||||
run: |
|
||||
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
|
||||
if: needs.fetch-info.outputs.build_type == 'release'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: fladder-linux-flatpak
|
||||
path: fladder-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
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
@ -445,11 +626,10 @@ jobs:
|
|||
name: fladder-linux-appimage
|
||||
path: fladder-linux-appimage
|
||||
|
||||
- name: Move linux AppImages
|
||||
- name: Move Linux AppImages
|
||||
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.zsync Fladder-Linux-${{needs.fetch-info.outputs.version_name}}.AppImage.zsync
|
||||
|
||||
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-${{ steps.version.outputs.version }}.AppImage.zsync
|
||||
- name: Download Artifacts Web
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
|
@ -459,32 +639,50 @@ jobs:
|
|||
- name: Compress Web
|
||||
run: |
|
||||
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
|
||||
with:
|
||||
draft: true
|
||||
fail_on_unmatched_files: true
|
||||
generate_release_notes: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: |
|
||||
Fladder-Android-${{needs.fetch-info.outputs.version_name}}.apk
|
||||
Fladder-Windows-${{needs.fetch-info.outputs.version_name}}-Setup.exe
|
||||
Fladder-Windows-${{needs.fetch-info.outputs.version_name}}.zip
|
||||
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
|
||||
Fladder-Linux-${{ steps.version.outputs.version }}.flatpak
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release_web:
|
||||
name: Release Web
|
||||
needs:
|
||||
- fetch-info
|
||||
- create_release
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: needs.fetch-info.outputs.build_type == 'release'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
|
|
|||
5
.gitignore
vendored
|
|
@ -5,6 +5,8 @@
|
|||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.dmg_temp/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
|
@ -73,3 +75,6 @@ local.properties
|
|||
|
||||
# Inno Setup builds
|
||||
Output
|
||||
|
||||
# Generated localizations
|
||||
lib/l10n/generated
|
||||
|
|
|
|||
19
.vscode/launch.json
vendored
|
|
@ -10,7 +10,18 @@
|
|||
"type": "dart",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"development"
|
||||
"development",
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Fladder Development HTPC (debug)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"development",
|
||||
"-a",
|
||||
"--htpc",
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -69,7 +80,8 @@
|
|||
"deviceId": "chrome",
|
||||
"args": [
|
||||
"--web-port",
|
||||
"9090"
|
||||
"9090",
|
||||
"--web-experimental-hot-reload"
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -80,7 +92,8 @@
|
|||
"flutterMode": "release",
|
||||
"args": [
|
||||
"--web-port",
|
||||
"9090"
|
||||
"9090",
|
||||
"--web-experimental-hot-reload"
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
12
.vscode/settings.json
vendored
|
|
@ -7,7 +7,7 @@
|
|||
"LTWH",
|
||||
"outro"
|
||||
],
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.29.2",
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.32.1",
|
||||
"search.exclude": {
|
||||
"**/.fvm": true
|
||||
},
|
||||
|
|
@ -15,5 +15,13 @@
|
|||
"**/.fvm": 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
|
|
@ -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/
|
||||
|
|
@ -16,6 +16,7 @@ Platform-specific installation instructions can be found in this document.
|
|||
- [iOS](#iosipados)
|
||||
- [Sideloadly](#sideloadly)
|
||||
- [Docker](#docker)
|
||||
- [Web](#web)
|
||||
|
||||
|
||||
## Windows
|
||||
|
|
@ -140,3 +141,10 @@ Run `docker-compose up -d` to start the container. It will be available on `http
|
|||
> [!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.
|
||||
|
||||
## 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)
|
||||
|
||||
|
|
|
|||
11
README.md
|
|
@ -53,11 +53,14 @@
|
|||
<details close>
|
||||
<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/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/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_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/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">
|
||||
</details>
|
||||
|
||||
|
|
@ -65,8 +68,14 @@
|
|||
<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/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/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>
|
||||
|
||||
Web/Desktop [try out the web build!](https://DonutWare.github.io/Fladder)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ include: package:lints/recommended.yaml
|
|||
analyzer:
|
||||
exclude:
|
||||
- build/**
|
||||
- .dart_tool/**
|
||||
- .fvm/**
|
||||
- pubspec.yaml
|
||||
- packages/**
|
||||
- lib/jellyfin/**
|
||||
- lib/**/**.g.dart
|
||||
- lib/**/**.freezed.dart
|
||||
- lib/**/**.mapped.dart
|
||||
- packages/**
|
||||
- pubspec.yaml
|
||||
- lib/l10n/**
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
implicit-dynamic: false
|
||||
|
|
|
|||
BIN
assets/macos-dmg/Fladder-DMG-Background.afphoto
Normal file
BIN
assets/macos-dmg/Fladder-DMG-Background.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 732 KiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 731 KiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
BIN
assets/marketing/screenshots/Mobile/Library_Search.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 895 KiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 996 KiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 930 KiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 4.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 3.3 MiB |
BIN
assets/marketing/screenshots/Tablet/Details_2.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
assets/marketing/screenshots/Tablet/Favourites.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/marketing/screenshots/Tablet/Library.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
assets/marketing/screenshots/Tablet/Library_Search.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/marketing/screenshots/Tablet/Player.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
assets/marketing/screenshots/Tablet/Resume_Tab.png
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 1.1 MiB |
|
|
@ -27,6 +27,9 @@ targets:
|
|||
- "**/**_page.dart"
|
||||
freezed|freezed:
|
||||
options:
|
||||
copy_with: false
|
||||
equal: false
|
||||
tostring:
|
||||
generate_for:
|
||||
- "**/**.f.dart"
|
||||
- "**/**.g.dart"
|
||||
|
|
@ -34,7 +37,7 @@ targets:
|
|||
options:
|
||||
ignoreNull: true
|
||||
discriminatorKey: type
|
||||
generateMethods: [decode, encode, copy, stringify]
|
||||
generateMethods: [copy]
|
||||
chopper_generator:
|
||||
options:
|
||||
header: "//Generated jellyfin api code"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
id: nl.jknaapen.fladder
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: '46'
|
||||
runtime-version: '48'
|
||||
sdk: org.gnome.Sdk
|
||||
|
||||
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:
|
||||
# X11/Wayland access
|
||||
- --share=ipc
|
||||
|
|
@ -15,6 +24,8 @@ finish-args:
|
|||
- --share=network
|
||||
# File access for downloads/media
|
||||
- --filesystem=home
|
||||
# File access for downloads temporary directory
|
||||
- --filesystem=/tmp
|
||||
# Allow access to PipeWire socket for mpv
|
||||
- --filesystem=xdg-run/pipewire-0:ro
|
||||
# Hardware acceleration
|
||||
|
|
@ -61,42 +72,12 @@ modules:
|
|||
sources:
|
||||
- type: git
|
||||
url: https://github.com/FFmpeg/nv-codec-headers.git
|
||||
tag: n12.2.72.0
|
||||
commit: c69278340ab1d5559c7d7bf0edf615dc33ddbba7
|
||||
tag: n13.0.19.0
|
||||
commit: e844e5b26f46bb77479f063029595293aa8f812d
|
||||
x-checker-data:
|
||||
type: git
|
||||
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
|
||||
|
||||
- name: libass
|
||||
config-opts:
|
||||
- --enable-shared
|
||||
|
|
@ -113,7 +94,6 @@ modules:
|
|||
x-checker-data:
|
||||
type: git
|
||||
tag-pattern: ^([\d.]+)$
|
||||
|
||||
- name: uchardet
|
||||
buildsystem: cmake-ninja
|
||||
config-opts:
|
||||
|
|
@ -134,15 +114,12 @@ modules:
|
|||
url: https://www.freedesktop.org/software/uchardet/releases/
|
||||
version-pattern: uchardet-(\d+\.\d+\.\d+)\.tar\.xz
|
||||
url-template: https://www.freedesktop.org/software/uchardet/releases/uchardet-$version.tar.xz
|
||||
|
||||
- name: libplacebo
|
||||
buildsystem: meson
|
||||
config-opts:
|
||||
- -Dvulkan=enabled
|
||||
- -Dshaderc=enabled
|
||||
cleanup:
|
||||
- /include
|
||||
- /lib/pkgconfig
|
||||
- --libdir=lib
|
||||
sources:
|
||||
- type: git
|
||||
url: https://code.videolan.org/videolan/libplacebo.git
|
||||
|
|
@ -151,49 +128,6 @@ modules:
|
|||
x-checker-data:
|
||||
type: git
|
||||
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
|
||||
buildsystem: meson
|
||||
|
|
@ -215,4 +149,4 @@ modules:
|
|||
- install -Dm644 icons/fladder_icon.svg /app/share/icons/hicolor/scalable/apps/${FLATPAK_ID}.svg
|
||||
sources:
|
||||
- type: dir
|
||||
path: ..
|
||||
path: ..
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@ arb-dir: lib/l10n
|
|||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
||||
nullable-getter: false
|
||||
synthetic-package: false
|
||||
output-dir: lib/l10n/generated
|
||||
|
|
|
|||
|
|
@ -1284,8 +1284,62 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"settingsPlayerBufferSizeTitle": "حجم ذاكرة التخزين المؤقت للفيديو",
|
||||
"settingsPlayerBufferSizeTitle": "حجم التخزين المؤقت للفيديو",
|
||||
"@settingsPlayerBufferSizeTitle": {},
|
||||
"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
|
|
@ -1290,5 +1290,69 @@
|
|||
"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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -721,6 +721,7 @@
|
|||
"@settingsContinue": {},
|
||||
"settingsEnableOsMediaControls": "Enable OS media controls",
|
||||
"@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": {},
|
||||
"settingsHomeBannerTitle": "Home banner",
|
||||
|
|
@ -1189,6 +1190,7 @@
|
|||
"segmentActionAskToSkip": "Ask to skip",
|
||||
"segmentActionSkip": "Skip",
|
||||
"loading": "Loading",
|
||||
"exitFladderTitle": "Exit Fladder",
|
||||
"castAndCrew": "Cast & Crew",
|
||||
"guestActor": "{count, plural, other{Guest Actors} one{Guest Actor}}",
|
||||
"@guestActor": {
|
||||
|
|
@ -1199,5 +1201,42 @@
|
|||
"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"
|
||||
}
|
||||
|
|
@ -37,5 +37,33 @@
|
|||
"appLockPasscode": "Codice",
|
||||
"@appLockPasscode": {},
|
||||
"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
1265
lib/l10n/app_mt.arb
Normal file
|
|
@ -1284,5 +1284,63 @@
|
|||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1153,5 +1153,37 @@
|
|||
"episodeUnaired": "Niewyemitowany",
|
||||
"@episodeUnaired": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1246,7 +1246,7 @@
|
|||
"@layoutModeDual": {},
|
||||
"copiedToClipboard": "Copiado para a área de transferência",
|
||||
"@copiedToClipboard": {},
|
||||
"internetStreamingQualityDesc": "Qualidade máxima de streaming pela internet (celular)",
|
||||
"internetStreamingQualityDesc": "Qualidade máxima de streaming por dados móveis (celular)",
|
||||
"@internetStreamingQualityDesc": {},
|
||||
"homeStreamingQualityTitle": "Qualidade na rede local",
|
||||
"@homeStreamingQualityTitle": {},
|
||||
|
|
@ -1289,5 +1289,67 @@
|
|||
"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
|
|
@ -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
137
lib/l10n/app_sk.arb
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
"@mediaSegmentRecap": {},
|
||||
"addAsFavorite": "பிடித்ததாகச் சேர்க்கவும்",
|
||||
"@addAsFavorite": {},
|
||||
"actor": "{count, plural, other{கூத்தர்கள்} one {கூத்தர்}}",
|
||||
"actor": "{count, plural, other{நடிகர்கள்} one {நடிகர்}}",
|
||||
"@actor": {
|
||||
"description": "actor",
|
||||
"placeholders": {
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
"@advanced": {},
|
||||
"all": "அனைத்தும்",
|
||||
"@all": {},
|
||||
"appLockAutoLogin": "ஆட்டோ உள்நுழைவு",
|
||||
"appLockAutoLogin": "தானாக உள்நுழை",
|
||||
"@appLockAutoLogin": {},
|
||||
"appLockBiometrics": "பயோமெட்ரிக்ச்",
|
||||
"@appLockBiometrics": {},
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
},
|
||||
"ascending": "ஏறுதல்",
|
||||
"@ascending": {},
|
||||
"audio": "ஒலி தேர்வு",
|
||||
"audio": "ஆடியோ",
|
||||
"@audio": {},
|
||||
"autoPlay": "தானாக விளையாடும்",
|
||||
"@autoPlay": {},
|
||||
|
|
@ -1209,5 +1209,83 @@
|
|||
"errorLogs": "பிழை பதிவுகள்",
|
||||
"@errorLogs": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1295,5 +1295,65 @@
|
|||
"settingsPlayerBufferSizeTitle": "Розмір буфера відео",
|
||||
"@settingsPlayerBufferSizeTitle": {},
|
||||
"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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -892,23 +892,23 @@
|
|||
"@video": {},
|
||||
"videoScaling": "视频缩放",
|
||||
"@videoScaling": {},
|
||||
"videoScalingContain": "包含",
|
||||
"videoScalingContain": "适应窗口",
|
||||
"@videoScalingContain": {},
|
||||
"videoScalingCover": "覆盖",
|
||||
"videoScalingCover": "覆盖填充",
|
||||
"@videoScalingCover": {},
|
||||
"videoScalingFill": "填充",
|
||||
"videoScalingFill": "拉伸填充",
|
||||
"@videoScalingFill": {},
|
||||
"videoScalingFillScreenDesc": "填充导航栏和状态栏",
|
||||
"videoScalingFillScreenDesc": "延伸至导航栏和状态栏区域",
|
||||
"@videoScalingFillScreenDesc": {},
|
||||
"videoScalingFillScreenNotif": "全屏覆盖视频适应,在水平旋转中",
|
||||
"videoScalingFillScreenNotif": "全屏模式将覆盖视频适配设置",
|
||||
"@videoScalingFillScreenNotif": {},
|
||||
"videoScalingFillScreenTitle": "填满屏幕",
|
||||
"videoScalingFillScreenTitle": "全屏填充",
|
||||
"@videoScalingFillScreenTitle": {},
|
||||
"videoScalingFitHeight": "适应高度",
|
||||
"@videoScalingFitHeight": {},
|
||||
"videoScalingFitWidth": "适应宽度",
|
||||
"@videoScalingFitWidth": {},
|
||||
"videoScalingScaleDown": "缩小",
|
||||
"videoScalingScaleDown": "自适应缩小",
|
||||
"@videoScalingScaleDown": {},
|
||||
"viewPhotos": "查看照片",
|
||||
"@viewPhotos": {},
|
||||
|
|
@ -946,13 +946,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"syncStatusComplete": "完成",
|
||||
"syncStatusComplete": "同步完成",
|
||||
"@syncStatusComplete": {},
|
||||
"syncNoFolderSetup": "未设置同步文件夹",
|
||||
"@syncNoFolderSetup": {},
|
||||
"syncStatusNotFound": "未找到",
|
||||
"syncStatusNotFound": "资源未找到",
|
||||
"@syncStatusNotFound": {},
|
||||
"syncStatusPaused": "已暂停",
|
||||
"syncStatusPaused": "同步已暂停",
|
||||
"@syncStatusPaused": {},
|
||||
"syncOverlayDeleting": "正在删除已同步的项目",
|
||||
"@syncOverlayDeleting": {},
|
||||
|
|
@ -964,7 +964,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"syncStatusCanceled": "已取消",
|
||||
"syncStatusCanceled": "用户已取消",
|
||||
"@syncStatusCanceled": {},
|
||||
"settingsHomeBannerInformationDesc": "主页横幅中显示的信息",
|
||||
"@settingsHomeBannerInformationDesc": {},
|
||||
|
|
@ -972,21 +972,21 @@
|
|||
"@settingsHomeBannerInformationTitle": {},
|
||||
"syncStatusEnqueued": "已添加到队列",
|
||||
"@syncStatusEnqueued": {},
|
||||
"syncStatusRunning": "运行中",
|
||||
"syncStatusRunning": "同步中",
|
||||
"@syncStatusRunning": {},
|
||||
"syncStatusFailed": "已失败",
|
||||
"syncStatusFailed": "同步失败",
|
||||
"@syncStatusFailed": {},
|
||||
"syncStatusWaitingToRetry": "等待重试中",
|
||||
"syncStatusWaitingToRetry": "等待自动重试",
|
||||
"@syncStatusWaitingToRetry": {},
|
||||
"syncStatusSynced": "已同步",
|
||||
"syncStatusSynced": "已完成同步",
|
||||
"@syncStatusSynced": {},
|
||||
"syncStatusPartially": "部分地",
|
||||
"syncStatusPartially": "部分同步成功",
|
||||
"@syncStatusPartially": {},
|
||||
"syncOverlaySyncing": "正在同步项目详情",
|
||||
"@syncOverlaySyncing": {},
|
||||
"syncSelectDownloadsFolder": "选择下载文件夹",
|
||||
"@syncSelectDownloadsFolder": {},
|
||||
"syncRemoveUnableToDeleteItem": "无法移除已同步的项目,出了点问题",
|
||||
"syncRemoveUnableToDeleteItem": "出现了一些问题,导致无法移除已同步的项目",
|
||||
"@syncRemoveUnableToDeleteItem": {},
|
||||
"syncAddItemForSyncing": "已添加 {item} 进行同步",
|
||||
"@syncAddItemForSyncing": {
|
||||
|
|
@ -1086,9 +1086,9 @@
|
|||
"@settingsAutoNextDesc": {},
|
||||
"autoNextOffStaticDesc": "播放时间还剩 30 秒时显示接下来预览窗",
|
||||
"@autoNextOffStaticDesc": {},
|
||||
"autoNextOffSmartTitle": "自动的",
|
||||
"autoNextOffSmartTitle": "智能模式",
|
||||
"@autoNextOffSmartTitle": {},
|
||||
"autoNextOffStaticTitle": "静态的",
|
||||
"autoNextOffStaticTitle": "固定模式",
|
||||
"@autoNextOffStaticTitle": {},
|
||||
"playbackRate": "播放速率",
|
||||
"@playbackRate": {},
|
||||
|
|
@ -1286,5 +1286,65 @@
|
|||
"settingsPlayerBufferSizeDesc": "配置视频播放的缓冲区大小,确定加载到缓存中的数据量。",
|
||||
"@settingsPlayerBufferSizeDesc": {},
|
||||
"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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.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:isar/isar.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:window_manager/window_manager.dart';
|
||||
|
||||
import 'package:fladder/l10n/generated/app_localizations.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/providers/arguments_provider.dart';
|
||||
import 'package:fladder/providers/crash_log_provider.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_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/screens/login/lock_screen.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/fladder_config.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
|
@ -52,7 +53,7 @@ Future<Map<String, dynamic>> loadConfig() async {
|
|||
return jsonDecode(configString);
|
||||
}
|
||||
|
||||
void main() async {
|
||||
void main(List<String> args) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final crashProvider = CrashLogNotifier();
|
||||
|
||||
|
|
@ -96,6 +97,7 @@ void main() async {
|
|||
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
|
||||
applicationInfoProvider.overrideWith((ref) => applicationInfo),
|
||||
crashLogProvider.overrideWith((ref) => crashProvider),
|
||||
argumentsStateProvider.overrideWith((ref) => ArgumentsModel.fromArguments(args)),
|
||||
syncProvider.overrideWith((ref) => SyncNotifier(
|
||||
ref,
|
||||
!kIsWeb
|
||||
|
|
@ -108,13 +110,7 @@ void main() async {
|
|||
))
|
||||
],
|
||||
child: AdaptiveLayoutBuilder(
|
||||
fallBack: ViewSize.tablet,
|
||||
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(),
|
||||
child: (context) => const Main(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -241,6 +237,10 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
|||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
final startupArguments = ref.read(argumentsStateProvider);
|
||||
if (startupArguments.htpcMode && !(await windowManager.isFullScreen())) {
|
||||
await windowManager.setFullScreen(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
||||
|
|
@ -288,13 +288,15 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
|||
),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
builder: (context, child) => Localizations.override(
|
||||
context: context,
|
||||
locale: AppLocalizations.supportedLocales.firstWhere(
|
||||
(element) => element.languageCode == language.languageCode,
|
||||
orElse: () => const Locale('en', "GB"),
|
||||
),
|
||||
child: LocalizationContextWrapper(child: ScaffoldMessenger(child: child ?? Container())),
|
||||
locale: language,
|
||||
localeResolutionCallback: (locale, supportedLocales) {
|
||||
if (locale == null || !supportedLocales.contains(locale)) {
|
||||
return const Locale('en');
|
||||
}
|
||||
return locale;
|
||||
},
|
||||
builder: (context, child) => LocalizationContextWrapper(
|
||||
child: ScaffoldMessenger(child: child ?? Container()),
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
darkTheme: darkTheme.copyWith(
|
||||
|
|
@ -304,6 +306,7 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
|||
colorScheme: darkTheme.colorScheme.copyWith(
|
||||
surface: amoledOverwrite,
|
||||
surfaceContainerHighest: amoledOverwrite,
|
||||
surfaceContainerLow: amoledOverwrite,
|
||||
),
|
||||
),
|
||||
themeMode: themeMode,
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:iconsax_plus/iconsax_plus.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/models/credentials_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';
|
||||
|
||||
part 'account_model.freezed.dart';
|
||||
part 'account_model.g.dart';
|
||||
|
||||
@freezed
|
||||
@Freezed(copyWith: true)
|
||||
class AccountModel with _$AccountModel {
|
||||
const AccountModel._();
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ class AccountModel with _$AccountModel {
|
|||
@Default([]) List<LibraryFiltersModel> savedFilters,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) UserConfiguration? userConfiguration,
|
||||
}) = _AccountModel;
|
||||
|
||||
factory AccountModel.fromJson(Map<String, dynamic> json) => _$AccountModelFromJson(json);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ mixin _$AccountModel {
|
|||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? get serverConfiguration =>
|
||||
throw _privateConstructorUsedError;
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
UserConfiguration? get userConfiguration =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AccountModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
|
@ -68,7 +71,9 @@ abstract class $AccountModelCopyWith<$Res> {
|
|||
List<LibraryFiltersModel> savedFilters,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? serverConfiguration});
|
||||
ServerConfiguration? serverConfiguration,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
UserConfiguration? userConfiguration});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -99,6 +104,7 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
|
|||
Object? savedFilters = null,
|
||||
Object? policy = freezed,
|
||||
Object? serverConfiguration = freezed,
|
||||
Object? userConfiguration = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
|
|
@ -153,6 +159,10 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
|
|||
? _value.serverConfiguration
|
||||
: serverConfiguration // ignore: cast_nullable_to_non_nullable
|
||||
as ServerConfiguration?,
|
||||
userConfiguration: freezed == userConfiguration
|
||||
? _value.userConfiguration
|
||||
: userConfiguration // ignore: cast_nullable_to_non_nullable
|
||||
as UserConfiguration?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
|
@ -179,7 +189,9 @@ abstract class _$$AccountModelImplCopyWith<$Res>
|
|||
List<LibraryFiltersModel> savedFilters,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? serverConfiguration});
|
||||
ServerConfiguration? serverConfiguration,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
UserConfiguration? userConfiguration});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -208,6 +220,7 @@ class __$$AccountModelImplCopyWithImpl<$Res>
|
|||
Object? savedFilters = null,
|
||||
Object? policy = freezed,
|
||||
Object? serverConfiguration = freezed,
|
||||
Object? userConfiguration = freezed,
|
||||
}) {
|
||||
return _then(_$AccountModelImpl(
|
||||
name: null == name
|
||||
|
|
@ -262,6 +275,10 @@ class __$$AccountModelImplCopyWithImpl<$Res>
|
|||
? _value.serverConfiguration
|
||||
: serverConfiguration // ignore: cast_nullable_to_non_nullable
|
||||
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 [],
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) this.policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
this.serverConfiguration})
|
||||
this.serverConfiguration,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
this.userConfiguration})
|
||||
: _latestItemsExcludes = latestItemsExcludes,
|
||||
_searchQueryHistory = searchQueryHistory,
|
||||
_savedFilters = savedFilters,
|
||||
|
|
@ -346,10 +365,13 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
|
|||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final ServerConfiguration? serverConfiguration;
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final UserConfiguration? userConfiguration;
|
||||
|
||||
@override
|
||||
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
|
||||
|
|
@ -369,56 +391,10 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
|
|||
..add(DiagnosticsProperty('quickConnectState', quickConnectState))
|
||||
..add(DiagnosticsProperty('savedFilters', savedFilters))
|
||||
..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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
|
|
@ -451,7 +427,9 @@ abstract class _AccountModel extends AccountModel {
|
|||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final ServerConfiguration? serverConfiguration}) = _$AccountModelImpl;
|
||||
final ServerConfiguration? serverConfiguration,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final UserConfiguration? userConfiguration}) = _$AccountModelImpl;
|
||||
const _AccountModel._() : super._();
|
||||
|
||||
factory _AccountModel.fromJson(Map<String, dynamic> json) =
|
||||
|
|
@ -485,6 +463,9 @@ abstract class _AccountModel extends AccountModel {
|
|||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? get serverConfiguration;
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
UserConfiguration? get userConfiguration;
|
||||
|
||||
/// Create a copy of AccountModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
|
|
|||
|
|
@ -108,34 +108,11 @@ class BoxSetModelMapper extends SubClassMapperBase<BoxSetModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_BoxSetModelCopyWithImpl(this as BoxSetModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return BoxSetModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as BoxSetModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension BoxSetModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ extension CollectionTypeExtension on CollectionType {
|
|||
|
||||
Set<FladderItemType> get itemKinds {
|
||||
switch (this) {
|
||||
case CollectionType.music:
|
||||
return {FladderItemType.musicAlbum};
|
||||
case CollectionType.movies:
|
||||
return {FladderItemType.movie};
|
||||
case CollectionType.tvshows:
|
||||
|
|
@ -30,6 +32,8 @@ extension CollectionTypeExtension on CollectionType {
|
|||
|
||||
IconData getIconType(bool outlined) {
|
||||
switch (this) {
|
||||
case CollectionType.music:
|
||||
return outlined ? IconsaxPlusLinear.music_square : IconsaxPlusBold.music_square;
|
||||
case CollectionType.movies:
|
||||
return outlined ? IconsaxPlusLinear.video_horizontal : IconsaxPlusBold.video_horizontal;
|
||||
case CollectionType.tvshows:
|
||||
|
|
@ -48,4 +52,16 @@ extension CollectionTypeExtension on CollectionType {
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.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.swagger.dart' as dto;
|
||||
|
|
@ -304,6 +304,15 @@ enum FladderItemType {
|
|||
|
||||
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 => {
|
||||
FladderItemType.series,
|
||||
FladderItemType.episode,
|
||||
|
|
@ -317,27 +326,25 @@ enum FladderItemType {
|
|||
FladderItemType.video,
|
||||
};
|
||||
|
||||
String label(BuildContext context) {
|
||||
return switch (this) {
|
||||
FladderItemType.baseType => context.localized.mediaTypeBase,
|
||||
FladderItemType.audio => context.localized.audio,
|
||||
FladderItemType.collectionFolder => context.localized.collectionFolder,
|
||||
FladderItemType.musicAlbum => context.localized.musicAlbum,
|
||||
FladderItemType.musicVideo => context.localized.video,
|
||||
FladderItemType.video => context.localized.video,
|
||||
FladderItemType.movie => context.localized.mediaTypeMovie,
|
||||
FladderItemType.series => context.localized.mediaTypeSeries,
|
||||
FladderItemType.season => context.localized.mediaTypeSeason,
|
||||
FladderItemType.episode => context.localized.mediaTypeEpisode,
|
||||
FladderItemType.photo => context.localized.mediaTypePhoto,
|
||||
FladderItemType.person => context.localized.mediaTypePerson,
|
||||
FladderItemType.photoAlbum => context.localized.mediaTypePhotoAlbum,
|
||||
FladderItemType.folder => context.localized.mediaTypeFolder,
|
||||
FladderItemType.boxset => context.localized.mediaTypeBoxset,
|
||||
FladderItemType.playlist => context.localized.mediaTypePlaylist,
|
||||
FladderItemType.book => context.localized.mediaTypeBook,
|
||||
};
|
||||
}
|
||||
String label(BuildContext context) => switch (this) {
|
||||
FladderItemType.baseType => context.localized.mediaTypeBase,
|
||||
FladderItemType.audio => context.localized.audio,
|
||||
FladderItemType.collectionFolder => context.localized.collectionFolder,
|
||||
FladderItemType.musicAlbum => context.localized.musicAlbum,
|
||||
FladderItemType.musicVideo => context.localized.video,
|
||||
FladderItemType.video => context.localized.video,
|
||||
FladderItemType.movie => context.localized.mediaTypeMovie,
|
||||
FladderItemType.series => context.localized.mediaTypeSeries,
|
||||
FladderItemType.season => context.localized.mediaTypeSeason,
|
||||
FladderItemType.episode => context.localized.mediaTypeEpisode,
|
||||
FladderItemType.photo => context.localized.mediaTypePhoto,
|
||||
FladderItemType.person => context.localized.mediaTypePerson,
|
||||
FladderItemType.photoAlbum => context.localized.mediaTypePhotoAlbum,
|
||||
FladderItemType.folder => context.localized.mediaTypeFolder,
|
||||
FladderItemType.boxset => context.localized.mediaTypeBoxset,
|
||||
FladderItemType.playlist => context.localized.mediaTypePlaylist,
|
||||
FladderItemType.book => context.localized.mediaTypeBook,
|
||||
};
|
||||
|
||||
BaseItemKind get dtoKind => switch (this) {
|
||||
FladderItemType.baseType => BaseItemKind.userrootfolder,
|
||||
|
|
|
|||
|
|
@ -93,35 +93,12 @@ class ItemBaseModelMapper extends ClassMapperBase<ItemBaseModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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>
|
||||
get copyWith => _ItemBaseModelCopyWithImpl(
|
||||
this as ItemBaseModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return ItemBaseModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as ItemBaseModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemBaseModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -199,17 +199,34 @@ extension EpisodeListExtensions on List<EpisodeModel> {
|
|||
}
|
||||
|
||||
EpisodeModel? get nextUp {
|
||||
final lastProgress =
|
||||
lastIndexWhere((element) => element.userData.progress != 0 && element.status == EpisodeStatus.available);
|
||||
final lastPlayed =
|
||||
lastIndexWhere((element) => element.userData.played && element.status == EpisodeStatus.available);
|
||||
final episodes = where((e) => e.season > 0 && e.status == EpisodeStatus.available).toList();
|
||||
if (episodes.isEmpty) return null;
|
||||
|
||||
if (lastProgress == -1 && lastPlayed == -1) {
|
||||
return firstWhereOrNull((element) => element.status == EpisodeStatus.available);
|
||||
} else {
|
||||
return getRange(lastProgress > lastPlayed ? lastProgress : lastPlayed + 1, length)
|
||||
.firstWhereOrNull((element) => element.status == EpisodeStatus.available);
|
||||
final lastProgressIndex = episodes.lastIndexWhere((e) => e.userData.progress != 0);
|
||||
final lastPlayedIndex = episodes.lastIndexWhere((e) => e.userData.played);
|
||||
final lastWatchedIndex = [lastProgressIndex, lastPlayedIndex].reduce((a, b) => a > b ? a : b);
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -141,34 +141,11 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_EpisodeModelCopyWithImpl(this as EpisodeModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return EpisodeModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as EpisodeModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension EpisodeModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -108,34 +108,11 @@ class FolderModelMapper extends SubClassMapperBase<FolderModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_FolderModelCopyWithImpl(this as FolderModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return FolderModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as FolderModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension FolderModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -18,92 +18,6 @@ final _privateConstructorUsedError = UnsupportedError(
|
|||
mixin _$ItemPropertiesModel {
|
||||
bool get canDelete => 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
|
||||
|
|
@ -122,29 +36,6 @@ class _$ItemPropertiesModelImpl extends _ItemPropertiesModel {
|
|||
String toString() {
|
||||
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 {
|
||||
|
|
@ -157,11 +48,4 @@ abstract class _ItemPropertiesModel extends ItemPropertiesModel {
|
|||
bool get canDelete;
|
||||
@override
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import 'package:fladder/models/items/images_models.dart';
|
|||
|
||||
part 'item_shared_models.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
@MappableClass(generateMethods: GenerateMethods.encode | GenerateMethods.decode | GenerateMethods.copy)
|
||||
class UserData with UserDataMappable {
|
||||
final bool isFavourite;
|
||||
final int playCount;
|
||||
|
|
|
|||
|
|
@ -92,10 +92,6 @@ mixin UserDataMappable {
|
|||
|
||||
UserDataCopyWith<UserData, UserData, UserData> get copyWith =>
|
||||
_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> {
|
||||
|
|
|
|||
|
|
@ -112,35 +112,12 @@ class ItemStreamModelMapper extends SubClassMapperBase<ItemStreamModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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>
|
||||
get copyWith => _ItemStreamModelCopyWithImpl(
|
||||
this as ItemStreamModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return ItemStreamModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as ItemStreamModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemStreamModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:collection/collection.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/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 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 = {
|
||||
|
|
|
|||
|
|
@ -24,82 +24,6 @@ mixin _$MediaSegmentsModel {
|
|||
|
||||
/// Serializes this MediaSegmentsModel to a JSON map.
|
||||
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
|
||||
|
|
@ -126,28 +50,6 @@ class _$MediaSegmentsModelImpl extends _MediaSegmentsModel {
|
|||
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
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$MediaSegmentsModelImplToJson(
|
||||
|
|
@ -166,13 +68,6 @@ abstract class _MediaSegmentsModel extends MediaSegmentsModel {
|
|||
|
||||
@override
|
||||
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) {
|
||||
|
|
@ -187,102 +82,6 @@ mixin _$MediaSegment {
|
|||
|
||||
/// Serializes this MediaSegment to a JSON map.
|
||||
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
|
||||
|
|
@ -307,28 +106,6 @@ class _$MediaSegmentImpl extends _MediaSegment {
|
|||
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
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$MediaSegmentImplToJson(
|
||||
|
|
@ -353,11 +130,4 @@ abstract class _MediaSegment extends MediaSegment {
|
|||
Duration get start;
|
||||
@override
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
final String name;
|
||||
final int index;
|
||||
|
|
@ -250,19 +264,17 @@ extension SortByExternalExtension<T extends StreamModel> on Iterable<T> {
|
|||
}
|
||||
}
|
||||
|
||||
class AudioStreamModel extends StreamModel {
|
||||
final String displayTitle;
|
||||
final String language;
|
||||
class AudioStreamModel extends AudioAndSubStreamModel {
|
||||
final String channelLayout;
|
||||
|
||||
AudioStreamModel({
|
||||
required this.displayTitle,
|
||||
required super.displayTitle,
|
||||
required super.name,
|
||||
required super.codec,
|
||||
required super.isDefault,
|
||||
required super.isExternal,
|
||||
required super.index,
|
||||
required this.language,
|
||||
required super.language,
|
||||
required this.channelLayout,
|
||||
});
|
||||
|
||||
|
|
@ -292,8 +304,8 @@ class AudioStreamModel extends StreamModel {
|
|||
|
||||
AudioStreamModel.no({
|
||||
super.name = 'Off',
|
||||
this.displayTitle = 'Off',
|
||||
this.language = '',
|
||||
super.displayTitle = 'Off',
|
||||
super.language = '',
|
||||
super.codec = '',
|
||||
this.channelLayout = '',
|
||||
super.isDefault = false,
|
||||
|
|
@ -302,19 +314,17 @@ class AudioStreamModel extends StreamModel {
|
|||
});
|
||||
}
|
||||
|
||||
class SubStreamModel extends StreamModel {
|
||||
class SubStreamModel extends AudioAndSubStreamModel {
|
||||
String id;
|
||||
String title;
|
||||
String displayTitle;
|
||||
String language;
|
||||
String? url;
|
||||
bool supportsExternalStream;
|
||||
SubStreamModel({
|
||||
required super.name,
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.displayTitle,
|
||||
required this.language,
|
||||
required super.displayTitle,
|
||||
required super.language,
|
||||
this.url,
|
||||
required super.codec,
|
||||
required super.isDefault,
|
||||
|
|
@ -327,8 +337,8 @@ class SubStreamModel extends StreamModel {
|
|||
super.name = 'Off',
|
||||
this.id = 'Off',
|
||||
this.title = 'Off',
|
||||
this.displayTitle = 'Off',
|
||||
this.language = '',
|
||||
super.displayTitle = 'Off',
|
||||
super.language = '',
|
||||
this.url = '',
|
||||
super.codec = '',
|
||||
super.isDefault = false,
|
||||
|
|
|
|||
|
|
@ -147,34 +147,11 @@ class MovieModelMapper extends SubClassMapperBase<MovieModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_MovieModelCopyWithImpl(this as MovieModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return MovieModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as MovieModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension MovieModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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/util/duration_extensions.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'overview_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
|
|
@ -76,7 +75,4 @@ class OverviewModel with OverviewModelMappable {
|
|||
people: Person.peopleFromDto(item.people ?? [], ref),
|
||||
);
|
||||
}
|
||||
|
||||
factory OverviewModel.fromMap(Map<String, dynamic> map) => OverviewModelMapper.fromMap(map);
|
||||
factory OverviewModel.fromJson(String json) => OverviewModelMapper.fromJson(json);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,35 +114,12 @@ class OverviewModelMapper extends ClassMapperBase<OverviewModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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>
|
||||
get copyWith => _OverviewModelCopyWithImpl(
|
||||
this as OverviewModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return OverviewModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as OverviewModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension OverviewModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -124,34 +124,11 @@ class PersonModelMapper extends SubClassMapperBase<PersonModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_PersonModelCopyWithImpl(this as PersonModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return PersonModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as PersonModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension PersonModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -108,35 +108,12 @@ class PhotoAlbumModelMapper extends SubClassMapperBase<PhotoAlbumModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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>
|
||||
get copyWith => _PhotoAlbumModelCopyWithImpl(
|
||||
this as PhotoAlbumModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return PhotoAlbumModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as PhotoAlbumModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoAlbumModelValueCopy<$R, $Out>
|
||||
|
|
@ -359,34 +336,11 @@ class PhotoModelMapper extends SubClassMapperBase<PhotoModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_PhotoModelCopyWithImpl(this as PhotoModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return PhotoModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as PhotoModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -137,34 +137,11 @@ class SeasonModelMapper extends SubClassMapperBase<SeasonModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_SeasonModelCopyWithImpl(this as SeasonModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return SeasonModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as SeasonModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension SeasonModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
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/overview_model.dart';
|
||||
import 'package:fladder/models/items/season_model.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
|
||||
|
||||
part 'series_model.mapper.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -135,34 +135,11 @@ class SeriesModelMapper extends SubClassMapperBase<SeriesModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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 =>
|
||||
_SeriesModelCopyWithImpl(this as SeriesModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return SeriesModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as SeriesModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension SeriesModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||
part 'trick_play_model.freezed.dart';
|
||||
part 'trick_play_model.g.dart';
|
||||
|
||||
@freezed
|
||||
@Freezed(copyWith: true)
|
||||
class TrickPlayModel with _$TrickPlayModel {
|
||||
factory TrickPlayModel({
|
||||
required int width,
|
||||
|
|
|
|||
|
|
@ -225,36 +225,6 @@ class _$TrickPlayModelImpl extends _TrickPlayModel {
|
|||
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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import 'package:fladder/util/map_bool_helper.dart';
|
|||
part 'library_filters_model.freezed.dart';
|
||||
part 'library_filters_model.g.dart';
|
||||
|
||||
@freezed
|
||||
@Freezed(copyWith: true)
|
||||
class LibraryFiltersModel with _$LibraryFiltersModel {
|
||||
const LibraryFiltersModel._();
|
||||
|
||||
|
|
@ -40,11 +40,16 @@ class LibraryFiltersModel with _$LibraryFiltersModel {
|
|||
|
||||
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(
|
||||
id: Xid().toString(),
|
||||
id: id ?? Xid().toString(),
|
||||
name: name,
|
||||
isFavourite: false,
|
||||
isFavourite: isFavourite ?? false,
|
||||
ids: searchModel.views.included.map((e) => e.id).toList(),
|
||||
genres: searchModel.genres,
|
||||
filters: searchModel.filters,
|
||||
|
|
|
|||
|
|
@ -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)';
|
||||
}
|
||||
|
||||
@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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ const _$FladderItemTypeEnumMap = {
|
|||
};
|
||||
|
||||
const _$SortingOptionsEnumMap = {
|
||||
SortingOptions.name: 'name',
|
||||
SortingOptions.sortName: 'sortName',
|
||||
SortingOptions.communityRating: 'communityRating',
|
||||
SortingOptions.parentalRating: 'parentalRating',
|
||||
SortingOptions.dateAdded: 'dateAdded',
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class LibrarySearchModel with LibrarySearchModelMappable {
|
|||
FladderItemType.video: true,
|
||||
},
|
||||
this.favourites = false,
|
||||
this.sortingOption = SortingOptions.name,
|
||||
this.sortingOption = SortingOptions.sortName,
|
||||
this.sortOrder = SortingOrder.ascending,
|
||||
this.hideEmptyShows = true,
|
||||
this.recursive = false,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
|
|||
v.sortingOption;
|
||||
static const Field<LibrarySearchModel, SortingOptions> _f$sortingOption =
|
||||
Field('sortingOption', _$sortingOption,
|
||||
opt: true, def: SortingOptions.name);
|
||||
opt: true, def: SortingOptions.sortName);
|
||||
static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder;
|
||||
static const Field<LibrarySearchModel, SortingOrder> _f$sortOrder =
|
||||
Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending);
|
||||
|
|
@ -177,36 +177,13 @@ class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
|
|||
|
||||
@override
|
||||
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 {
|
||||
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,
|
||||
LibrarySearchModel>
|
||||
get copyWith => _LibrarySearchModelCopyWithImpl(
|
||||
this as LibrarySearchModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return LibrarySearchModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as LibrarySearchModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension LibrarySearchModelValueCopy<$R, $Out>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.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 {
|
||||
name([ItemSortBy.name]),
|
||||
sortName([ItemSortBy.sortname]),
|
||||
communityRating([ItemSortBy.communityrating]),
|
||||
// criticsRating([ItemSortBy.criticrating]),
|
||||
parentalRating([ItemSortBy.officialrating]),
|
||||
dateAdded([ItemSortBy.datecreated]),
|
||||
dateLastContentAdded([ItemSortBy.datelastcontentadded]),
|
||||
|
|
@ -23,10 +22,10 @@ enum SortingOptions {
|
|||
const SortingOptions(this.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) {
|
||||
name => context.localized.name,
|
||||
sortName => context.localized.name,
|
||||
communityRating => context.localized.communityRating,
|
||||
parentalRating => context.localized.parentalRating,
|
||||
dateAdded => context.localized.dateAdded,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:collection/collection.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/playback/direct_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/syncing/sync_item.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/list_extensions.dart';
|
||||
import 'package:fladder/util/map_bool_helper.dart';
|
||||
import 'package:fladder/util/streams_selection.dart';
|
||||
import 'package:fladder/wrappers/media_control_wrapper.dart';
|
||||
|
||||
class Media {
|
||||
|
|
@ -48,10 +52,10 @@ extension PlaybackModelExtension on PlaybackModel? {
|
|||
AudioStreamModel? get defaultAudioStream =>
|
||||
this?.audioStreams?.firstWhereOrNull((element) => element.index == this?.mediaStreams?.defaultAudioStreamIndex);
|
||||
|
||||
String? get label => switch (this) {
|
||||
DirectPlaybackModel _ => PlaybackType.directStream.name,
|
||||
TranscodePlaybackModel _ => PlaybackType.transcode.name,
|
||||
OfflinePlaybackModel _ => PlaybackType.offline.name,
|
||||
String? label(BuildContext context) => switch (this) {
|
||||
DirectPlaybackModel _ => PlaybackType.directStream.name(context),
|
||||
TranscodePlaybackModel _ => PlaybackType.transcode.name(context),
|
||||
OfflinePlaybackModel _ => PlaybackType.offline.name(context),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
|
@ -117,13 +121,14 @@ class PlaybackModelHelper {
|
|||
ref.read(videoPlayerProvider).pause();
|
||||
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(buffering: true));
|
||||
final currentModel = ref.read(playBackModel);
|
||||
final newModel = (await createServerPlaybackModel(
|
||||
newItem,
|
||||
final newModel = (await createPlaybackModel(
|
||||
null,
|
||||
newItem,
|
||||
oldModel: currentModel,
|
||||
)) ??
|
||||
await createOfflinePlaybackModel(
|
||||
await _createOfflinePlaybackModel(
|
||||
newItem,
|
||||
null,
|
||||
ref.read(syncProvider.notifier).getSyncedItem(newItem),
|
||||
oldModel: currentModel,
|
||||
);
|
||||
|
|
@ -132,8 +137,9 @@ class PlaybackModelHelper {
|
|||
return newModel;
|
||||
}
|
||||
|
||||
Future<OfflinePlaybackModel?> createOfflinePlaybackModel(
|
||||
Future<OfflinePlaybackModel?> _createOfflinePlaybackModel(
|
||||
ItemBaseModel item,
|
||||
MediaStreamsModel? streamModel,
|
||||
SyncedItem? syncedItem, {
|
||||
PlaybackModel? oldModel,
|
||||
}) async {
|
||||
|
|
@ -156,53 +162,121 @@ class PlaybackModelHelper {
|
|||
);
|
||||
}
|
||||
|
||||
Future<EpisodeModel?> getNextUpEpisode(String itemId) async {
|
||||
final response = await api.showsNextUpGet(parentId: itemId, fields: [ItemFields.overview]);
|
||||
final episode = response.body?.items?.firstOrNull;
|
||||
if (episode == null) {
|
||||
return null;
|
||||
Future<PlaybackModel?> createPlaybackModel(
|
||||
BuildContext? context,
|
||||
ItemBaseModel? item, {
|
||||
PlaybackModel? oldModel,
|
||||
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 {
|
||||
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(
|
||||
ItemBaseModel? item,
|
||||
Future<PlaybackModel?> _createServerPlaybackModel(
|
||||
ItemBaseModel item,
|
||||
MediaStreamsModel? streamModel,
|
||||
PlaybackType? type, {
|
||||
PlaybackModel? oldModel,
|
||||
List<ItemBaseModel>? libraryQueue,
|
||||
required List<ItemBaseModel> libraryQueue,
|
||||
Duration? startPosition,
|
||||
}) async {
|
||||
try {
|
||||
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 _ => (await getNextUpEpisode(item.id) ?? queue.first),
|
||||
_ => item,
|
||||
};
|
||||
|
||||
final fullItem = await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id);
|
||||
final newStreamModel = streamModel ?? item.streamModel;
|
||||
|
||||
Map<Bitrate, bool> qualityOptions = getVideoQualityOptions(
|
||||
VideoQualitySettings(
|
||||
maxBitRate: ref.read(videoPlayerSettingsProvider.select((value) => value.maxHomeBitrate)),
|
||||
videoBitRate: firstItemToPlay.streamModel?.videoStreams.firstOrNull?.bitRate ?? 0,
|
||||
videoCodec: firstItemToPlay.streamModel?.videoStreams.firstOrNull?.codec,
|
||||
videoBitRate: newStreamModel?.videoStreams.firstOrNull?.bitRate ?? 0,
|
||||
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(
|
||||
itemId: firstItemToPlay.id,
|
||||
itemId: item.id,
|
||||
body: PlaybackInfoDto(
|
||||
startTimeTicks: startPosition?.toRuntimeTicks,
|
||||
audioStreamIndex: streamModel?.defaultAudioStreamIndex,
|
||||
subtitleStreamIndex: streamModel?.defaultSubStreamIndex,
|
||||
audioStreamIndex: audioStreamIndex,
|
||||
subtitleStreamIndex: subStreamIndex,
|
||||
enableTranscoding: true,
|
||||
autoOpenLiveStream: true,
|
||||
deviceProfile: ref.read(videoProfileProvider),
|
||||
|
|
@ -210,7 +284,7 @@ class PlaybackModelHelper {
|
|||
enableDirectPlay: type != PlaybackType.transcode,
|
||||
enableDirectStream: type != PlaybackType.transcode,
|
||||
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;
|
||||
|
||||
final mediaSource = playbackInfo.mediaSources?[streamModel?.versionStreamIndex ?? 0];
|
||||
final mediaSource = playbackInfo.mediaSources?[newStreamModel?.versionStreamIndex ?? 0];
|
||||
|
||||
if (mediaSource == null) return null;
|
||||
|
||||
final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(playbackInfo.mediaSources, ref).copyWith(
|
||||
defaultAudioStreamIndex: streamModel?.defaultAudioStreamIndex,
|
||||
defaultSubStreamIndex: streamModel?.defaultSubStreamIndex,
|
||||
defaultAudioStreamIndex: audioStreamIndex,
|
||||
defaultSubStreamIndex: subStreamIndex,
|
||||
);
|
||||
|
||||
final mediaSegments = await api.mediaSegmentsGet(id: item.id);
|
||||
final trickPlay = (await api.getTrickPlay(item: fullItem.body, ref: ref))?.body;
|
||||
final chapters = fullItem.body?.overview.chapters ?? [];
|
||||
final trickPlay = (await api.getTrickPlay(item: item, ref: ref))?.body;
|
||||
final chapters = item.overview.chapters ?? [];
|
||||
|
||||
final mediaPath = isValidVideoUrl(mediaSource.path ?? "");
|
||||
|
||||
|
|
@ -252,8 +326,8 @@ class PlaybackModelHelper {
|
|||
final playbackUrl = joinAll([ref.read(userProvider)!.server, "Videos", mediaSource.id!, "stream?$params"]);
|
||||
|
||||
return DirectPlaybackModel(
|
||||
item: fullItem.body ?? item,
|
||||
queue: queue,
|
||||
item: item,
|
||||
queue: libraryQueue,
|
||||
mediaSegments: mediaSegments?.body,
|
||||
chapters: chapters,
|
||||
playbackInfo: playbackInfo,
|
||||
|
|
@ -264,8 +338,8 @@ class PlaybackModelHelper {
|
|||
);
|
||||
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
|
||||
return TranscodePlaybackModel(
|
||||
item: fullItem.body ?? item,
|
||||
queue: queue,
|
||||
item: item,
|
||||
queue: libraryQueue,
|
||||
mediaSegments: mediaSegments?.body,
|
||||
chapters: chapters,
|
||||
trickPlay: trickPlay,
|
||||
|
|
@ -328,8 +402,16 @@ class PlaybackModelHelper {
|
|||
|
||||
final currentPosition = ref.read(mediaPlaybackProvider.select((value) => value.position));
|
||||
|
||||
final audioIndex = playbackModel.mediaStreams?.defaultAudioStreamIndex;
|
||||
final subIndex = playbackModel.mediaStreams?.defaultSubStreamIndex;
|
||||
final audioIndex = selectAudioStream(
|
||||
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(
|
||||
itemId: item.id,
|
||||
|
|
|
|||
57
lib/models/playback/playback_options_dialogue.dart
Normal 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);
|
||||
},
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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/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 {
|
||||
final String name;
|
||||
final NameSwitch name;
|
||||
final List<ItemBaseModel> posters;
|
||||
final String type;
|
||||
final RecommendationType? type;
|
||||
RecommendedModel({
|
||||
required this.name,
|
||||
required this.posters,
|
||||
required this.type,
|
||||
this.type,
|
||||
});
|
||||
|
||||
RecommendedModel copyWith({
|
||||
String? name,
|
||||
NameSwitch? name,
|
||||
List<ItemBaseModel>? posters,
|
||||
String? type,
|
||||
RecommendationType? type,
|
||||
}) {
|
||||
return RecommendedModel(
|
||||
name: name ?? this.name,
|
||||
|
|
@ -22,4 +68,12 @@ class RecommendedModel {
|
|||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
lib/models/settings/arguments_model.dart
Normal 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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/models/settings/arguments_model.freezed.dart
Normal 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;
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import 'package:fladder/util/custom_color_themes.dart';
|
|||
part 'client_settings_model.freezed.dart';
|
||||
part 'client_settings_model.g.dart';
|
||||
|
||||
@freezed
|
||||
@Freezed(copyWith: true)
|
||||
class ClientSettingsModel with _$ClientSettingsModel {
|
||||
const ClientSettingsModel._();
|
||||
factory ClientSettingsModel({
|
||||
|
|
@ -32,7 +32,11 @@ class ClientSettingsModel with _$ClientSettingsModel {
|
|||
@Default(false) bool mouseDragSupport,
|
||||
@Default(true) bool requireWifi,
|
||||
@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,
|
||||
}) = _ClientSettingsModel;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ mixin _$ClientSettingsModel {
|
|||
bool get mouseDragSupport => throw _privateConstructorUsedError;
|
||||
bool get requireWifi => throw _privateConstructorUsedError;
|
||||
bool get showAllCollectionTypes => throw _privateConstructorUsedError;
|
||||
int get maxConcurrentDownloads => 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;
|
||||
|
||||
/// Serializes this ClientSettingsModel to a JSON map.
|
||||
|
|
@ -75,7 +79,11 @@ abstract class $ClientSettingsModelCopyWith<$Res> {
|
|||
bool mouseDragSupport,
|
||||
bool requireWifi,
|
||||
bool showAllCollectionTypes,
|
||||
int maxConcurrentDownloads,
|
||||
DynamicSchemeVariant schemeVariant,
|
||||
bool backgroundPosters,
|
||||
bool checkForUpdates,
|
||||
String? lastViewedUpdate,
|
||||
int? libraryPageSize});
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +119,11 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel>
|
|||
Object? mouseDragSupport = null,
|
||||
Object? requireWifi = null,
|
||||
Object? showAllCollectionTypes = null,
|
||||
Object? maxConcurrentDownloads = null,
|
||||
Object? schemeVariant = null,
|
||||
Object? backgroundPosters = null,
|
||||
Object? checkForUpdates = null,
|
||||
Object? lastViewedUpdate = freezed,
|
||||
Object? libraryPageSize = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
|
|
@ -183,10 +195,26 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel>
|
|||
? _value.showAllCollectionTypes
|
||||
: showAllCollectionTypes // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
maxConcurrentDownloads: null == maxConcurrentDownloads
|
||||
? _value.maxConcurrentDownloads
|
||||
: maxConcurrentDownloads // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
schemeVariant: null == schemeVariant
|
||||
? _value.schemeVariant
|
||||
: schemeVariant // ignore: cast_nullable_to_non_nullable
|
||||
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
|
||||
? _value.libraryPageSize
|
||||
: libraryPageSize // ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -221,7 +249,11 @@ abstract class _$$ClientSettingsModelImplCopyWith<$Res>
|
|||
bool mouseDragSupport,
|
||||
bool requireWifi,
|
||||
bool showAllCollectionTypes,
|
||||
int maxConcurrentDownloads,
|
||||
DynamicSchemeVariant schemeVariant,
|
||||
bool backgroundPosters,
|
||||
bool checkForUpdates,
|
||||
String? lastViewedUpdate,
|
||||
int? libraryPageSize});
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +287,11 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res>
|
|||
Object? mouseDragSupport = null,
|
||||
Object? requireWifi = null,
|
||||
Object? showAllCollectionTypes = null,
|
||||
Object? maxConcurrentDownloads = null,
|
||||
Object? schemeVariant = null,
|
||||
Object? backgroundPosters = null,
|
||||
Object? checkForUpdates = null,
|
||||
Object? lastViewedUpdate = freezed,
|
||||
Object? libraryPageSize = freezed,
|
||||
}) {
|
||||
return _then(_$ClientSettingsModelImpl(
|
||||
|
|
@ -327,10 +363,26 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res>
|
|||
? _value.showAllCollectionTypes
|
||||
: showAllCollectionTypes // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
maxConcurrentDownloads: null == maxConcurrentDownloads
|
||||
? _value.maxConcurrentDownloads
|
||||
: maxConcurrentDownloads // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
schemeVariant: null == schemeVariant
|
||||
? _value.schemeVariant
|
||||
: schemeVariant // ignore: cast_nullable_to_non_nullable
|
||||
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
|
||||
? _value.libraryPageSize
|
||||
: libraryPageSize // ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -361,7 +413,11 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel
|
|||
this.mouseDragSupport = false,
|
||||
this.requireWifi = true,
|
||||
this.showAllCollectionTypes = false,
|
||||
this.schemeVariant = DynamicSchemeVariant.tonalSpot,
|
||||
this.maxConcurrentDownloads = 2,
|
||||
this.schemeVariant = DynamicSchemeVariant.rainbow,
|
||||
this.backgroundPosters = true,
|
||||
this.checkForUpdates = true,
|
||||
this.lastViewedUpdate,
|
||||
this.libraryPageSize})
|
||||
: super._();
|
||||
|
||||
|
|
@ -418,13 +474,24 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel
|
|||
final bool showAllCollectionTypes;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int maxConcurrentDownloads;
|
||||
@override
|
||||
@JsonKey()
|
||||
final DynamicSchemeVariant schemeVariant;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool backgroundPosters;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool checkForUpdates;
|
||||
@override
|
||||
final String? lastViewedUpdate;
|
||||
@override
|
||||
final int? libraryPageSize;
|
||||
|
||||
@override
|
||||
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
|
||||
|
|
@ -450,78 +517,15 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel
|
|||
..add(DiagnosticsProperty('requireWifi', requireWifi))
|
||||
..add(
|
||||
DiagnosticsProperty('showAllCollectionTypes', showAllCollectionTypes))
|
||||
..add(
|
||||
DiagnosticsProperty('maxConcurrentDownloads', maxConcurrentDownloads))
|
||||
..add(DiagnosticsProperty('schemeVariant', schemeVariant))
|
||||
..add(DiagnosticsProperty('backgroundPosters', backgroundPosters))
|
||||
..add(DiagnosticsProperty('checkForUpdates', checkForUpdates))
|
||||
..add(DiagnosticsProperty('lastViewedUpdate', lastViewedUpdate))
|
||||
..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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
|
|
@ -558,7 +562,11 @@ abstract class _ClientSettingsModel extends ClientSettingsModel {
|
|||
final bool mouseDragSupport,
|
||||
final bool requireWifi,
|
||||
final bool showAllCollectionTypes,
|
||||
final int maxConcurrentDownloads,
|
||||
final DynamicSchemeVariant schemeVariant,
|
||||
final bool backgroundPosters,
|
||||
final bool checkForUpdates,
|
||||
final String? lastViewedUpdate,
|
||||
final int? libraryPageSize}) = _$ClientSettingsModelImpl;
|
||||
_ClientSettingsModel._() : super._();
|
||||
|
||||
|
|
@ -601,8 +609,16 @@ abstract class _ClientSettingsModel extends ClientSettingsModel {
|
|||
@override
|
||||
bool get showAllCollectionTypes;
|
||||
@override
|
||||
int get maxConcurrentDownloads;
|
||||
@override
|
||||
DynamicSchemeVariant get schemeVariant;
|
||||
@override
|
||||
bool get backgroundPosters;
|
||||
@override
|
||||
bool get checkForUpdates;
|
||||
@override
|
||||
String? get lastViewedUpdate;
|
||||
@override
|
||||
int? get libraryPageSize;
|
||||
|
||||
/// Create a copy of ClientSettingsModel
|
||||
|
|
|
|||
|
|
@ -36,9 +36,14 @@ _$ClientSettingsModelImpl _$$ClientSettingsModelImplFromJson(
|
|||
mouseDragSupport: json['mouseDragSupport'] as bool? ?? false,
|
||||
requireWifi: json['requireWifi'] as bool? ?? true,
|
||||
showAllCollectionTypes: json['showAllCollectionTypes'] as bool? ?? false,
|
||||
maxConcurrentDownloads:
|
||||
(json['maxConcurrentDownloads'] as num?)?.toInt() ?? 2,
|
||||
schemeVariant: $enumDecodeNullable(
|
||||
_$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(),
|
||||
);
|
||||
|
||||
|
|
@ -62,7 +67,11 @@ Map<String, dynamic> _$$ClientSettingsModelImplToJson(
|
|||
'mouseDragSupport': instance.mouseDragSupport,
|
||||
'requireWifi': instance.requireWifi,
|
||||
'showAllCollectionTypes': instance.showAllCollectionTypes,
|
||||
'maxConcurrentDownloads': instance.maxConcurrentDownloads,
|
||||
'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!,
|
||||
'backgroundPosters': instance.backgroundPosters,
|
||||
'checkForUpdates': instance.checkForUpdates,
|
||||
'lastViewedUpdate': instance.lastViewedUpdate,
|
||||
'libraryPageSize': instance.libraryPageSize,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
part 'home_settings_model.freezed.dart';
|
||||
part 'home_settings_model.g.dart';
|
||||
|
||||
@freezed
|
||||
@Freezed(copyWith: true)
|
||||
class HomeSettingsModel with _$HomeSettingsModel {
|
||||
factory HomeSettingsModel({
|
||||
@Default({...LayoutMode.values}) Set<LayoutMode> screenLayouts,
|
||||
|
|
@ -36,42 +37,6 @@ T selectAvailableOrSmaller<T>(T value, Set<T> availableOptions, List<T> allOptio
|
|||
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 {
|
||||
hide,
|
||||
carousel,
|
||||
|
|
|
|||
|
|
@ -205,32 +205,6 @@ class _$HomeSettingsModelImpl implements _HomeSettingsModel {
|
|||
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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import 'package:fladder/util/localization_helper.dart';
|
|||
part 'video_player_settings.freezed.dart';
|
||||
part 'video_player_settings.g.dart';
|
||||
|
||||
@freezed
|
||||
@Freezed(copyWith: true)
|
||||
class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel {
|
||||
const VideoPlayerSettingsModel._();
|
||||
|
||||
|
|
@ -43,7 +43,10 @@ class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel {
|
|||
PlayerOptions get wantedPlayer => playerOptions ?? PlayerOptions.platformDefaults;
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.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/media_segments_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.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';
|
||||
|
||||
@freezed
|
||||
@Freezed(copyWith: true)
|
||||
class SyncedItem with _$SyncedItem {
|
||||
const SyncedItem._();
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ abstract class $SyncedItemCopyWith<$Res> {
|
|||
List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData});
|
||||
|
||||
$MediaSegmentsModelCopyWith<$Res>? get mediaSegments;
|
||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
||||
}
|
||||
|
||||
|
|
@ -162,20 +161,6 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
|
|||
) 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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
|
|
@ -216,8 +201,6 @@ abstract class _$$SyncItemImplCopyWith<$Res>
|
|||
List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData});
|
||||
|
||||
@override
|
||||
$MediaSegmentsModelCopyWith<$Res>? get mediaSegments;
|
||||
@override
|
||||
$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)';
|
||||
}
|
||||
|
||||
@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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:fladder/models/syncing/sync_item.dart';
|
|||
|
||||
part 'sync_settings_model.freezed.dart';
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@Freezed(toJson: false, fromJson: false, copyWith: true)
|
||||
class SyncSettingsModel with _$SyncSettingsModel {
|
||||
const SyncSettingsModel._();
|
||||
|
||||
|
|
|
|||
|
|
@ -116,18 +116,6 @@ class _$SyncSettignsModelImpl extends _SyncSettignsModel {
|
|||
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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.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/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/syncing/sync_item.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
enum PlaybackType {
|
||||
directStream,
|
||||
|
|
@ -24,16 +25,11 @@ enum PlaybackType {
|
|||
PlaybackType.transcode => IconsaxPlusLinear.convert,
|
||||
};
|
||||
|
||||
String get name {
|
||||
switch (this) {
|
||||
case PlaybackType.directStream:
|
||||
return "Direct";
|
||||
case PlaybackType.offline:
|
||||
return "Offline";
|
||||
case PlaybackType.transcode:
|
||||
return "Transcoding";
|
||||
}
|
||||
}
|
||||
String name(BuildContext context) => switch (this) {
|
||||
PlaybackType.directStream => context.localized.playbackTypeDirect,
|
||||
PlaybackType.offline => context.localized.playbackTypeOffline,
|
||||
PlaybackType.transcode => context.localized.playbackTypeTranscode
|
||||
};
|
||||
}
|
||||
|
||||
class VideoPlayback {
|
||||
|
|
|
|||