diff --git a/.fvmrc b/.fvmrc deleted file mode 100644 index c0ef54d..0000000 --- a/.fvmrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "flutter": "3.32.0" -} \ No newline at end of file diff --git a/.github/actions/flutter-setup/action.yaml b/.github/actions/flutter-setup/action.yaml deleted file mode 100644 index 6534571..0000000 --- a/.github/actions/flutter-setup/action.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# .github/actions/flutter-setup/action.yml -name: "Flutter Setup Composite Action" -description: "Checks out code, sets up Java/Flutter, caches, and runs pub get" - -# Define inputs for customization (optional, but good practice) -inputs: - flutter-channel: - description: "Flutter channel to use (stable, beta, dev, master)" - required: false - default: "stable" - java-version: - description: "Java version to set up" - required: false - default: "17" - -runs: - using: "composite" # Specify this is a composite action - steps: - - name: Set up Java - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: ${{ inputs.java-version }} - - - name: Set up Flutter SDK - uses: subosito/flutter-action@v2 - with: - channel: ${{ inputs.flutter-channel }} - flutter-version-file: pubspec.yaml - cache: true # Cache Flutter SDK itself - - - name: Cache Flutter dependencies - id: cache-pub - uses: actions/cache@v4 - with: - path: ${{ env.FLUTTER_HOME }}/.pub-cache - key: ${{ runner.os }}-flutter-pub-${{ hashFiles('**/pubspec.lock') }} - restore-keys: | - ${{ runner.os }}-flutter-pub- - - - name: Get Flutter dependencies - run: flutter pub get - # Use shell: bash for potential cross-platform compatibility in complex commands - shell: bash - - # Add other common setup steps if needed diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 3e3ee30..9ec7edd 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -40,7 +40,7 @@ template: | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION exclude-labels: - - "skip changelog" + - "skip-changelog" exclude-contributors: - "Dr-Blank" @@ -55,15 +55,15 @@ autolabeler: branch: - '/feature\/.+/' title: - - "/^feat(ure)?/i" + - "/feat(ure)?/i" body: - "/JIRA-[0-9]{1,4}/" - label: "chore" title: - - "/^chore\b/i" + - "/chore/i" - label: "ui" title: - "/^ui\b/i" - label: "refactor" title: - - "/^refactor/i" + - "/refactor/i" diff --git a/.github/workflows/flutter-ci.yaml b/.github/workflows/flutter-ci.yaml deleted file mode 100644 index c503b65..0000000 --- a/.github/workflows/flutter-ci.yaml +++ /dev/null @@ -1,218 +0,0 @@ -name: Flutter CI & Release - -on: - push: - branches: [main] - tags: ["v*.*.*"] - pull_request: - branches: [main] - workflow_dispatch: - -jobs: - test: - name: Test - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup Flutter Environment - uses: ./.github/actions/flutter-setup # Path to the composite action directory - # Pass inputs if needed (optional, using defaults here) - # with: - # flutter-channel: 'stable' - # java-version: '17' - # Debug: Echo current directory contents - - name: List root directory contents - run: | - pwd - ls -la - - # Debug: Recursive directory structure - - name: Show full directory structure - run: | - echo "Full directory structure:" - tree -L 3 - - # Debug: Submodule status and details - - name: Check submodule status - run: | - echo "Submodule status:" - git submodule status - - echo "\nSubmodule details:" - git submodule foreach 'echo $path: && pwd && ls -la' - - # - name: Run static analysis - # run: flutter analyze - - - name: Check formatting - run: | - dart format -o none --set-exit-if-changed lib/ - - - name: Run tests - run: flutter test - - build_android: - name: Build Android APKs - needs: test - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Setup Flutter Environment - uses: ./.github/actions/flutter-setup # Path to the composite action directory - with: - flutter-channel: stable - java-version: 17 - - - name: Accept Android SDK Licenses - run: | - yes | sudo $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses - - - name: Decode android/upload.jks - run: echo "${{ secrets.UPLOAD_KEYSTORE_JKS }}" | base64 --decode > android/upload.jks - - - name: Decode android/key.properties - run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties - - - name: Build APKs - run: flutter build apk --release --split-per-abi - - - name: Build Universal APK - run: flutter build apk --release - - - name: Rename Universal APK - run: mv build/app/outputs/flutter-apk/{app-release,app-universal-release}.apk - - - name: Build App Bundle - run: flutter build appbundle --release - - - name: Upload Android APK Artifact - uses: actions/upload-artifact@v4 - with: - name: android-release-artifacts - path: | - build/app/outputs/flutter-apk/*-release*.apk - build/app/outputs/bundle/release/*.aab - - build_linux: - needs: test - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Setup Flutter Environment - uses: ./.github/actions/flutter-setup # Path to the composite action directory - - - name: Install Linux dependencies - run: | - sudo apt-get update -y - sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev locate libfuse2 - # Download and install appimagetool - wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage - chmod +x appimagetool-x86_64.AppImage - sudo mv appimagetool-x86_64.AppImage /usr/local/bin/appimagetool - shell: bash - - name: setup fastforge - run: | - dart pub global activate fastforge - - name: Build Linux AppImage and deb - run: fastforge package --platform linux --targets deb,appimage - - - name: Rename Linux Artifacts - run: | - # Find and rename .deb file - DEB_FILE=$(find dist/ -name "*.deb" -type f) - if [ -n "$DEB_FILE" ]; then - mv "$DEB_FILE" dist/vaani-linux-amd64.deb - echo "Renamed DEB: $DEB_FILE to dist/vaani-linux-amd64.deb" - else - echo "Error: .deb file not found in dist/" - exit 1 - fi - - # Find and rename .AppImage file - APPIMAGE_FILE=$(find dist/ -name "*.AppImage" -type f) - if [ -n "$APPIMAGE_FILE" ]; then - mv "$APPIMAGE_FILE" dist/vaani-linux-amd64.AppImage - echo "Renamed AppImage: $APPIMAGE_FILE to dist/vaani-linux-amd64.AppImage" - else - echo "Error: .AppImage file not found in dist/" - exit 1 - fi - shell: bash - - - name: Upload Linux Artifacts - uses: actions/upload-artifact@v4 - with: - name: linux-release-artifacts - path: | - dist/vaani-linux-amd64.deb - dist/vaani-linux-amd64.AppImage - - # Job 4: Create GitHub Release (NEW - runs only on tag pushes) - create_release: - name: Create GitHub Release - needs: [build_android, build_linux] # Depends on successful builds - runs-on: ubuntu-latest - permissions: - contents: write # Need write access to create release - # <<< CONDITION: Only run this job if the trigger was a tag starting with 'v' - if: startsWith(github.ref, 'refs/tags/v') - - steps: - # No checkout needed if only downloading artifacts and using context variables - # - name: Checkout repository - # uses: actions/checkout@v4 - - # Download artifacts created earlier IN THIS SAME WORKFLOW RUN - - name: Download Android Artifacts - uses: actions/download-artifact@v4 - with: - name: android-release-artifacts - path: ./release-artifacts/android - - - name: Download Linux Artifacts - uses: actions/download-artifact@v4 - with: - name: linux-release-artifacts - path: ./release-artifacts/linux - - - name: List downloaded files (for debugging) - run: ls -R ./release-artifacts - shell: bash - - # Extract version info from the tag - - name: Extract Version from Tag - id: version - run: | - TAG_NAME=${GITHUB_REF#refs/tags/} - VERSION=${TAG_NAME#v} - echo "tag=${TAG_NAME}" >> $GITHUB_OUTPUT - echo "version=${VERSION}" >> $GITHUB_OUTPUT - shell: bash - - # Generate release notes (optional, consider its configuration for tags) - - name: Generate Release Notes - id: generate_release_notes - uses: release-drafter/release-drafter@v6 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Create the GitHub Release using downloaded artifacts - - name: Create GitHub Release - uses: ncipollo/release-action@v1 - with: - artifacts: "./release-artifacts/**/*" # Use downloaded artifacts - name: Release v${{ steps.version.outputs.version }} - tag: ${{ github.ref }} - body: ${{ steps.generate_release_notes.outputs.body }} - # token: ${{ secrets.GITHUB_TOKEN }} # Usually inferred diff --git a/.github/workflows/flutter_release.yaml b/.github/workflows/flutter_release.yaml new file mode 100644 index 0000000..e5544be --- /dev/null +++ b/.github/workflows/flutter_release.yaml @@ -0,0 +1,84 @@ +name: Flutter Release Workflow + +on: + push: + tags: + - "v**" + # manually trigger a release if needed + workflow_dispatch: + +jobs: + build: + permissions: + # write permission is required to create a github release + contents: write + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Checkout shelfsdk + uses: actions/checkout@v3 + with: + repository: Dr-Blank/shelfsdk + path: ./shelfsdk + + - name: Set Up Java + uses: actions/setup-java@v3.12.0 + with: + distribution: "oracle" + java-version: "17" + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + + - name: Install dependencies + run: flutter pub get + + # - name: Run tests + # run: flutter test + + - name: Decode android/upload.jks + run: echo "${{ secrets.UPLOAD_KEYSTORE_JKS }}" | base64 --decode > android/upload.jks + + - name: Decode android/key.properties + run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties + + - name: Build APKs + run: flutter build apk --release --split-per-abi + + - name: Build Universal APK + run: flutter build apk --release + + - name: Rename Universal APK + run: mv build/app/outputs/flutter-apk/{app-release,app-release-universal}.apk + + - name: Build App Bundle + run: flutter build appbundle --release + + - name: version + id: version + run: | + tag=${GITHUB_REF/refs\/tags\//} + version=${tag#v} + major=${version%%.*} + echo "tag=${tag}" >> $GITHUB_OUTPUT + echo "version=${version}" >> $GITHUB_OUTPUT + echo "major=${major}" >> $GITHUB_OUTPUT + + - name: Generate Release Notes + id: generate_release_notes + uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Create GitHub Release + uses: ncipollo/release-action@v1 + with: + artifacts: "build/app/outputs/flutter-apk/*-release*.apk,build/app/outputs/bundle/release/*.aab" + name: v${{ steps.version.outputs.version }} + tag: ${{ github.ref }} + body: ${{ steps.generate_release_notes.outputs.body }} diff --git a/.github/workflows/flutter_test.yaml b/.github/workflows/flutter_test.yaml new file mode 100644 index 0000000..9c74813 --- /dev/null +++ b/.github/workflows/flutter_test.yaml @@ -0,0 +1,53 @@ +name: Flutter Test + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Decode android/upload.jks + run: echo "${{ secrets.UPLOAD_KEYSTORE_JKS }}" | base64 --decode > android/upload.jks + + - name: Decode android/key.properties + run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties + + - name: Checkout shelfsdk + uses: actions/checkout@v3 + with: + repository: Dr-Blank/shelfsdk + path: ./shelfsdk + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + + - name: Install dependencies + run: flutter pub get + + - name: Run tests + run: flutter test + + - name: Set Up Java + uses: actions/setup-java@v3.12.0 + with: + distribution: "oracle" + java-version: "17" + + - name: Build APK + run: flutter build apk --release + + - name: Upload APKs + uses: actions/upload-artifact@v4 + with: + name: app-release + path: build/app/outputs/flutter-apk/*.apk diff --git a/.github/workflows/prepare-release.yaml b/.github/workflows/prepare-release.yaml deleted file mode 100644 index ceb9105..0000000 --- a/.github/workflows/prepare-release.yaml +++ /dev/null @@ -1,130 +0,0 @@ -# .github/workflows/prepare-release.yml -name: Prepare Release (using Cider) - -on: - workflow_dispatch: - inputs: - bump_type: - description: "Type of version bump (patch, minor, major)" - required: true - type: choice - options: - - patch - - minor - - major - default: "patch" - -permissions: - contents: write # NEEDED to commit, push, and tag - -jobs: - bump_version_and_tag: - name: Bump Version and Tag using Cider - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - # Use a PAT if pushing to protected branches is restricted for GITHUB_TOKEN - token: ${{ secrets.PAT_TOKEN }} # Create PAT with repo scope - # token: ${{ secrets.GITHUB_TOKEN }} # this does not trigger other workflows - - # Setup Flutter/Dart environment needed to run dart pub global activate - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" # Or match your project's channel - flutter-version-file: pubspec.yaml - - - name: Install Cider - run: dart pub global activate cider - shell: bash - - # Add pub global bin to PATH for this job - - name: Add pub global bin to PATH - run: echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH - shell: bash - - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - shell: bash - - - name: Bump version using Cider - id: bump - run: | - echo "Current version:" - grep '^version:' pubspec.yaml - - # Run cider to bump version and build number - # Cider modifies pubspec.yaml in place - cider bump ${{ github.event.inputs.bump_type }} --bump-build - - echo "New version (after cider bump):" - # Read the *new* version directly from the modified file - new_version_line=$(grep '^version:' pubspec.yaml) - # Extract just the version string (e.g., 1.2.3+4) - new_version=$(echo "$new_version_line" | sed 's/version: *//') - - echo "$new_version_line" - echo "Extracted new version: $new_version" - - if [[ -z "$new_version" ]]; then - echo "Error: Could not extract new version after cider bump." - exit 1 - fi - - # Create tag name (e.g., v1.2.3 - usually tags don't include build number) - # Extract version part before '+' for the tag - version_for_tag=$(echo "$new_version" | cut -d'+' -f1) - new_tag="v$version_for_tag" - echo "New tag: $new_tag" - - # Set outputs for later steps - echo "new_version=$new_version" >> $GITHUB_OUTPUT - echo "new_tag=$new_tag" >> $GITHUB_OUTPUT - shell: bash - - - name: Commit version bump - run: | - # Add pubspec.yaml. Add CHANGELOG.md if cider modifies it and you want to commit it. - git add pubspec.yaml - # git add CHANGELOG.md # Uncomment if needed - - # Check if there are changes to commit - if git diff --staged --quiet; then - echo "No changes detected in pubspec.yaml (or CHANGELOG.md) to commit." - else - # Use the version *without* build number for the commit message usually - git commit -m "chore(release): bump version to ${{ steps.bump.outputs.new_tag }}" - fi - shell: bash - - - name: Create Git tag - # Only run if the commit step actually committed something (check git status) - # or simply run always, it won't hurt if the commit was skipped - run: | - git tag ${{ steps.bump.outputs.new_tag }} - shell: bash - - - name: Push changes and tag - run: | - # Push the commit first (e.g., to main branch - adjust if needed) - # Handle potential conflicts if main changed since checkout? (More advanced setup) - # Check if there are commits to push before pushing branch - if ! git diff --quiet HEAD^ HEAD; then - echo "Pushing commit to main..." - git push origin HEAD:main - else - echo "No new commits to push to main." - fi - - # Always push the tag - echo "Pushing tag ${{ steps.bump.outputs.new_tag }}..." - git push origin ${{ steps.bump.outputs.new_tag }} - shell: bash - - - name: Output New Tag - run: echo "Successfully tagged release ${{ steps.bump.outputs.new_tag }}" diff --git a/.gitignore b/.gitignore index 3eee232..c137f7a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,6 @@ migrate_working_dir/ .pub-cache/ .pub/ /build/ -dist/ # Symbolication related app.*.symbols @@ -42,10 +41,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release -/android/app/.cxx/ -# secret keys -/secrets - -# FVM Version Cache -.fvm/ \ No newline at end of file +# separate git repo for api sdk +/shelfsdk \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 557497e..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "shelfsdk"] - path = shelfsdk - url = https://github.com/Dr-Blank/shelfsdk diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d5d2ff..18ecea1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,6 @@ { "name": "vaani", "request": "launch", - "program": "lib/main.dart", "type": "dart" }, { diff --git a/.vscode/settings.json b/.vscode/settings.json index 9185c43..ffa9f03 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,35 +1,27 @@ { - "cmake.configureOnOpen": false, + "workbench.colorCustomizations": { + "activityBar.background": "#5A1021", + "titleBar.activeBackground": "#7E162E", + "titleBar.activeForeground": "#FEFBFC" + }, + "files.exclude": { + "**/*.freezed.dart": true, + "**/*.g.dart": true + }, "cSpell.words": [ "audioplayers", "autolabeler", "Autovalidate", - "Checkmark", - "Debounceable", "deeplinking", "fullscreen", "Lerp", "miniplayer", "mocktail", - "nodename", - "numberpicker", "riverpod", - "Schyler", "shelfsdk", - "sysname", "tapable", "unfocus", - "utsname", "Vaani" ], - "dart.flutterSdkPath": ".fvm/versions/3.32.0", - "files.exclude": { - "**/*.freezed.dart": true, - "**/*.g.dart": true - }, - "workbench.colorCustomizations": { - "activityBar.background": "#5A1021", - "titleBar.activeBackground": "#7E162E", - "titleBar.activeForeground": "#FEFBFC" - } + "cmake.configureOnOpen": false } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 606e94c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,181 +0,0 @@ -# Contributing to Vaani - -## Welcome Contributors! 🚀 - -We appreciate your interest in contributing to Vaani. This guide will help you navigate the contribution process effectively. - -## How to Contribute - -### Reporting Bugs 🐞 - -1. **Check Existing Issues**: - - Search through the [GitHub Issues](https://github.com/Dr-Blank/Vaani/issues) - -2. **Create a Detailed Bug Report**: - - Provide: - * Exact steps to reproduce - * Relevant error logs or screenshots - -### Submodule Contribution Workflow 🧩 - -#### Understanding Vaani's Submodule Structure - -Vaani uses Git submodules to manage interconnected components. This means each submodule is a separate Git repository nested within the main project. - -#### Working with Submodules - -1. **Identifying Submodules**: - - List all submodules in the project - ```bash - git submodule status - ``` - -2. **Initializing Submodules**: - ```bash - # Ensure all submodules are initialized - git submodule update --init --recursive - ``` - -3. **Contributing to a Specific Submodule**: - - a. **Navigate to Submodule Directory**: - ```bash - cd path/to/submodule - ``` - - b. **Create a Separate Branch**: - ```bash - git checkout -b feature/your-submodule-feature - ``` - - c. **Make and Commit Changes**: - ```bash - # Stage changes - git add . - - # Commit with descriptive message - git commit -m "feat(submodule): describe specific change" - ``` - - d. **Push Submodule Changes**: - ```bash - git push origin feature/your-submodule-feature - ``` - -4. **Updating Submodule References**: - After making changes to a submodule: - ```bash - # From the main repository root - git add path/to/submodule - git commit -m "Update submodule reference to latest changes" - ``` - -5. **Pulling Latest Submodule Changes**: - ```bash - # Update all submodules - git submodule update --recursive --remote - - # Or update a specific submodule - git submodule update --remote path/to/specific/submodule - ``` - -#### Submodule Contribution Best Practices - -- Always work in a feature branch within the submodule -- Ensure submodule changes do not break the main application -- Write tests for submodule-specific changes -- Update documentation if the submodule's interface changes -- Create a pull request for the submodule first, then update the main project's submodule reference - -### Development Workflow - -#### Setting Up the Development Environment - -1. **Prerequisites**: - - [Git](https://git-scm.com/) - - [Flutter SDK](https://flutter.dev/) - - Recommended IDE: [VS Code](https://code.visualstudio.com/) - -2. **Repository Setup**: - - 1. [Fork the repo](https://github.com/Dr-Blank/Vaani/fork) - 1. Clone the forked repository to your local machine - ```bash - # Fork the main repository on GitHub - git clone --recursive https://github.com/[YOUR_USERNAME]/Vaani.git - cd Vaani - - # Initialize and update submodules - git submodule update --init --recursive - - # Install dependencies for the main app and submodules - flutter pub get - ``` - -#### Coding Standards - -1. **Code Style**: - - Follow [Flutter's style guide](https://dart.dev/guides/language/effective-dart/style) - - Use `dart format` and `flutter analyze` - - ```bash - dart format . - flutter analyze - ``` - -2. **Testing**: - - Write unit and widget tests - - Ensure tests pass for both the main app and submodules - - ```bash - flutter test - ``` - -### Pull Request Process - -1. **Branch Naming**: - - Use descriptive branch names - - Prefix with feature/, bugfix/, or docs/ - - ```bash - git checkout -b feature/add-accessibility-support - ``` - -2. **Commit Messages**: - - Use clear, concise descriptions - - Reference issue numbers when applicable - - Follow conventional commits format: - `(scope): ` - -3. **Pull Request Guidelines**: - - Clearly describe the purpose of your changes - - Include screenshots for visual changes - - Specify if changes affect specific submodules - - Ensure all CI checks pass - -### Signing the app - -once the keystore is created, you can sign the app with the keystore. - -but for github action you need to make a base64 encoded string of the keystore. - -```bash -# convert keystore to base64 -cat android/key.properties | base64 > key.base64 - -# convert keystore to base64 -cat android/upload.jks | base64 > keystore.base64 -``` - -## Communication - -* [Open an Issue](https://github.com/Dr-Blank/Vaani/issues) -* [Discussion Forum](https://github.com/Dr-Blank/Vaani/discussions) - -## Code of Conduct - -* Be respectful and inclusive -* Constructive feedback is welcome -* Collaborate and support fellow contributors - -Happy Contributing! 🌟 \ No newline at end of file diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 7a118b4..0000000 --- a/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "fastlane" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 1db5478..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,221 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.7) - base64 - nkf - rexml - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) - artifactory (3.0.17) - atomos (0.1.3) - aws-eventstream (1.3.0) - aws-partitions (1.1018.0) - aws-sdk-core (3.214.0) - aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.992.0) - aws-sigv4 (~> 1.9) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.96.0) - aws-sdk-core (~> 3, >= 3.210.0) - aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.176.0) - aws-sdk-core (~> 3, >= 3.210.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.5) - aws-sigv4 (1.10.1) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - base64 (0.2.0) - claide (1.1.0) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - declarative (0.0.20) - digest-crc (0.6.5) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20240107) - dotenv (2.8.1) - emoji_regex (3.2.3) - excon (0.112.0) - faraday (1.10.4) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.2) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.1) - faraday (~> 1.0) - fastimage (2.3.1) - fastlane (2.225.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored (~> 1.2) - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - fastlane-sirp (>= 1.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-env (>= 1.6.0, < 2.0.0) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - http-cookie (~> 1.0.5) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (>= 2.0.0, < 3.0.0) - naturally (~> 2.2) - optparse (>= 0.1.1, < 1.0.0) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.5) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (~> 3) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) - fastlane-sirp (1.0.0) - sysrandom (~> 1.0) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.54.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.3) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-playcustomapp_v1 (0.13.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.31.0) - google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.7.1) - google-cloud-env (>= 1.0, < 3.a) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.4.0) - google-cloud-storage (1.47.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.31.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.8) - domain_name (~> 0.5) - httpclient (2.8.3) - jmespath (1.6.2) - json (2.9.0) - jwt (2.9.3) - base64 - mini_magick (4.13.2) - mini_mime (1.1.5) - multi_json (1.15.0) - multipart-post (2.4.1) - nanaimo (0.4.0) - naturally (2.2.1) - nkf (0.2.0) - optparse (0.6.0) - os (1.1.4) - plist (3.7.1) - public_suffix (6.0.1) - rake (13.2.1) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.3.9) - rouge (2.0.7) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.5) - signet (0.19.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - sysrandom (1.0.5) - terminal-notifier (2.0.0) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.2) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unicode-display_width (2.6.0) - word_wrap (1.0.0) - xcodeproj (1.27.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.4.0) - rexml (>= 3.3.6, < 4.0) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - x64-mingw-ucrt - -DEPENDENCIES - fastlane - -BUNDLED WITH - 2.5.23 diff --git a/README.md b/README.md index 14f16fb..c50a48f 100644 --- a/README.md +++ b/README.md @@ -18,25 +18,19 @@ Client for [Audiobookshelf](https://github.com/advplyr/audiobookshelf) server ma ### Android -[Get it on Obtainium](http://apps.obtainium.imranr.dev/redirect.html?r=obtainium://add/https://github.com/Dr-Blank/Vaani) -[Get it on Google Play](https://play.google.com/store/apps/details?id=dr.blank.vaani) -[Get it on IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/dr.blank.vaani) -[Get it on GitHub](https://github.com/Dr-Blank/Vaani/releases/latest/download/app-universal-release.apk) +[Get it on GitHub](https://github.com/Dr-Blank/Vaani/releases/latest/download/app-release-universal.apk) [Get it on Obtainium](http://apps.obtainium.imranr.dev/redirect.html?r=obtainium://add/https://github.com/Dr-Blank/Vaani) -*Play Store version is paid if you want to support the development.* - -### Linux - -[Download Linux (.deb)](https://github.com/Dr-Blank/Vaani/releases/latest/download/vaani-linux-amd64.deb) -[Download Linux (AppImage)](https://github.com/Dr-Blank/Vaani/releases/latest/download/vaani-linux-amd64.AppImage) +Playstore App is in closed testing. To join testing +1. [Join the Google Group](https://groups.google.com/g/vaani-app) +2. [Join on Android](https://play.google.com/store/apps/details?id=dr.blank.vaani) Or [Join on Web](https://play.google.com/apps/testing/dr.blank.vaani) ## Screencaps https://github.com/user-attachments/assets/2ac9ace2-4a3c-40fc-adde-55914e4cf62d -| | | | -| :-----------------------------------------------------------: | :---------------------------------------------------------------: | :-------------------------------------------------------------: | -| Home | Book View | Player | +|||| +|:---:|:---:|:---:| +|Home|Book View|Player| Currently, the app is in development and is not ready for production use. diff --git a/analysis_options.yaml b/analysis_options.yaml index 1a27822..2c5752a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -25,10 +25,6 @@ linter: # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule require_trailing_commas: true analyzer: - exclude: - - '**.freezed.dart' - - '**.g.dart' - - '**.gr.dart' errors: invalid_annotation_target: ignore plugins: diff --git a/android/app/build.gradle b/android/app/build.gradle index 0161f0d..b13356f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -31,11 +31,7 @@ if (keystorePropertiesFile.exists()) { android { namespace "dr.blank.vaani" compileSdk flutter.compileSdkVersion - // ndkVersion flutter.ndkVersion - // The NDK version is set to a specific version since it was not building - // TODO remove when https://github.com/flutter/flutter/issues/139427 is closed - ndkVersion = "29.0.13113456" - + ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -50,15 +46,6 @@ android { main.java.srcDirs += 'src/main/kotlin' } - // see: https://gitlab.com/IzzyOnDroid/repo/-/issues/623#note_2149548690 - // https://android.izzysoft.de/articles/named/iod-scan-apkchecks#blobs - dependenciesInfo { - // Disables dependency metadata when building APKs. - includeInApk = false - // Disables dependency metadata when building Android App Bundles. - includeInBundle = false - } - defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "dr.blank.vaani" @@ -93,11 +80,3 @@ flutter { } dependencies {} - -// https://stackoverflow.com/questions/78626580/how-to-resolve-app-execution-failure-due-to-androidx-corecore1-15-0-alpha -configurations.all { - resolutionStrategy { - force "androidx.core:core:1.13.1" - force "androidx.core:core-ktx:1.13.1" - } -} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3856060..71b0a89 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,13 +7,10 @@ - - Vaani is a client for your (self-hosted) Audiobookshelf server. - -Features: - -- Functional Player: Speed Control, Sleep Timer, Shake to Control Player -- Save data with Offline listening and caching -- Material Design -- Extensive Settings to customize the every tiny detail - -Note: you need an Audiobookshelf server setup for this app to work. Please see https://www.audiobookshelf.org/ on how to setup one if not already. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png deleted file mode 100644 index ed9c09d..0000000 Binary files a/fastlane/metadata/android/en-US/images/featureGraphic.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png deleted file mode 100644 index b260956..0000000 Binary files a/fastlane/metadata/android/en-US/images/icon.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.jpeg deleted file mode 100644 index a84ddb6..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.jpeg deleted file mode 100644 index 7b6c86b..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.jpeg deleted file mode 100644 index 3c1dbeb..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.jpeg deleted file mode 100644 index afce5bb..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.jpeg deleted file mode 100644 index 4a51186..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.jpeg deleted file mode 100644 index e45f00a..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.jpeg deleted file mode 100644 index 3e625ed..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/8_en-US.jpeg b/fastlane/metadata/android/en-US/images/phoneScreenshots/8_en-US.jpeg deleted file mode 100644 index 3714195..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/8_en-US.jpeg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt deleted file mode 100644 index 0d9933e..0000000 --- a/fastlane/metadata/android/en-US/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Beautiful, Fast and Functional Audiobook Player for your Audiobookshelf server. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt deleted file mode 100644 index ed0326a..0000000 --- a/fastlane/metadata/android/en-US/title.txt +++ /dev/null @@ -1 +0,0 @@ -Vaani \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/video.txt b/fastlane/metadata/android/en-US/video.txt deleted file mode 100644 index e69de29..0000000 diff --git a/fastlane/report.xml b/fastlane/report.xml deleted file mode 100644 index 9f24822..0000000 --- a/fastlane/report.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/images/vaani_logo.svg b/images/vaani_logo.svg deleted file mode 100644 index 6c79c5a..0000000 --- a/images/vaani_logo.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Created by potrace 1.16, written by Peter Selinger 2001-2019 - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/api/api_provider.dart b/lib/api/api_provider.dart index a991727..1d3745f 100644 --- a/lib/api/api_provider.dart +++ b/lib/api/api_provider.dart @@ -2,15 +2,12 @@ import 'dart:convert'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:http/http.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:vaani/db/cache_manager.dart'; -import 'package:vaani/models/error_response.dart'; import 'package:vaani/settings/api_settings_provider.dart'; -import 'package:vaani/settings/models/authenticated_user.dart'; import 'package:vaani/shared/extensions/obfuscation.dart'; part 'api_provider.g.dart'; @@ -35,7 +32,7 @@ Uri makeBaseUrl(String address) { /// get the api instance for the given base url @riverpod -AudiobookshelfApi audiobookshelfApi(Ref ref, Uri? baseUrl) { +AudiobookshelfApi audiobookshelfApi(AudiobookshelfApiRef ref, Uri? baseUrl) { // try to get the base url from app settings final apiSettings = ref.watch(apiSettingsProvider); baseUrl ??= apiSettings.activeServer?.serverUrl; @@ -48,10 +45,10 @@ AudiobookshelfApi audiobookshelfApi(Ref ref, Uri? baseUrl) { /// /// if the user is not authenticated throw an error @Riverpod(keepAlive: true) -AudiobookshelfApi authenticatedApi(Ref ref) { - final user = ref.watch(apiSettingsProvider.select((s) => s.activeUser)); +AudiobookshelfApi authenticatedApi(AuthenticatedApiRef ref) { + final apiSettings = ref.watch(apiSettingsProvider); + final user = apiSettings.activeUser; if (user == null) { - _logger.severe('No active user can not provide authenticated api'); throw StateError('No active user'); } return AudiobookshelfApi( @@ -62,7 +59,7 @@ AudiobookshelfApi authenticatedApi(Ref ref) { /// ping the server to check if it is reachable @riverpod -FutureOr isServerAlive(Ref ref, String address) async { +FutureOr isServerAlive(IsServerAliveRef ref, String address) async { if (address.isEmpty) { return false; } @@ -80,7 +77,7 @@ FutureOr isServerAlive(Ref ref, String address) async { /// fetch status of server @riverpod FutureOr serverStatus( - Ref ref, + ServerStatusRef ref, Uri baseUrl, [ ResponseErrorHandler? responseErrorHandler, ]) async { @@ -100,28 +97,17 @@ class PersonalizedView extends _$PersonalizedView { final api = ref.watch(authenticatedApiProvider); final apiSettings = ref.watch(apiSettingsProvider); final user = apiSettings.activeUser; - if (user == null) { - _logger.warning('no active user'); - yield []; - return; - } if (apiSettings.activeLibraryId == null) { // set it to default user library by logging in and getting the library id - final login = await ref.read(loginProvider().future); - if (login == null) { - _logger.shout('failed to login, not building personalized view'); - yield []; - return; - } + final login = + await api.login(username: user!.username!, password: user.password!); ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith(activeLibraryId: login.userDefaultLibraryId), + apiSettings.copyWith(activeLibraryId: login!.userDefaultLibraryId), ); - yield []; - return; } // try to find in cache // final cacheKey = 'personalizedView:${apiSettings.activeLibraryId}'; - final key = 'personalizedView:${apiSettings.activeLibraryId! + user.id}'; + var key = 'personalizedView:${apiSettings.activeLibraryId! + user!.id!}'; final cachedRes = await apiResponseCacheManager.getFileFromMemory( key, ) ?? @@ -141,7 +127,7 @@ class PersonalizedView extends _$PersonalizedView { } } - // ! exaggerated delay + // ! exagerated delay // await Future.delayed(const Duration(seconds: 2)); final res = await api.libraries .getPersonalized(libraryId: apiSettings.activeLibraryId!); @@ -165,7 +151,6 @@ class PersonalizedView extends _$PersonalizedView { // method to force refresh the view and ignore the cache Future forceRefresh() async { // clear the cache - // TODO: find a better way to clear the cache for only personalized view key return apiResponseCacheManager.emptyCache(); } } @@ -173,7 +158,7 @@ class PersonalizedView extends _$PersonalizedView { /// fetch continue listening audiobooks @riverpod FutureOr fetchContinueListening( - Ref ref, + FetchContinueListeningRef ref, ) async { final api = ref.watch(authenticatedApiProvider); final res = await api.me.getSessions(); @@ -185,50 +170,9 @@ FutureOr fetchContinueListening( @riverpod FutureOr me( - Ref ref, + MeRef ref, ) async { final api = ref.watch(authenticatedApiProvider); - final errorResponseHandler = ErrorResponseHandler(); - final res = await api.me.getUser( - responseErrorHandler: errorResponseHandler.storeError, - ); - if (res == null) { - _logger.severe( - 'me failed, got response: ${errorResponseHandler.response.obfuscate()}', - ); - throw StateError('me failed'); - } - return res; -} - -@riverpod -FutureOr login( - Ref ref, { - AuthenticatedUser? user, -}) async { - if (user == null) { - // try to get the user from settings - final apiSettings = ref.watch(apiSettingsProvider); - user = apiSettings.activeUser; - if (user == null) { - _logger.severe('no active user to login'); - return null; - } - _logger.fine('no user provided, using active user: ${user.obfuscate()}'); - } - final api = ref.watch(audiobookshelfApiProvider(user.server.serverUrl)); - api.token = user.authToken; - var errorResponseHandler = ErrorResponseHandler(); - _logger.fine('logging in with authenticated api'); - final res = await api.misc.authorize( - responseErrorHandler: errorResponseHandler.storeError, - ); - if (res == null) { - _logger.severe( - 'login failed, got response: ${errorResponseHandler.response.obfuscate()}', - ); - return null; - } - _logger.fine('login response: ${res.obfuscate()}'); - return res; + final res = await api.me.getUser(); + return res!; } diff --git a/lib/api/api_provider.g.dart b/lib/api/api_provider.g.dart index 619a729..7758c7b 100644 --- a/lib/api/api_provider.g.dart +++ b/lib/api/api_provider.g.dart @@ -6,7 +6,7 @@ part of 'api_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$audiobookshelfApiHash() => r'f23a06c404e11867a7f796877eaca99b8ff25458'; +String _$audiobookshelfApiHash() => r'2c310ea77fea9918ccf96180a92075acd037bd95'; /// Copied from Dart SDK class _SystemHash { @@ -154,8 +154,6 @@ class AudiobookshelfApiProvider extends AutoDisposeProvider { } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin AudiobookshelfApiRef on AutoDisposeProviderRef { /// The parameter `baseUrl` of this provider. Uri? get baseUrl; @@ -170,7 +168,7 @@ class _AudiobookshelfApiProviderElement Uri? get baseUrl => (origin as AudiobookshelfApiProvider).baseUrl; } -String _$authenticatedApiHash() => r'284be2c39823c20fb70035a136c430862c28fa27'; +String _$authenticatedApiHash() => r'f555efb6eede590b5a8d60cad2e6bfc2847e2d14'; /// get the api instance for the authenticated user /// @@ -188,10 +186,8 @@ final authenticatedApiProvider = Provider.internal( allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef AuthenticatedApiRef = ProviderRef; -String _$isServerAliveHash() => r'bb3a53cae1eb64b8760a56864feed47b7a3f1c29'; +String _$isServerAliveHash() => r'6ff90b6e0febd2cd4a4d3a5209a59afc778cd3b6'; /// ping the server to check if it is reachable /// @@ -318,8 +314,6 @@ class IsServerAliveProvider extends AutoDisposeFutureProvider { } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin IsServerAliveRef on AutoDisposeFutureProviderRef { /// The parameter `address` of this provider. String get address; @@ -333,7 +327,7 @@ class _IsServerAliveProviderElement String get address => (origin as IsServerAliveProvider).address; } -String _$serverStatusHash() => r'2d9c5d6f970caec555e5322d43a388ea8572619f'; +String _$serverStatusHash() => r'd7079e19e68f5f61b0afa0f73a2af8807c4b3cf6'; /// fetch status of server /// @@ -473,8 +467,6 @@ class ServerStatusProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin ServerStatusRef on AutoDisposeFutureProviderRef { /// The parameter `baseUrl` of this provider. Uri get baseUrl; @@ -496,7 +488,7 @@ class _ServerStatusProviderElement } String _$fetchContinueListeningHash() => - r'50aeb77369eda38d496b2f56f3df2aea135dab45'; + r'f65fe3ac3a31b8ac074330525c5d2cc4b526802d'; /// fetch continue listening audiobooks /// @@ -513,11 +505,9 @@ final fetchContinueListeningProvider = allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef FetchContinueListeningRef = AutoDisposeFutureProviderRef; -String _$meHash() => r'b3b6d6d940b465c60d0c29cd6e81ba2fcccab186'; +String _$meHash() => r'bdc664c4fd867ad13018fa769ce7a6913248c44f'; /// See also [me]. @ProviderFor(me) @@ -530,139 +520,8 @@ final meProvider = AutoDisposeFutureProvider.internal( allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef MeRef = AutoDisposeFutureProviderRef; -String _$loginHash() => r'99410c2bed9c8f412c7b47c4e655db64e0054be2'; - -/// See also [login]. -@ProviderFor(login) -const loginProvider = LoginFamily(); - -/// See also [login]. -class LoginFamily extends Family> { - /// See also [login]. - const LoginFamily(); - - /// See also [login]. - LoginProvider call({ - AuthenticatedUser? user, - }) { - return LoginProvider( - user: user, - ); - } - - @override - LoginProvider getProviderOverride( - covariant LoginProvider provider, - ) { - return call( - user: provider.user, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'loginProvider'; -} - -/// See also [login]. -class LoginProvider extends AutoDisposeFutureProvider { - /// See also [login]. - LoginProvider({ - AuthenticatedUser? user, - }) : this._internal( - (ref) => login( - ref as LoginRef, - user: user, - ), - from: loginProvider, - name: r'loginProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$loginHash, - dependencies: LoginFamily._dependencies, - allTransitiveDependencies: LoginFamily._allTransitiveDependencies, - user: user, - ); - - LoginProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.user, - }) : super.internal(); - - final AuthenticatedUser? user; - - @override - Override overrideWith( - FutureOr Function(LoginRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: LoginProvider._internal( - (ref) => create(ref as LoginRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - user: user, - ), - ); - } - - @override - AutoDisposeFutureProviderElement createElement() { - return _LoginProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is LoginProvider && other.user == user; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, user.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin LoginRef on AutoDisposeFutureProviderRef { - /// The parameter `user` of this provider. - AuthenticatedUser? get user; -} - -class _LoginProviderElement - extends AutoDisposeFutureProviderElement with LoginRef { - _LoginProviderElement(super.provider); - - @override - AuthenticatedUser? get user => (origin as LoginProvider).user; -} - -String _$personalizedViewHash() => r'425e89d99d7e4712b4d6a688f3a12442bd66584f'; +String _$personalizedViewHash() => r'4c392ece4650bdc36d7195a0ddb8810e8fe4caa9'; /// fetch the personalized view /// @@ -681,4 +540,4 @@ final personalizedViewProvider = typedef _$PersonalizedView = AutoDisposeStreamNotifier>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/api/authenticated_users_provider.dart b/lib/api/authenticated_user_provider.dart similarity index 86% rename from lib/api/authenticated_users_provider.dart rename to lib/api/authenticated_user_provider.dart index 5e78fab..c4f065c 100644 --- a/lib/api/authenticated_users_provider.dart +++ b/lib/api/authenticated_user_provider.dart @@ -8,15 +8,15 @@ import 'package:vaani/settings/models/audiobookshelf_server.dart'; import 'package:vaani/settings/models/authenticated_user.dart' as model; import 'package:vaani/shared/extensions/obfuscation.dart'; -part 'authenticated_users_provider.g.dart'; +part 'authenticated_user_provider.g.dart'; final _box = AvailableHiveBoxes.authenticatedUserBox; -final _logger = Logger('authenticated_users_provider'); +final _logger = Logger('authenticated_user_provider'); /// provides with a set of authenticated users @riverpod -class AuthenticatedUsers extends _$AuthenticatedUsers { +class AuthenticatedUser extends _$AuthenticatedUser { @override Set build() { ref.listenSelf((_, __) { @@ -56,7 +56,6 @@ class AuthenticatedUsers extends _$AuthenticatedUsers { void addUser(model.AuthenticatedUser user, {bool setActive = false}) { state = state..add(user); - ref.invalidateSelf(); if (setActive) { final apiSettings = ref.read(apiSettingsProvider); ref.read(apiSettingsProvider.notifier).updateState( @@ -83,12 +82,9 @@ class AuthenticatedUsers extends _$AuthenticatedUsers { // also remove the user from the active user final apiSettings = ref.read(apiSettingsProvider); if (apiSettings.activeUser == user) { - // replace the active user with the first user in the list - // or null if there are no users left - final newActiveUser = state.isNotEmpty ? state.first : null; ref.read(apiSettingsProvider.notifier).updateState( apiSettings.copyWith( - activeUser: newActiveUser, + activeUser: null, ), ); } diff --git a/lib/api/authenticated_user_provider.g.dart b/lib/api/authenticated_user_provider.g.dart new file mode 100644 index 0000000..65f7c32 --- /dev/null +++ b/lib/api/authenticated_user_provider.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'authenticated_user_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$authenticatedUserHash() => r'1983527595207c63a12bc84cf0bf1a3c1d729506'; + +/// provides with a set of authenticated users +/// +/// Copied from [AuthenticatedUser]. +@ProviderFor(AuthenticatedUser) +final authenticatedUserProvider = AutoDisposeNotifierProvider>.internal( + AuthenticatedUser.new, + name: r'authenticatedUserProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$authenticatedUserHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AuthenticatedUser = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/api/authenticated_users_provider.g.dart b/lib/api/authenticated_users_provider.g.dart deleted file mode 100644 index 44a2610..0000000 --- a/lib/api/authenticated_users_provider.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'authenticated_users_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$authenticatedUsersHash() => - r'5fdd472f62fc3b73ff8417cdce9f02e86c33d00f'; - -/// provides with a set of authenticated users -/// -/// Copied from [AuthenticatedUsers]. -@ProviderFor(AuthenticatedUsers) -final authenticatedUsersProvider = AutoDisposeNotifierProvider< - AuthenticatedUsers, Set>.internal( - AuthenticatedUsers.new, - name: r'authenticatedUsersProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$authenticatedUsersHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$AuthenticatedUsers - = AutoDisposeNotifier>; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/api/image_provider.g.dart b/lib/api/image_provider.g.dart index 25f94d6..5fa646e 100644 --- a/lib/api/image_provider.g.dart +++ b/lib/api/image_provider.g.dart @@ -155,8 +155,6 @@ class CoverImageProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin CoverImageRef on StreamNotifierProviderRef { /// The parameter `itemId` of this provider. String get itemId; @@ -171,4 +169,4 @@ class _CoverImageProviderElement String get itemId => (origin as CoverImageProvider).itemId; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/api/library_item_provider.g.dart b/lib/api/library_item_provider.g.dart index 5261d9c..af297d0 100644 --- a/lib/api/library_item_provider.g.dart +++ b/lib/api/library_item_provider.g.dart @@ -170,8 +170,6 @@ class LibraryItemProvider extends StreamNotifierProviderImpl { /// The parameter `id` of this provider. @@ -186,4 +184,4 @@ class _LibraryItemProviderElement extends StreamNotifierProviderElement< String get id => (origin as LibraryItemProvider).id; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/api/library_provider.dart b/lib/api/library_provider.dart deleted file mode 100644 index 62c79d8..0000000 --- a/lib/api/library_provider.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart' show Ref; -import 'package:logging/logging.dart' show Logger; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -import 'package:shelfsdk/audiobookshelf_api.dart' show Library; -import 'package:vaani/api/api_provider.dart' show authenticatedApiProvider; -import 'package:vaani/settings/api_settings_provider.dart' - show apiSettingsProvider; -part 'library_provider.g.dart'; - -final _logger = Logger('LibraryProvider'); - -@riverpod -Future library(Ref ref, String id) async { - final api = ref.watch(authenticatedApiProvider); - final library = await api.libraries.get(libraryId: id); - if (library == null) { - _logger.warning('No library found through id: $id'); - // try to get the library from the list of libraries - final libraries = await ref.watch(librariesProvider.future); - for (final lib in libraries) { - if (lib.id == id) { - return lib; - } - } - _logger.warning('No library found in the list of libraries'); - return null; - } - _logger.fine('Fetched library: $library'); - return library.library; -} - -@riverpod -Future currentLibrary(Ref ref) async { - final libraryId = - ref.watch(apiSettingsProvider.select((s) => s.activeLibraryId)); - if (libraryId == null) { - _logger.warning('No active library id found'); - return null; - } - return await ref.watch(libraryProvider(libraryId).future); -} - -@riverpod -class Libraries extends _$Libraries { - @override - FutureOr> build() async { - final api = ref.watch(authenticatedApiProvider); - final libraries = await api.libraries.getAll(); - if (libraries == null) { - _logger.warning('Failed to fetch libraries'); - return []; - } - _logger.fine('Fetched ${libraries.length} libraries'); - ref.keepAlive(); - return libraries; - } -} diff --git a/lib/api/library_provider.g.dart b/lib/api/library_provider.g.dart deleted file mode 100644 index a8bc88a..0000000 --- a/lib/api/library_provider.g.dart +++ /dev/null @@ -1,192 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'library_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$libraryHash() => r'f8a34100acb58f02fa958c71a629577bf815710e'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -/// See also [library]. -@ProviderFor(library) -const libraryProvider = LibraryFamily(); - -/// See also [library]. -class LibraryFamily extends Family> { - /// See also [library]. - const LibraryFamily(); - - /// See also [library]. - LibraryProvider call( - String id, - ) { - return LibraryProvider( - id, - ); - } - - @override - LibraryProvider getProviderOverride( - covariant LibraryProvider provider, - ) { - return call( - provider.id, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'libraryProvider'; -} - -/// See also [library]. -class LibraryProvider extends AutoDisposeFutureProvider { - /// See also [library]. - LibraryProvider( - String id, - ) : this._internal( - (ref) => library( - ref as LibraryRef, - id, - ), - from: libraryProvider, - name: r'libraryProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$libraryHash, - dependencies: LibraryFamily._dependencies, - allTransitiveDependencies: LibraryFamily._allTransitiveDependencies, - id: id, - ); - - LibraryProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.id, - }) : super.internal(); - - final String id; - - @override - Override overrideWith( - FutureOr Function(LibraryRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: LibraryProvider._internal( - (ref) => create(ref as LibraryRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - id: id, - ), - ); - } - - @override - AutoDisposeFutureProviderElement createElement() { - return _LibraryProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is LibraryProvider && other.id == id; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin LibraryRef on AutoDisposeFutureProviderRef { - /// The parameter `id` of this provider. - String get id; -} - -class _LibraryProviderElement extends AutoDisposeFutureProviderElement - with LibraryRef { - _LibraryProviderElement(super.provider); - - @override - String get id => (origin as LibraryProvider).id; -} - -String _$currentLibraryHash() => r'658498a531e04a01e2b3915a3319101285601118'; - -/// See also [currentLibrary]. -@ProviderFor(currentLibrary) -final currentLibraryProvider = AutoDisposeFutureProvider.internal( - currentLibrary, - name: r'currentLibraryProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$currentLibraryHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef CurrentLibraryRef = AutoDisposeFutureProviderRef; -String _$librariesHash() => r'95ebd4d1ac0cc2acf7617dc22895eff0ca30600f'; - -/// See also [Libraries]. -@ProviderFor(Libraries) -final librariesProvider = - AutoDisposeAsyncNotifierProvider>.internal( - Libraries.new, - name: r'librariesProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$librariesHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$Libraries = AutoDisposeAsyncNotifier>; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/api/server_provider.dart b/lib/api/server_provider.dart index ef1c864..f4d8a21 100644 --- a/lib/api/server_provider.dart +++ b/lib/api/server_provider.dart @@ -1,6 +1,7 @@ +import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:vaani/api/authenticated_users_provider.dart'; +import 'package:vaani/api/authenticated_user_provider.dart'; import 'package:vaani/db/storage.dart'; import 'package:vaani/settings/api_settings_provider.dart'; import 'package:vaani/settings/models/audiobookshelf_server.dart' as model; @@ -49,7 +50,7 @@ class AudiobookShelfServer extends _$AudiobookShelfServer { if (_box.isNotEmpty) { final foundServers = _box.getRange(0, _box.length); _logger.info('found servers in box: ${foundServers.obfuscate()}'); - return foundServers.nonNulls.toSet(); + return foundServers.whereNotNull().toSet(); } else { _logger.info('no settings found in box'); return {}; @@ -88,7 +89,7 @@ class AudiobookShelfServer extends _$AudiobookShelfServer { } // remove the users of this server if (removeUsers) { - ref.read(authenticatedUsersProvider.notifier).removeUsersOfServer(server); + ref.read(authenticatedUserProvider.notifier).removeUsersOfServer(server); } } diff --git a/lib/api/server_provider.g.dart b/lib/api/server_provider.g.dart index ff2406a..7ff40c2 100644 --- a/lib/api/server_provider.g.dart +++ b/lib/api/server_provider.g.dart @@ -7,7 +7,7 @@ part of 'server_provider.dart'; // ************************************************************************** String _$audiobookShelfServerHash() => - r'31a96b431221965cd586aad670a32ca901539e41'; + r'0084fb72c4c54323207928b95716cfd9ca496c11'; /// provides with a set of servers added by the user /// @@ -27,4 +27,4 @@ final audiobookShelfServerProvider = AutoDisposeNotifierProvider< typedef _$AudiobookShelfServer = AutoDisposeNotifier>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/constants/hero_tag_conventions.dart b/lib/constants/hero_tag_conventions.dart index 4ec40a9..6934c00 100644 --- a/lib/constants/hero_tag_conventions.dart +++ b/lib/constants/hero_tag_conventions.dart @@ -10,4 +10,5 @@ class HeroTagPrefixes { static const String bookTitle = 'book_title_'; static const String narratorName = 'narrator_name_'; static const String libraryItemPlayButton = 'library_item_play_button_'; + } diff --git a/lib/features/downloads/providers/download_manager.dart b/lib/features/downloads/providers/download_manager.dart index 6ffdded..9f56129 100644 --- a/lib/features/downloads/providers/download_manager.dart +++ b/lib/features/downloads/providers/download_manager.dart @@ -1,5 +1,4 @@ import 'package:background_downloader/background_downloader.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; @@ -123,7 +122,7 @@ class ItemDownloadProgress extends _$ItemDownloadProgress { @riverpod FutureOr> downloadHistory( - Ref ref, { + DownloadHistoryRef ref, { String? group, }) async { return await FileDownloader().database.allRecords(group: group); diff --git a/lib/features/downloads/providers/download_manager.g.dart b/lib/features/downloads/providers/download_manager.g.dart index 6b08886..47cbf4d 100644 --- a/lib/features/downloads/providers/download_manager.g.dart +++ b/lib/features/downloads/providers/download_manager.g.dart @@ -6,7 +6,7 @@ part of 'download_manager.dart'; // RiverpodGenerator // ************************************************************************** -String _$downloadHistoryHash() => r'4d8b84e30f7ff5ae69d23c8e03ff24af1234a1ad'; +String _$downloadHistoryHash() => r'76c449e8abfa61d57566991686f534a06dc7fef7'; /// Copied from Dart SDK class _SystemHash { @@ -143,8 +143,6 @@ class DownloadHistoryProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin DownloadHistoryRef on AutoDisposeFutureProviderRef> { /// The parameter `group` of this provider. String? get group; @@ -320,8 +318,6 @@ class IsItemDownloadingProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin IsItemDownloadingRef on AutoDisposeNotifierProviderRef { /// The parameter `id` of this provider. String get id; @@ -467,8 +463,6 @@ class ItemDownloadProgressProvider extends AutoDisposeAsyncNotifierProviderImpl< } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin ItemDownloadProgressRef on AutoDisposeAsyncNotifierProviderRef { /// The parameter `id` of this provider. String get id; @@ -613,8 +607,6 @@ class IsItemDownloadedProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin IsItemDownloadedRef on AutoDisposeAsyncNotifierProviderRef { /// The parameter `item` of this provider. LibraryItemExpanded get item; @@ -629,4 +621,4 @@ class _IsItemDownloadedProviderElement LibraryItemExpanded get item => (origin as IsItemDownloadedProvider).item; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/downloads/view/downloads_page.dart b/lib/features/downloads/view/downloads_page.dart index 7c5dbfb..e8c0e9c 100644 --- a/lib/features/downloads/view/downloads_page.dart +++ b/lib/features/downloads/view/downloads_page.dart @@ -13,6 +13,7 @@ class DownloadsPage extends HookConsumerWidget { return Scaffold( appBar: AppBar( title: const Text('Downloads'), + backgroundColor: Colors.transparent, ), body: Center( // history of downloads diff --git a/lib/features/explore/providers/search_controller.g.dart b/lib/features/explore/providers/search_controller.g.dart index fab5671..4e6fa28 100644 --- a/lib/features/explore/providers/search_controller.g.dart +++ b/lib/features/explore/providers/search_controller.g.dart @@ -26,4 +26,4 @@ final globalSearchControllerProvider = typedef _$GlobalSearchController = Notifier>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/explore/providers/search_result_provider.dart b/lib/features/explore/providers/search_result_provider.dart index 2c903f7..1552a69 100644 --- a/lib/features/explore/providers/search_result_provider.dart +++ b/lib/features/explore/providers/search_result_provider.dart @@ -1,4 +1,3 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:vaani/api/api_provider.dart'; @@ -9,7 +8,7 @@ part 'search_result_provider.g.dart'; /// The provider for the search result. @riverpod FutureOr searchResult( - Ref ref, + SearchResultRef ref, String query, { int limit = 25, }) async { diff --git a/lib/features/explore/providers/search_result_provider.g.dart b/lib/features/explore/providers/search_result_provider.g.dart index 764b781..20d8c40 100644 --- a/lib/features/explore/providers/search_result_provider.g.dart +++ b/lib/features/explore/providers/search_result_provider.g.dart @@ -6,7 +6,7 @@ part of 'search_result_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$searchResultHash() => r'33785de298ad0d53c9d21e8fec88ba2f22f1363f'; +String _$searchResultHash() => r'9baa643cce24f3a5e022f42202e423373939ef95'; /// Copied from Dart SDK class _SystemHash { @@ -167,8 +167,6 @@ class SearchResultProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin SearchResultRef on AutoDisposeFutureProviderRef { /// The parameter `query` of this provider. String get query; @@ -188,4 +186,4 @@ class _SearchResultProviderElement int get limit => (origin as SearchResultProvider).limit; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/explore/view/explore_page.dart b/lib/features/explore/view/explore_page.dart index 1682254..2a46983 100644 --- a/lib/features/explore/view/explore_page.dart +++ b/lib/features/explore/view/explore_page.dart @@ -30,6 +30,7 @@ class ExplorePage extends HookConsumerWidget { return Scaffold( appBar: AppBar( title: const Text('Explore'), + backgroundColor: Colors.transparent, ), body: const MySearchBar(), ); @@ -97,10 +98,8 @@ class MySearchBar extends HookConsumerWidget { // opacity: 0.5 for the hint text hintStyle: WidgetStatePropertyAll( Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.5), + color: + Theme.of(context).colorScheme.onSurface.withOpacity(0.5), ), ), textInputAction: TextInputAction.search, diff --git a/lib/features/item_viewer/view/library_item_actions.dart b/lib/features/item_viewer/view/library_item_actions.dart index fe5525f..1b1b5a8 100644 --- a/lib/features/item_viewer/view/library_item_actions.dart +++ b/lib/features/item_viewer/view/library_item_actions.dart @@ -425,6 +425,7 @@ class DownloadSheet extends HookConsumerWidget { class _LibraryItemPlayButton extends HookConsumerWidget { const _LibraryItemPlayButton({ + super.key, required this.item, }); diff --git a/lib/features/item_viewer/view/library_item_hero_section.dart b/lib/features/item_viewer/view/library_item_hero_section.dart index da4adea..83b5555 100644 --- a/lib/features/item_viewer/view/library_item_hero_section.dart +++ b/lib/features/item_viewer/view/library_item_hero_section.dart @@ -15,7 +15,7 @@ import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/duration_format.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/widgets/shelves/book_shelf.dart'; -import 'package:vaani/theme/providers/theme_from_cover_provider.dart'; +import 'package:vaani/theme/theme_from_cover_provider.dart'; class LibraryItemHeroSection extends HookConsumerWidget { const LibraryItemHeroSection({ @@ -78,6 +78,7 @@ class LibraryItemHeroSection extends HookConsumerWidget { class _BookDetails extends HookConsumerWidget { const _BookDetails({ + super.key, required this.id, this.extraMap, }); @@ -135,6 +136,7 @@ class _BookDetails extends HookConsumerWidget { class _LibraryItemProgressIndicator extends HookConsumerWidget { const _LibraryItemProgressIndicator({ + super.key, required this.id, }); @@ -199,10 +201,8 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget { '${remainingTime.smartBinaryFormat} left', style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.75), + color: + Theme.of(context).colorScheme.onSurface.withOpacity(0.75), ), ), ], @@ -213,6 +213,7 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget { class _HeroSectionSubLabelWithIcon extends HookConsumerWidget { const _HeroSectionSubLabelWithIcon({ + super.key, required this.icon, required this.text, }); @@ -229,7 +230,7 @@ class _HeroSectionSubLabelWithIcon extends HookConsumerWidget { ref.watch(appSettingsProvider).themeSettings.useMaterialThemeOnItemPage; final color = useMaterialThemeOnItemPage ? themeData.colorScheme.primary - : themeData.colorScheme.onSurface.withValues(alpha: 0.75); + : themeData.colorScheme.onSurface.withOpacity(0.75); return Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Row( @@ -259,6 +260,7 @@ class _HeroSectionSubLabelWithIcon extends HookConsumerWidget { class _BookSeries extends StatelessWidget { const _BookSeries({ + super.key, required this.itemBookMetadata, required this.bookDetailsCached, }); @@ -304,6 +306,7 @@ class _BookSeries extends StatelessWidget { class _BookNarrators extends StatelessWidget { const _BookNarrators({ + super.key, required this.itemBookMetadata, required this.bookDetailsCached, }); @@ -339,6 +342,7 @@ class _BookNarrators extends StatelessWidget { class _BookCover extends HookConsumerWidget { const _BookCover({ + super.key, required this.itemId, }); @@ -349,17 +353,16 @@ class _BookCover extends HookConsumerWidget { final coverImage = ref.watch(coverImageProvider(itemId)); final themeData = Theme.of(context); // final item = ref.watch(libraryItemProvider(itemId)); - final themeSettings = ref.watch(appSettingsProvider).themeSettings; + final useMaterialThemeOnItemPage = + ref.watch(appSettingsProvider).themeSettings.useMaterialThemeOnItemPage; ColorScheme? coverColorScheme; - if (themeSettings.useMaterialThemeOnItemPage) { + if (useMaterialThemeOnItemPage) { coverColorScheme = ref .watch( themeOfLibraryItemProvider( itemId, brightness: Theme.of(context).brightness, - highContrast: themeSettings.highContrast || - MediaQuery.of(context).highContrast, ), ) .valueOrNull; @@ -368,7 +371,7 @@ class _BookCover extends HookConsumerWidget { return ThemeSwitcher( builder: (context) { // change theme after 2 seconds - if (themeSettings.useMaterialThemeOnItemPage) { + if (useMaterialThemeOnItemPage) { Future.delayed(150.ms, () { try { ThemeSwitcher.of(context).changeTheme( @@ -412,6 +415,7 @@ class _BookCover extends HookConsumerWidget { class _BookTitle extends StatelessWidget { const _BookTitle({ + super.key, required this.extraMap, required this.itemBookMetadata, }); @@ -445,7 +449,7 @@ class _BookTitle extends StatelessWidget { ? const SizedBox.shrink() : Text( style: themeData.textTheme.titleSmall?.copyWith( - color: themeData.colorScheme.onSurface.withValues(alpha: 0.8), + color: themeData.colorScheme.onSurface.withOpacity(0.8), ), itemBookMetadata?.subtitle ?? '', ), @@ -456,6 +460,7 @@ class _BookTitle extends StatelessWidget { class _BookAuthors extends StatelessWidget { const _BookAuthors({ + super.key, required this.itemBookMetadata, required this.bookDetailsCached, }); diff --git a/lib/features/item_viewer/view/library_item_metadata.dart b/lib/features/item_viewer/view/library_item_metadata.dart index 664daac..5874a92 100644 --- a/lib/features/item_viewer/view/library_item_metadata.dart +++ b/lib/features/item_viewer/view/library_item_metadata.dart @@ -96,10 +96,7 @@ class LibraryItemMetadata extends HookConsumerWidget { return VerticalDivider( indent: 6, endIndent: 6, - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.6), + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ); }, ), @@ -112,6 +109,7 @@ class LibraryItemMetadata extends HookConsumerWidget { /// key-value pair to display as column class _MetadataItem extends StatelessWidget { const _MetadataItem({ + super.key, required this.title, required this.value, }); @@ -128,7 +126,7 @@ class _MetadataItem extends StatelessWidget { children: [ Text( style: themeData.textTheme.titleMedium?.copyWith( - color: themeData.colorScheme.onSurface.withValues(alpha: 0.90), + color: themeData.colorScheme.onSurface.withOpacity(0.90), ), value, maxLines: 1, @@ -136,7 +134,7 @@ class _MetadataItem extends StatelessWidget { ), Text( style: themeData.textTheme.bodySmall?.copyWith( - color: themeData.colorScheme.onSurface.withValues(alpha: 0.7), + color: themeData.colorScheme.onSurface.withOpacity(0.7), ), title, maxLines: 1, diff --git a/lib/features/item_viewer/view/library_item_page.dart b/lib/features/item_viewer/view/library_item_page.dart index e7b9310..9e98003 100644 --- a/lib/features/item_viewer/view/library_item_page.dart +++ b/lib/features/item_viewer/view/library_item_page.dart @@ -3,11 +3,10 @@ import 'dart:math'; import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/api/library_item_provider.dart'; import 'package:vaani/features/item_viewer/view/library_item_sliver_app_bar.dart'; -import 'package:vaani/features/player/view/mini_player_bottom_padding.dart'; +import 'package:vaani/features/player/providers/player_form.dart'; import 'package:vaani/router/models/library_item_extras.dart'; import 'package:vaani/shared/widgets/expandable_description.dart'; @@ -24,89 +23,19 @@ class LibraryItemPage extends HookConsumerWidget { final String itemId; final Object? extra; - static const double _showFabThreshold = 300.0; @override Widget build(BuildContext context, WidgetRef ref) { final additionalItemData = extra is LibraryItemExtras ? extra as LibraryItemExtras : null; - final scrollController = useScrollController(); - final showFab = useState(false); - - // Effect to listen to scroll changes and update FAB visibility - useEffect( - () { - void listener() { - if (!scrollController.hasClients) { - return; // Ensure controller is attached - } - final shouldShow = scrollController.offset > _showFabThreshold; - // Update state only if it changes and widget is still mounted - if (showFab.value != shouldShow && context.mounted) { - showFab.value = shouldShow; - } - } - - scrollController.addListener(listener); - // Initial check in case the view starts scrolled (less likely but safe) - WidgetsBinding.instance.addPostFrameCallback((_) { - if (scrollController.hasClients && context.mounted) { - listener(); - } - }); - - // Cleanup: remove the listener when the widget is disposed - return () => scrollController.removeListener(listener); - }, - [scrollController], - ); // Re-run effect if scrollController changes - - // --- FAB Scroll-to-Top Logic --- - void scrollToTop() { - if (scrollController.hasClients) { - scrollController.animateTo( - 0.0, // Target offset (top) - duration: 300.ms, - curve: Curves.easeInOut, - ); - } - } return ThemeProvider( initTheme: Theme.of(context), duration: 200.ms, child: ThemeSwitchingArea( child: Scaffold( - floatingActionButton: AnimatedSwitcher( - duration: 250.ms, - // A common transition for FABs (fade + scale) - transitionBuilder: (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, - child: FadeTransition( - opacity: animation, - child: child, - ), - ); - }, - child: showFab.value - ? FloatingActionButton( - // Key is important for AnimatedSwitcher to differentiate - key: const ValueKey('fab-scroll-top'), - onPressed: scrollToTop, - tooltip: 'Scroll to top', - child: const Icon(Icons.arrow_upward), - ) - : const SizedBox.shrink( - key: ValueKey('fab-empty'), - ), - ), body: CustomScrollView( - controller: scrollController, slivers: [ - LibraryItemSliverAppBar( - id: itemId, - scrollController: scrollController, - ), + const LibraryItemSliverAppBar(), SliverPadding( padding: const EdgeInsets.all(8), sliver: LibraryItemHeroSection( @@ -127,7 +56,9 @@ class LibraryItemPage extends HookConsumerWidget { child: LibraryItemDescription(id: itemId), ), // a padding at the bottom to make sure the last item is not hidden by mini player - const SliverToBoxAdapter(child: MiniPlayerBottomPadding()), + const SliverToBoxAdapter( + child: SizedBox(height: playerMinHeight), + ), ], ), ), diff --git a/lib/features/item_viewer/view/library_item_sliver_app_bar.dart b/lib/features/item_viewer/view/library_item_sliver_app_bar.dart index fd6b621..99068e2 100644 --- a/lib/features/item_viewer/view/library_item_sliver_app_bar.dart +++ b/lib/features/item_viewer/view/library_item_sliver_app_bar.dart @@ -1,80 +1,23 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/api/library_item_provider.dart' show libraryItemProvider; -class LibraryItemSliverAppBar extends HookConsumerWidget { +class LibraryItemSliverAppBar extends StatelessWidget { const LibraryItemSliverAppBar({ super.key, - required this.id, - required this.scrollController, }); - final String id; - final ScrollController scrollController; - - static const double _showTitleThreshold = kToolbarHeight * 0.5; - @override - Widget build(BuildContext context, WidgetRef ref) { - final item = ref.watch(libraryItemProvider(id)).valueOrNull; - - final showTitle = useState(false); - - useEffect( - () { - void listener() { - final shouldShow = scrollController.hasClients && - scrollController.offset > _showTitleThreshold; - if (showTitle.value != shouldShow) { - showTitle.value = shouldShow; - } - } - - scrollController.addListener(listener); - // Trigger listener once initially in case the view starts scrolled - // (though unlikely for this specific use case, it's good practice) - WidgetsBinding.instance.addPostFrameCallback((_) { - if (scrollController.hasClients) { - listener(); - } - }); - return () => scrollController.removeListener(listener); - }, - [scrollController], - ); - + Widget build(BuildContext context) { return SliverAppBar( + backgroundColor: Colors.transparent, elevation: 0, - floating: false, - pinned: true, + floating: true, primary: true, + snap: true, actions: [ - // IconButton( - // icon: const Icon(Icons.cast), - // onPressed: () { - // // Handle search action - // }, - // ), + // cast button + IconButton(onPressed: () {}, icon: const Icon(Icons.cast)), + IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)), ], - title: AnimatedSwitcher( - duration: const Duration(milliseconds: 150), - child: showTitle.value - ? Text( - // Use a Key to help AnimatedSwitcher differentiate widgets - key: const ValueKey('title-text'), - item?.media.metadata.title ?? '', - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium, - ) - : const SizedBox( - // Also give it a key for differentiation - key: ValueKey('empty-title'), - width: 0, // Ensure it takes no space if possible - height: 0, - ), - ), - centerTitle: false, ); } } diff --git a/lib/features/library_browser/view/library_browser_page.dart b/lib/features/library_browser/view/library_browser_page.dart index 4327b17..d12de57 100644 --- a/lib/features/library_browser/view/library_browser_page.dart +++ b/lib/features/library_browser/view/library_browser_page.dart @@ -1,83 +1,47 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/api/library_provider.dart' show currentLibraryProvider; -import 'package:vaani/features/you/view/widgets/library_switch_chip.dart' - show showLibrarySwitcher; -import 'package:vaani/router/router.dart' show Routes; -import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons; -import 'package:vaani/shared/widgets/not_implemented.dart' - show showNotImplementedToast; +import 'package:vaani/router/router.dart'; class LibraryBrowserPage extends HookConsumerWidget { const LibraryBrowserPage({super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { - final currentLibrary = ref.watch(currentLibraryProvider).valueOrNull; - - // Determine the icon to use, with a fallback - final IconData libraryIconData = - AbsIcons.getIconByName(currentLibrary?.icon) ?? Icons.library_books; - - // Determine the title text - final String appBarTitle = '${currentLibrary?.name ?? 'Your'} Library'; - return Scaffold( - // Use CustomScrollView to enable slivers - body: CustomScrollView( - slivers: [ - SliverAppBar( - pinned: true, - // floating: true, // Optional: uncomment if you want floating behavior - // snap: - // true, // Optional: uncomment if you want snapping behavior (usually with floating: true) - leading: IconButton( - icon: Icon(libraryIconData), - tooltip: 'Switch Library', // Helpful tooltip for users - onPressed: () { - showLibrarySwitcher(context, ref); - }, - ), - title: Text(appBarTitle), + appBar: AppBar( + title: const Text('Library'), + backgroundColor: Colors.transparent, + ), + // a list redirecting to authors, genres, and series pages + body: ListView( + children: [ + ListTile( + title: const Text('Authors'), + leading: const Icon(Icons.person), + trailing: const Icon(Icons.chevron_right), + onTap: () {}, ), - SliverList( - delegate: SliverChildListDelegate( - [ - ListTile( - title: const Text('Authors'), - leading: const Icon(Icons.person), - trailing: const Icon(Icons.chevron_right), - onTap: () { - showNotImplementedToast(context); - }, - ), - ListTile( - title: const Text('Genres'), - leading: const Icon(Icons.category), - trailing: const Icon(Icons.chevron_right), - onTap: () { - showNotImplementedToast(context); - }, - ), - ListTile( - title: const Text('Series'), - leading: const Icon(Icons.list), - trailing: const Icon(Icons.chevron_right), - onTap: () { - showNotImplementedToast(context); - }, - ), - // Downloads - ListTile( - title: const Text('Downloads'), - leading: const Icon(Icons.download), - trailing: const Icon(Icons.chevron_right), - onTap: () { - GoRouter.of(context).pushNamed(Routes.downloads.name); - }, - ), - ], - ), + ListTile( + title: const Text('Genres'), + leading: const Icon(Icons.category), + trailing: const Icon(Icons.chevron_right), + onTap: () {}, + ), + ListTile( + title: const Text('Series'), + leading: const Icon(Icons.list), + trailing: const Icon(Icons.chevron_right), + onTap: () {}, + ), + // Downloads + ListTile( + title: const Text('Downloads'), + leading: const Icon(Icons.download), + trailing: const Icon(Icons.chevron_right), + onTap: () { + GoRouter.of(context).pushNamed(Routes.downloads.name); + }, ), ], ), diff --git a/lib/features/logging/providers/logs_provider.dart b/lib/features/logging/providers/logs_provider.dart index 9fd9e4a..ecdc0b6 100644 --- a/lib/features/logging/providers/logs_provider.dart +++ b/lib/features/logging/providers/logs_provider.dart @@ -1,11 +1,11 @@ import 'dart:io'; import 'package:archive/archive_io.dart'; -import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:vaani/features/logging/core/logger.dart'; + part 'logs_provider.g.dart'; @riverpod @@ -29,23 +29,11 @@ class Logs extends _$Logs { } Future getZipFilePath() async { - final String targetZipPath = await generateZipFilePath(); var encoder = ZipFileEncoder(); - encoder.create(targetZipPath); - final logFilePath = await getLoggingFilePath(); - final logFile = File(logFilePath); - if (await logFile.exists()) { - // Check if log file exists before adding - await encoder.addFile(logFile); - } else { - // Handle case where log file doesn't exist? Maybe log a warning? - // Or create an empty file inside the zip? For now, just don't add. - debugPrint( - 'Warning: Log file not found at $logFilePath, creating potentially empty zip.', - ); - } - await encoder.close(); - return targetZipPath; + encoder.create(await generateZipFilePath()); + encoder.addFile(File(await getLoggingFilePath())); + encoder.close(); + return encoder.zipPath; } } @@ -55,7 +43,7 @@ Future generateZipFilePath() async { } String generateZipFileName() { - return 'vaani-${DateTime.now().microsecondsSinceEpoch}.zip'; + return 'vaani-${DateTime.now().toIso8601String()}.zip'; } Level parseLevel(String level) { diff --git a/lib/features/logging/providers/logs_provider.g.dart b/lib/features/logging/providers/logs_provider.g.dart index 53babc0..094893e 100644 --- a/lib/features/logging/providers/logs_provider.g.dart +++ b/lib/features/logging/providers/logs_provider.g.dart @@ -6,7 +6,7 @@ part of 'logs_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$logsHash() => r'aa9d3d56586cba6ddf69615320ea605d071ea5e2'; +String _$logsHash() => r'901376741d17ddbb889d1b7b96bc2882289720a0'; /// See also [Logs]. @ProviderFor(Logs) @@ -22,4 +22,4 @@ final logsProvider = typedef _$Logs = AutoDisposeAsyncNotifier>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/logging/view/logs_page.dart b/lib/features/logging/view/logs_page.dart index 74d1ad3..f5ee104 100644 --- a/lib/features/logging/view/logs_page.dart +++ b/lib/features/logging/view/logs_page.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -73,77 +76,32 @@ class LogsPage extends HookConsumerWidget { } }, ), + IconButton( + tooltip: 'Download logs', + icon: const Icon(Icons.download), + onPressed: () async { + appLogger.info('Preparing logs for download'); + final zipLogFilePath = + await ref.read(logsProvider.notifier).getZipFilePath(); - // downloads disabled since manage external storage permission was removed - // see https://gitlab.com/IzzyOnDroid/repo/-/issues/623#note_2240386369 - // IconButton( - // tooltip: 'Download logs', - // icon: const Icon(Icons.download), - // onPressed: () async { - // appLogger.info('Preparing logs for download'); - - // if (Platform.isAndroid) { - // final androidVersion = - // await ref.watch(deviceSdkVersionProvider.future); - - // if ((int.parse(androidVersion)) > 29) { - // final status = await Permission.storage.status; - // if (!status.isGranted) { - // appLogger - // .info('Requesting storage permission'); - // final newStatus = - // await Permission.storage.request(); - // if (!newStatus.isGranted) { - // appLogger - // .warning('storage permission denied'); - // ScaffoldMessenger.of(context).showSnackBar( - // const SnackBar( - // content: Text('Storage permission denied'), - // ), - // ); - // return; - // } - // } - // } else { - // final status = await Permission.storage.status; - // if (!status.isGranted) { - // appLogger.info('Requesting storage permission'); - // final newStatus = await Permission.storage.request(); - // if (!newStatus.isGranted) { - // appLogger.warning('Storage permission denied'); - // ScaffoldMessenger.of(context).showSnackBar( - // const SnackBar( - // content: Text('Storage permission denied'), - // ), - // ); - // return; - // } - // } - // } - // } - // final zipLogFilePath = - // await ref.read(logsProvider.notifier).getZipFilePath(); - - // // save to folder - // String? outputFile = await FilePicker.platform.saveFile( - // dialogTitle: 'Please select an output file:', - // fileName: zipLogFilePath.split('/').last, - // bytes: await File(zipLogFilePath).readAsBytes(), - // ); - // if (outputFile != null) { - // try { - // final file = File(outputFile); - // final zipFile = File(zipLogFilePath); - // await zipFile.copy(file.path); - // appLogger.info('File saved to: $outputFile'); - // } catch (e) { - // appLogger.severe('Error saving file: $e'); - // } - // } else { - // appLogger.info('Download cancelled'); - // } - // }, - // ), + // save to folder + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Please select an output file:', + fileName: zipLogFilePath.split('/').last, + ); + if (outputFile != null) { + try { + final file = File(outputFile); + final zipFile = File(zipLogFilePath); + await zipFile.copy(file.path); + } catch (e) { + appLogger.severe('Error saving file: $e'); + } + } else { + appLogger.info('Download cancelled'); + } + }, + ), IconButton( tooltip: 'Refresh logs', icon: const Icon(Icons.refresh), @@ -164,7 +122,7 @@ class LogsPage extends HookConsumerWidget { ), ], ), - // a column with ListView.builder and a scrollable list of logs + // a column with listview.builder and a scrollable list of logs body: Column( children: [ // a filter for log levels, loggers, and search diff --git a/lib/features/onboarding/providers/oauth_provider.dart b/lib/features/onboarding/providers/oauth_provider.dart index fb7856b..79445d9 100644 --- a/lib/features/onboarding/providers/oauth_provider.dart +++ b/lib/features/onboarding/providers/oauth_provider.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:vaani/api/api_provider.dart'; import 'package:vaani/models/error_response.dart'; @@ -62,7 +61,7 @@ class OauthFlows extends _$OauthFlows { /// the code returned by the server in exchange for the verifier @riverpod Future loginInExchangeForCode( - Ref ref, { + LoginInExchangeForCodeRef ref, { required State oauthState, required Code code, ErrorResponseHandler? responseHandler, diff --git a/lib/features/onboarding/providers/oauth_provider.g.dart b/lib/features/onboarding/providers/oauth_provider.g.dart index 8f44538..9b7c4f5 100644 --- a/lib/features/onboarding/providers/oauth_provider.g.dart +++ b/lib/features/onboarding/providers/oauth_provider.g.dart @@ -7,7 +7,7 @@ part of 'oauth_provider.dart'; // ************************************************************************** String _$loginInExchangeForCodeHash() => - r'bfc3945529048a0f536052fd5579b76457560fcd'; + r'e931254959d9eb8196439c6b0c884c26cbe17c2f'; /// Copied from Dart SDK class _SystemHash { @@ -179,8 +179,6 @@ class LoginInExchangeForCodeProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin LoginInExchangeForCodeRef on AutoDisposeFutureProviderRef { /// The parameter `oauthState` of this provider. String get oauthState; @@ -223,4 +221,4 @@ final oauthFlowsProvider = typedef _$OauthFlows = Notifier>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/onboarding/view/callback_page.dart b/lib/features/onboarding/view/callback_page.dart index f4bb098..dd3de86 100644 --- a/lib/features/onboarding/view/callback_page.dart +++ b/lib/features/onboarding/view/callback_page.dart @@ -98,6 +98,7 @@ class BackToLoginButton extends StatelessWidget { class _SomethingWentWrong extends StatelessWidget { const _SomethingWentWrong({ + super.key, this.message = 'Error with OAuth flow', }); diff --git a/lib/features/onboarding/view/onboarding_single_page.dart b/lib/features/onboarding/view/onboarding_single_page.dart index b9a7eb5..5ceff08 100644 --- a/lib/features/onboarding/view/onboarding_single_page.dart +++ b/lib/features/onboarding/view/onboarding_single_page.dart @@ -13,122 +13,89 @@ class OnboardingSinglePage extends HookConsumerWidget { super.key, }); - @override - Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - body: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Center( - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 600, - minWidth: - constraints.maxWidth < 600 ? constraints.maxWidth : 0, - ), - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 20.0), - child: SafeArea(child: OnboardingBody()), - ), - ), - ), - ); - }, - ), - ); - } -} - -Widget fadeSlideTransitionBuilder( - Widget child, - Animation animation, -) { - return FadeTransition( - opacity: animation, - child: SlideTransition( - position: Tween( - begin: const Offset(0, 0.3), - end: const Offset(0, 0), - ).animate(animation), - child: child, - ), - ); -} - -class OnboardingBody extends HookConsumerWidget { - const OnboardingBody({ - super.key, - }); - @override Widget build(BuildContext context, WidgetRef ref) { final apiSettings = ref.watch(apiSettingsProvider); final serverUriController = useTextEditingController( - text: apiSettings.activeServer?.serverUrl.toString() ?? 'https://', + text: apiSettings.activeServer?.serverUrl.toString() ?? '', ); var audiobookshelfUri = makeBaseUrl(serverUriController.text); final canUserLogin = useState(apiSettings.activeServer != null); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Welcome to Vaani', - style: Theme.of(context).textTheme.headlineSmall, + fadeSlideTransitionBuilder( + Widget child, + Animation animation, + ) { + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 0.3), + end: const Offset(0, 0), + ).animate(animation), + child: child, + ), + ); + } + + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Welcome to Vaani', + style: Theme.of(context).textTheme.headlineSmall, + ), ), - ), - const SizedBox.square( - dimension: 16.0, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: AnimatedSwitcher( + const SizedBox.square( + dimension: 16.0, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: AnimatedSwitcher( + duration: 500.ms, + transitionBuilder: fadeSlideTransitionBuilder, + child: canUserLogin.value + ? Text( + 'Server connected, please login', + key: const ValueKey('connected'), + style: Theme.of(context).textTheme.bodyMedium, + ) + : Text( + 'Please enter the URL of your AudiobookShelf Server', + key: const ValueKey('not_connected'), + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: AddNewServer( + controller: serverUriController, + allowEmpty: true, + onPressed: () { + canUserLogin.value = serverUriController.text.isNotEmpty; + }, + ), + ), + AnimatedSwitcher( duration: 500.ms, transitionBuilder: fadeSlideTransitionBuilder, child: canUserLogin.value - ? Text( - 'Server connected, please login', - key: const ValueKey('connected'), - style: Theme.of(context).textTheme.bodyMedium, + ? UserLoginWidget( + server: audiobookshelfUri, ) - : Text( - 'Please enter the URL of your AudiobookShelf Server', - key: const ValueKey('not_connected'), - style: Theme.of(context).textTheme.bodyMedium, - ), + // ).animate().fade(duration: 600.ms).slideY(begin: 0.3, end: 0) + : const RedirectToABS().animate().fadeIn().slideY( + curve: Curves.easeInOut, + duration: 500.ms, + ), ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: AddNewServer( - controller: serverUriController, - allowEmpty: true, - onPressed: () { - canUserLogin.value = serverUriController.text.isNotEmpty; - }, - ), - ), - const SizedBox.square( - dimension: 16.0, - ), - AnimatedSwitcher( - duration: 500.ms, - transitionBuilder: fadeSlideTransitionBuilder, - child: canUserLogin.value - ? UserLoginWidget( - server: audiobookshelfUri, - ) - // ).animate().fade(duration: 600.ms).slideY(begin: 0.3, end: 0) - : const RedirectToABS().animate().fadeIn().slideY( - curve: Curves.easeInOut, - duration: 500.ms, - ), - ), - ], + ], + ), ); } } diff --git a/lib/features/onboarding/view/user_login.dart b/lib/features/onboarding/view/user_login.dart index 8aeff14..2b3d575 100644 --- a/lib/features/onboarding/view/user_login.dart +++ b/lib/features/onboarding/view/user_login.dart @@ -1,42 +1,33 @@ import 'package:flutter/material.dart'; -import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shelfsdk/audiobookshelf_api.dart' show AuthMethod; -import 'package:vaani/api/api_provider.dart' show serverStatusProvider; -import 'package:vaani/api/server_provider.dart' - show ServerAlreadyExistsException, audiobookShelfServerProvider; -import 'package:vaani/features/onboarding/view/onboarding_single_page.dart' - show fadeSlideTransitionBuilder; -import 'package:vaani/features/onboarding/view/user_login_with_open_id.dart' - show UserLoginWithOpenID; -import 'package:vaani/features/onboarding/view/user_login_with_password.dart' - show UserLoginWithPassword; -import 'package:vaani/features/onboarding/view/user_login_with_token.dart' - show UserLoginWithToken; -import 'package:vaani/hacks/fix_autofill_losing_focus.dart' - show InactiveFocusScopeObserver; -import 'package:vaani/models/error_response.dart' show ErrorResponseHandler; -import 'package:vaani/settings/api_settings_provider.dart' - show apiSettingsProvider; +import 'package:shelfsdk/audiobookshelf_api.dart'; +import 'package:vaani/api/api_provider.dart'; +import 'package:vaani/api/server_provider.dart'; +import 'package:vaani/features/onboarding/view/user_login_with_open_id.dart'; +import 'package:vaani/features/onboarding/view/user_login_with_password.dart'; +import 'package:vaani/features/onboarding/view/user_login_with_token.dart'; +import 'package:vaani/hacks/fix_autofill_losing_focus.dart'; +import 'package:vaani/models/error_response.dart'; +import 'package:vaani/settings/api_settings_provider.dart'; import 'package:vaani/settings/models/models.dart' as model; class UserLoginWidget extends HookConsumerWidget { - const UserLoginWidget({ + UserLoginWidget({ super.key, required this.server, - this.onSuccess, }); final Uri server; - final Function(model.AuthenticatedUser)? onSuccess; + final serverStatusError = ErrorResponseHandler(); @override Widget build(BuildContext context, WidgetRef ref) { - final serverStatusError = useMemoized(() => ErrorResponseHandler(), []); final serverStatus = ref.watch(serverStatusProvider(server, serverStatusError.storeError)); + final api = ref.watch(audiobookshelfApiProvider(server)); + return serverStatus.when( data: (value) { if (value == null) { @@ -51,7 +42,6 @@ class UserLoginWidget extends HookConsumerWidget { openIDAvailable: value.authMethods?.contains(AuthMethod.openid) ?? false, openIDButtonText: value.authFormData?.authOpenIDButtonText, - onSuccess: onSuccess, ); }, loading: () { @@ -98,7 +88,6 @@ class UserLoginMultipleAuth extends HookConsumerWidget { this.openIDAvailable = false, this.onPressed, this.openIDButtonText, - this.onSuccess, }); final Uri server; @@ -106,7 +95,6 @@ class UserLoginMultipleAuth extends HookConsumerWidget { final bool openIDAvailable; final void Function()? onPressed; final String? openIDButtonText; - final Function(model.AuthenticatedUser)? onSuccess; @override Widget build(BuildContext context, WidgetRef ref) { @@ -116,6 +104,8 @@ class UserLoginMultipleAuth extends HookConsumerWidget { localAvailable ? AuthMethodChoice.local : AuthMethodChoice.authToken, ); + final apiSettings = ref.watch(apiSettingsProvider); + model.AudiobookShelfServer addServer() { var newServer = model.AudiobookShelfServer( serverUrl: server, @@ -129,9 +119,9 @@ class UserLoginMultipleAuth extends HookConsumerWidget { newServer = e.server; } finally { ref.read(apiSettingsProvider.notifier).updateState( - ref.read(apiSettingsProvider).copyWith( - activeServer: newServer, - ), + apiSettings.copyWith( + activeServer: newServer, + ), ); } return newServer; @@ -140,11 +130,11 @@ class UserLoginMultipleAuth extends HookConsumerWidget { return Center( child: InactiveFocusScopeObserver( child: AutofillGroup( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Wrap( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Wrap( // mainAxisAlignment: MainAxisAlignment.center, spacing: 10, runAlignment: WrapAlignment.center, @@ -182,38 +172,28 @@ class UserLoginMultipleAuth extends HookConsumerWidget { } }, ), - ].animate(interval: 100.ms).fadeIn( - duration: 150.ms, - curve: Curves.easeIn, - ), + ], ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: AnimatedSwitcher( - duration: 200.ms, - transitionBuilder: fadeSlideTransitionBuilder, - child: switch (methodChoice.value) { - AuthMethodChoice.authToken => UserLoginWithToken( - server: server, - addServer: addServer, - onSuccess: onSuccess, - ), - AuthMethodChoice.local => UserLoginWithPassword( - server: server, - addServer: addServer, - onSuccess: onSuccess, - ), - AuthMethodChoice.openid => UserLoginWithOpenID( - server: server, - addServer: addServer, - openIDButtonText: openIDButtonText, - onSuccess: onSuccess, - ), - }, + const SizedBox.square( + dimension: 8, ), - ), - ], + switch (methodChoice.value) { + AuthMethodChoice.authToken => UserLoginWithToken( + server: server, + addServer: addServer, + ), + AuthMethodChoice.local => UserLoginWithPassword( + server: server, + addServer: addServer, + ), + AuthMethodChoice.openid => UserLoginWithOpenID( + server: server, + addServer: addServer, + openIDButtonText: openIDButtonText, + ), + }, + ], + ), ), ), ), diff --git a/lib/features/onboarding/view/user_login_with_open_id.dart b/lib/features/onboarding/view/user_login_with_open_id.dart index b3a1d9e..71baa42 100644 --- a/lib/features/onboarding/view/user_login_with_open_id.dart +++ b/lib/features/onboarding/view/user_login_with_open_id.dart @@ -20,14 +20,12 @@ class UserLoginWithOpenID extends HookConsumerWidget { required this.server, required this.addServer, this.openIDButtonText, - this.onSuccess, }); final Uri server; final model.AudiobookShelfServer Function() addServer; final String? openIDButtonText; final responseErrorHandler = ErrorResponseHandler(name: 'OpenID'); - final Function(model.AuthenticatedUser)? onSuccess; @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/features/onboarding/view/user_login_with_password.dart b/lib/features/onboarding/view/user_login_with_password.dart index 210da77..91eeea0 100644 --- a/lib/features/onboarding/view/user_login_with_password.dart +++ b/lib/features/onboarding/view/user_login_with_password.dart @@ -5,11 +5,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lottie/lottie.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:vaani/api/api_provider.dart'; -import 'package:vaani/api/authenticated_users_provider.dart'; +import 'package:vaani/api/authenticated_user_provider.dart'; import 'package:vaani/hacks/fix_autofill_losing_focus.dart'; import 'package:vaani/models/error_response.dart'; import 'package:vaani/router/router.dart'; -import 'package:vaani/settings/constants.dart'; import 'package:vaani/settings/models/models.dart' as model; import 'package:vaani/shared/utils.dart'; @@ -18,20 +17,17 @@ class UserLoginWithPassword extends HookConsumerWidget { super.key, required this.server, required this.addServer, - this.onSuccess, }); final Uri server; final model.AudiobookShelfServer Function() addServer; final serverErrorResponse = ErrorResponseHandler(); - final Function(model.AuthenticatedUser)? onSuccess; @override Widget build(BuildContext context, WidgetRef ref) { final usernameController = useTextEditingController(); final passwordController = useTextEditingController(); final isPasswordVisibleAnimationController = useAnimationController( - initialValue: 1, duration: const Duration(milliseconds: 500), ); @@ -80,94 +76,92 @@ class UserLoginWithPassword extends HookConsumerWidget { final authenticatedUser = model.AuthenticatedUser( server: addServer(), id: success.user.id, + password: password, username: username, authToken: api.token!, ); + // add the user to the list of users + ref + .read(authenticatedUserProvider.notifier) + .addUser(authenticatedUser, setActive: true); - if (onSuccess != null) { - onSuccess!(authenticatedUser); - } else { - // add the user to the list of users - ref - .read(authenticatedUsersProvider.notifier) - .addUser(authenticatedUser, setActive: true); - context.goNamed(Routes.home.name); - } + // redirect to the library page + GoRouter.of(context).goNamed(Routes.home.name); } return Center( child: InactiveFocusScopeObserver( child: AutofillGroup( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextFormField( - controller: usernameController, - autofocus: true, - autofillHints: const [AutofillHints.username], - textInputAction: TextInputAction.next, - decoration: InputDecoration( - labelText: 'Username', - labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.8), - ), - border: const OutlineInputBorder(), - ), - ), - const SizedBox(height: 10), - TextFormField( - controller: passwordController, - autofillHints: const [AutofillHints.password], - textInputAction: TextInputAction.done, - obscureText: !isPasswordVisible.value, - onFieldSubmitted: (_) { - loginAndSave(); - }, - decoration: InputDecoration( - labelText: 'Password', - labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.8), - ), - border: const OutlineInputBorder(), - suffixIcon: ColorFiltered( - colorFilter: ColorFilter.mode( - Theme.of(context) + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextFormField( + controller: usernameController, + autofocus: true, + autofillHints: const [AutofillHints.username], + textInputAction: TextInputAction.next, + decoration: InputDecoration( + labelText: 'Username', + labelStyle: TextStyle( + color: Theme.of(context) .colorScheme - .primary - .withValues(alpha: 0.8), - BlendMode.srcIn, + .onSurface + .withOpacity(0.8), ), - child: InkWell( - borderRadius: BorderRadius.circular(50), - onTap: () { - isPasswordVisible.value = !isPasswordVisible.value; - }, - child: Container( - margin: const EdgeInsets.only(left: 8, right: 8), - child: Lottie.asset( - 'assets/animations/Animation - 1714930099660.json', - controller: isPasswordVisibleAnimationController, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 10), + TextFormField( + controller: passwordController, + autofillHints: const [AutofillHints.password], + textInputAction: TextInputAction.done, + obscureText: !isPasswordVisible.value, + onFieldSubmitted: (_) { + loginAndSave(); + }, + decoration: InputDecoration( + labelText: 'Password', + labelStyle: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.8), + ), + border: const OutlineInputBorder(), + suffixIcon: ColorFiltered( + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.primary.withOpacity(0.8), + BlendMode.srcIn, + ), + child: InkWell( + borderRadius: BorderRadius.circular(50), + onTap: () { + isPasswordVisible.value = !isPasswordVisible.value; + }, + child: Container( + margin: const EdgeInsets.only(left: 8, right: 8), + child: Lottie.asset( + 'assets/animations/Animation - 1714930099660.json', + controller: isPasswordVisibleAnimationController, + ), ), ), ), - ), - suffixIconConstraints: const BoxConstraints( - maxHeight: 45, + suffixIconConstraints: const BoxConstraints( + maxHeight: 45, + ), ), ), - ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: loginAndSave, - child: const Text('Login'), - ), - ], + const SizedBox(height: 30), + ElevatedButton( + onPressed: loginAndSave, + child: const Text('Login'), + ), + ], + ), ), ), ), @@ -213,10 +207,8 @@ Future handleServerError( onPressed: () { // open an issue on the github page handleLaunchUrl( - AppMetadata.githubRepo - // append the issue url - .replace( - path: '${AppMetadata.githubRepo.path}/issues/new', + Uri.parse( + 'https://github.com/Dr-Blank/Vaani/issues', ), ); }, diff --git a/lib/features/onboarding/view/user_login_with_token.dart b/lib/features/onboarding/view/user_login_with_token.dart index 7d2fcfb..35cdf55 100644 --- a/lib/features/onboarding/view/user_login_with_token.dart +++ b/lib/features/onboarding/view/user_login_with_token.dart @@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:vaani/api/api_provider.dart'; -import 'package:vaani/api/authenticated_users_provider.dart'; +import 'package:vaani/api/authenticated_user_provider.dart'; import 'package:vaani/models/error_response.dart'; import 'package:vaani/router/router.dart'; import 'package:vaani/settings/models/models.dart' as model; @@ -14,13 +14,11 @@ class UserLoginWithToken extends HookConsumerWidget { super.key, required this.server, required this.addServer, - this.onSuccess, }); final Uri server; final model.AudiobookShelfServer Function() addServer; final serverErrorResponse = ErrorResponseHandler(); - final Function(model.AuthenticatedUser)? onSuccess; @override Widget build(BuildContext context, WidgetRef ref) { @@ -67,14 +65,11 @@ class UserLoginWithToken extends HookConsumerWidget { authToken: api.token!, ); - if (onSuccess != null) { - onSuccess!(authenticatedUser); - } else { - ref - .read(authenticatedUsersProvider.notifier) - .addUser(authenticatedUser, setActive: true); - context.goNamed(Routes.home.name); - } + ref + .read(authenticatedUserProvider.notifier) + .addUser(authenticatedUser, setActive: true); + + context.goNamed(Routes.home.name); } return Form( @@ -89,10 +84,7 @@ class UserLoginWithToken extends HookConsumerWidget { decoration: InputDecoration( labelText: 'API Token', labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.8), + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8), ), border: const OutlineInputBorder(), ), diff --git a/lib/features/per_book_settings/providers/book_settings_provider.g.dart b/lib/features/per_book_settings/providers/book_settings_provider.g.dart index 221433f..943bd55 100644 --- a/lib/features/per_book_settings/providers/book_settings_provider.g.dart +++ b/lib/features/per_book_settings/providers/book_settings_provider.g.dart @@ -157,8 +157,6 @@ class BookSettingsProvider } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin BookSettingsRef on AutoDisposeNotifierProviderRef { /// The parameter `bookId` of this provider. String get bookId; @@ -173,4 +171,4 @@ class _BookSettingsProviderElement String get bookId => (origin as BookSettingsProvider).bookId; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart b/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart index 3c21f16..8b8936d 100644 --- a/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart +++ b/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart @@ -23,4 +23,4 @@ final playbackReporterProvider = typedef _$PlaybackReporter = AsyncNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/player/playlist_provider.g.dart b/lib/features/player/playlist_provider.g.dart index abf7c33..061289c 100644 --- a/lib/features/player/playlist_provider.g.dart +++ b/lib/features/player/playlist_provider.g.dart @@ -22,4 +22,4 @@ final playlistProvider = typedef _$Playlist = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/player/providers/audiobook_player.g.dart b/lib/features/player/providers/audiobook_player.g.dart index d38dd30..a1068eb 100644 --- a/lib/features/player/providers/audiobook_player.g.dart +++ b/lib/features/player/providers/audiobook_player.g.dart @@ -43,4 +43,4 @@ final audiobookPlayerProvider = typedef _$AudiobookPlayer = Notifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/player/providers/currently_playing_provider.dart b/lib/features/player/providers/currently_playing_provider.dart index e8b8af9..3ceff3e 100644 --- a/lib/features/player/providers/currently_playing_provider.dart +++ b/lib/features/player/providers/currently_playing_provider.dart @@ -1,4 +1,3 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; @@ -10,7 +9,7 @@ part 'currently_playing_provider.g.dart'; final _logger = Logger('CurrentlyPlayingProvider'); @riverpod -BookExpanded? currentlyPlayingBook(Ref ref) { +BookExpanded? currentlyPlayingBook(CurrentlyPlayingBookRef ref) { try { final player = ref.watch(audiobookPlayerProvider); return player.book; @@ -22,7 +21,7 @@ BookExpanded? currentlyPlayingBook(Ref ref) { /// provided the current chapter of the book being played @riverpod -BookChapter? currentPlayingChapter(Ref ref) { +BookChapter? currentPlayingChapter(CurrentPlayingChapterRef ref) { final player = ref.watch(audiobookPlayerProvider); player.slowPositionStream.listen((_) { ref.invalidateSelf(); @@ -33,7 +32,7 @@ BookChapter? currentPlayingChapter(Ref ref) { /// provides the book metadata of the currently playing book @riverpod -BookMetadataExpanded? currentBookMetadata(Ref ref) { +BookMetadataExpanded? currentBookMetadata(CurrentBookMetadataRef ref) { final player = ref.watch(audiobookPlayerProvider); if (player.book == null) return null; return player.book!.metadata.asBookMetadataExpanded; diff --git a/lib/features/player/providers/currently_playing_provider.g.dart b/lib/features/player/providers/currently_playing_provider.g.dart index 428aa0b..6dc1c2a 100644 --- a/lib/features/player/providers/currently_playing_provider.g.dart +++ b/lib/features/player/providers/currently_playing_provider.g.dart @@ -7,7 +7,7 @@ part of 'currently_playing_provider.dart'; // ************************************************************************** String _$currentlyPlayingBookHash() => - r'e4258694c8f0d1e89651b330fae0f672ca13a484'; + r'7440b0d54cb364f66e704783652e8f1490ae90e0'; /// See also [currentlyPlayingBook]. @ProviderFor(currentlyPlayingBook) @@ -22,11 +22,9 @@ final currentlyPlayingBookProvider = allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef CurrentlyPlayingBookRef = AutoDisposeProviderRef; String _$currentPlayingChapterHash() => - r'73db8b8a9058573bb0c68ec5d5f8aba9306f3d24'; + r'a084da724e3d8bb1b1475e867ab3200d7d61d827'; /// provided the current chapter of the book being played /// @@ -43,11 +41,9 @@ final currentPlayingChapterProvider = allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef CurrentPlayingChapterRef = AutoDisposeProviderRef; String _$currentBookMetadataHash() => - r'f537ef4ef19280bc952de658ecf6520c535ae344'; + r'9088debba151894b61f2dcba1bba12a89244b9b1'; /// provides the book metadata of the currently playing book /// @@ -64,8 +60,6 @@ final currentBookMetadataProvider = allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef CurrentBookMetadataRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/player/providers/player_form.dart b/lib/features/player/providers/player_form.dart index 10376ee..975f47f 100644 --- a/lib/features/player/providers/player_form.dart +++ b/lib/features/player/providers/player_form.dart @@ -6,7 +6,6 @@ import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:miniplayer/miniplayer.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; part 'player_form.g.dart'; @@ -27,7 +26,7 @@ extension on Ref { @Riverpod(keepAlive: true) Raw> playerExpandProgressNotifier( - Ref ref, + PlayerExpandProgressNotifierRef ref, ) { final ValueNotifier playerExpandProgress = ValueNotifier(playerMinHeight); @@ -47,7 +46,7 @@ Raw> playerExpandProgressNotifier( // a provider that will listen to the playerExpandProgressNotifier and return the percentage of the player expanded @Riverpod(keepAlive: true) double playerHeight( - Ref ref, + PlayerHeightRef ref, ) { final playerExpandProgress = ref.watch(playerExpandProgressNotifierProvider); @@ -61,20 +60,3 @@ double playerHeight( } final audioBookMiniplayerController = MiniplayerController(); - -@Riverpod(keepAlive: true) -bool isPlayerActive( - Ref ref, -) { - try { - final player = ref.watch(audiobookPlayerProvider); - if (player.book != null) { - return true; - } else { - final playerHeight = ref.watch(playerHeightProvider); - return playerHeight < playerMinHeight; - } - } catch (e) { - return false; - } -} diff --git a/lib/features/player/providers/player_form.g.dart b/lib/features/player/providers/player_form.g.dart index 6dcfcf9..b3b73d9 100644 --- a/lib/features/player/providers/player_form.g.dart +++ b/lib/features/player/providers/player_form.g.dart @@ -7,7 +7,7 @@ part of 'player_form.dart'; // ************************************************************************** String _$playerExpandProgressNotifierHash() => - r'1ac7172d90a070f96222286edd1a176be197f378'; + r'e4817361b9a311b61ca23e51082ed11b0a1120ab'; /// See also [playerExpandProgressNotifier]. @ProviderFor(playerExpandProgressNotifier) @@ -22,11 +22,9 @@ final playerExpandProgressNotifierProvider = allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef PlayerExpandProgressNotifierRef = ProviderRef>>; -String _$playerHeightHash() => r'3f031eaffdffbb2c6ddf7eb1aba31bf1619260fc'; +String _$playerHeightHash() => r'26dbcb180d494575488d700bd5bdb58c02c224a9'; /// See also [playerHeight]. @ProviderFor(playerHeight) @@ -39,25 +37,6 @@ final playerHeightProvider = Provider.internal( allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef PlayerHeightRef = ProviderRef; -String _$isPlayerActiveHash() => r'2c7ca125423126fb5f0ef218d37bc8fe0ca9ec98'; - -/// See also [isPlayerActive]. -@ProviderFor(isPlayerActive) -final isPlayerActiveProvider = Provider.internal( - isPlayerActive, - name: r'isPlayerActiveProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$isPlayerActiveHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef IsPlayerActiveRef = ProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/player/view/audiobook_player.dart b/lib/features/player/view/audiobook_player.dart index 1940ba7..46ed7aa 100644 --- a/lib/features/player/view/audiobook_player.dart +++ b/lib/features/player/view/audiobook_player.dart @@ -14,7 +14,7 @@ import 'package:vaani/features/player/providers/player_form.dart'; import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/inverse_lerp.dart'; import 'package:vaani/shared/widgets/shelves/book_shelf.dart'; -import 'package:vaani/theme/providers/theme_from_cover_provider.dart'; +import 'package:vaani/theme/theme_from_cover_provider.dart'; import 'player_when_expanded.dart'; import 'player_when_minimized.dart'; @@ -65,8 +65,6 @@ class AudiobookPlayer extends HookConsumerWidget { themeOfLibraryItemProvider( itemBeingPlayed.valueOrNull?.id, brightness: Theme.of(context).brightness, - highContrast: appSettings.themeSettings.highContrast || - MediaQuery.of(context).highContrast, ), ); diff --git a/lib/features/player/view/mini_player_bottom_padding.dart b/lib/features/player/view/mini_player_bottom_padding.dart deleted file mode 100644 index c403361..0000000 --- a/lib/features/player/view/mini_player_bottom_padding.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/features/player/providers/player_form.dart'; - -class MiniPlayerBottomPadding extends HookConsumerWidget { - const MiniPlayerBottomPadding({super.key}); - @override - Widget build(BuildContext context, WidgetRef ref) { - return AnimatedSize( - duration: const Duration(milliseconds: 200), - child: ref.watch(isPlayerActiveProvider) - ? const SizedBox(height: playerMinHeight + 8) - : const SizedBox.shrink(), - ); - } -} diff --git a/lib/features/player/view/player_when_expanded.dart b/lib/features/player/view/player_when_expanded.dart index 0deda52..5fe0ea7 100644 --- a/lib/features/player/view/player_when_expanded.dart +++ b/lib/features/player/view/player_when_expanded.dart @@ -104,10 +104,8 @@ class PlayerWhenExpanded extends HookConsumerWidget { decoration: BoxDecoration( boxShadow: [ BoxShadow( - color: Theme.of(context) - .colorScheme - .primary - .withValues(alpha: 0.1), + color: + Theme.of(context).colorScheme.primary.withOpacity(0.1), blurRadius: 32 * earlyPercentage, spreadRadius: 8 * earlyPercentage, // offset: Offset(0, 16 * earlyPercentage), @@ -173,7 +171,7 @@ class PlayerWhenExpanded extends HookConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withValues(alpha: 0.7), + .withOpacity(0.7), ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/features/player/view/player_when_minimized.dart b/lib/features/player/view/player_when_minimized.dart index 1a5774b..435a6de 100644 --- a/lib/features/player/view/player_when_minimized.dart +++ b/lib/features/player/view/player_when_minimized.dart @@ -93,7 +93,7 @@ class PlayerWhenMinimized extends HookConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withValues(alpha: 0.7), + .withOpacity(0.7), ), ), ], diff --git a/lib/features/player/view/widgets/chapter_selection_button.dart b/lib/features/player/view/widgets/chapter_selection_button.dart index 04cbd0e..889392a 100644 --- a/lib/features/player/view/widgets/chapter_selection_button.dart +++ b/lib/features/player/view/widgets/chapter_selection_button.dart @@ -1,18 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart' - show audiobookPlayerProvider; -import 'package:vaani/features/player/providers/currently_playing_provider.dart' - show currentPlayingChapterProvider, currentlyPlayingBookProvider; -import 'package:vaani/features/player/view/player_when_expanded.dart' - show pendingPlayerModals; -import 'package:vaani/features/player/view/widgets/playing_indicator_icon.dart'; -import 'package:vaani/main.dart' show appLogger; -import 'package:vaani/shared/extensions/chapter.dart' show ChapterDuration; -import 'package:vaani/shared/extensions/duration_format.dart' - show DurationFormat; -import 'package:vaani/shared/hooks.dart' show useTimer; +import 'package:vaani/features/player/providers/audiobook_player.dart'; +import 'package:vaani/features/player/providers/currently_playing_provider.dart'; +import 'package:vaani/features/player/view/player_when_expanded.dart'; +import 'package:vaani/main.dart'; +import 'package:vaani/shared/extensions/chapter.dart'; +import 'package:vaani/shared/extensions/duration_format.dart'; +import 'package:vaani/shared/hooks.dart'; class ChapterSelectionButton extends HookConsumerWidget { const ChapterSelectionButton({ @@ -72,7 +67,6 @@ class ChapterSelectionModal extends HookConsumerWidget { useTimer(scrollToCurrentChapter, 500.ms); // useInterval(scrollToCurrentChapter, 500.ms); - final theme = Theme.of(context); return Column( children: [ ListTile( @@ -87,41 +81,24 @@ class ChapterSelectionModal extends HookConsumerWidget { child: currentBook?.chapters == null ? const Text('No chapters found') : Column( - children: currentBook!.chapters.map( - (chapter) { - final isCurrent = currentChapterIndex == chapter.id; - final isPlayed = currentChapterIndex != null && - chapter.id < currentChapterIndex; - return ListTile( - autofocus: isCurrent, - iconColor: isPlayed && !isCurrent - ? theme.disabledColor - : null, - title: Text( - chapter.title, - style: isPlayed && !isCurrent - ? TextStyle(color: theme.disabledColor) - : null, - ), - subtitle: Text( + children: [ + for (final chapter in currentBook!.chapters) + ListTile( + title: Text(chapter.title), + trailing: Text( '(${chapter.duration.smartBinaryFormat})', - style: isPlayed && !isCurrent - ? TextStyle(color: theme.disabledColor) - : null, ), - trailing: isCurrent - ? const PlayingIndicatorIcon() - : const Icon(Icons.play_arrow), - selected: isCurrent, - key: isCurrent ? chapterKey : null, + selected: currentChapterIndex == chapter.id, + key: currentChapterIndex == chapter.id + ? chapterKey + : null, onTap: () { Navigator.of(context).pop(); notifier.seek(chapter.start + 90.ms); notifier.play(); }, - ); - }, - ).toList(), + ), + ], ), ), ), diff --git a/lib/features/player/view/widgets/playing_indicator_icon.dart b/lib/features/player/view/widgets/playing_indicator_icon.dart deleted file mode 100644 index d179797..0000000 --- a/lib/features/player/view/widgets/playing_indicator_icon.dart +++ /dev/null @@ -1,194 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:flutter_animate/flutter_animate.dart'; - -/// An icon that animates like audio equalizer bars to indicate playback. -/// -/// Creates multiple vertical bars that independently animate their height -/// in a looping, visually dynamic pattern. -class PlayingIndicatorIcon extends StatefulWidget { - /// The number of vertical bars in the indicator. - final int barCount; - - /// The total width and height of the icon area. - final double size; - - /// The color of the bars. Defaults to the current [IconTheme] color. - final Color? color; - - /// The minimum height factor for a bar (relative to [size]). - /// When [centerSymmetric] is true, this represents the minimum height - /// extending from the center line (so total minimum height is 2 * minHeightFactor * size). - /// When false, it's the minimum height from the bottom. - final double minHeightFactor; - - /// The maximum height factor for a bar (relative to [size]). - /// When [centerSymmetric] is true, this represents the maximum height - /// extending from the center line (so total maximum height is 2 * maxHeightFactor * size). - /// When false, it's the maximum height from the bottom. - final double maxHeightFactor; - - /// Base duration for a full up/down animation cycle for a single bar. - /// Actual duration will vary slightly per bar. - final Duration baseCycleDuration; - - /// If true, the bars animate symmetrically expanding/collapsing from the - /// horizontal center line. If false (default), they expand/collapse from - /// the bottom edge. - final bool centerSymmetric; - - const PlayingIndicatorIcon({ - super.key, - this.barCount = 4, - this.size = 20.0, - this.color, - this.minHeightFactor = 0.2, - this.maxHeightFactor = 1.0, - this.baseCycleDuration = const Duration(milliseconds: 350), - this.centerSymmetric = true, - }); - - @override - State createState() => _PlayingIndicatorIconState(); -} - -class _PlayingIndicatorIconState extends State { - late List<_BarAnimationParams> _animationParams; - final _random = Random(); - - @override - void initState() { - super.initState(); - _animationParams = - List.generate(widget.barCount, _createRandomParams, growable: false); - } - - // Helper to generate random parameters for one bar's animation cycle - _BarAnimationParams _createRandomParams(int index) { - final duration1 = - (widget.baseCycleDuration * (0.8 + _random.nextDouble() * 0.4)); - final duration2 = - (widget.baseCycleDuration * (0.8 + _random.nextDouble() * 0.4)); - - // Note: These factors represent the scale relative to the *half-height* - // if centerSymmetric is true, controlled by the alignment in scaleY. - final targetHeightFactor1 = widget.minHeightFactor + - _random.nextDouble() * - (widget.maxHeightFactor - widget.minHeightFactor); - final targetHeightFactor2 = widget.minHeightFactor + - _random.nextDouble() * - (widget.maxHeightFactor - widget.minHeightFactor); - - // --- Random initial delay --- - final initialDelay = - (_random.nextDouble() * (widget.baseCycleDuration.inMilliseconds / 4)) - .ms; - - return _BarAnimationParams( - duration1: duration1, - duration2: duration2, - targetHeightFactor1: targetHeightFactor1, - targetHeightFactor2: targetHeightFactor2, - initialDelay: initialDelay, - ); - } - - @override - Widget build(BuildContext context) { - final color = widget.color ?? - IconTheme.of(context).color ?? - Theme.of(context).colorScheme.primary; - - // --- Bar geometry calculation --- - final double totalSpacing = widget.size * 0.2; - // Ensure at least 1px spacing if size is very small - final double barSpacing = max(1.0, totalSpacing / (widget.barCount + 1)); - final double availableWidthForBars = - widget.size - (barSpacing * (widget.barCount + 1)); - final double barWidth = max(1.0, availableWidthForBars / widget.barCount); - // Max height remains the full size potential for the container - final double maxHeight = widget.size; - - // Determine the alignment for scaling based on the symmetric flag - final Alignment scaleAlignment = - widget.centerSymmetric ? Alignment.center : Alignment.bottomCenter; - - // Determine the cross axis alignment for the Row - final CrossAxisAlignment rowAlignment = widget.centerSymmetric - ? CrossAxisAlignment.center - : CrossAxisAlignment.end; - - return SizedBox( - width: widget.size, - height: widget.size, - // Clip ensures bars don't draw outside the SizedBox bounds - // especially important for center alignment if maxFactor > 0.5 - child: ClipRect( - child: Row( - // Use calculated alignment - crossAxisAlignment: rowAlignment, - // Use spaceEvenly for better distribution, especially with center alignment - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate( - widget.barCount, - (index) { - final params = _animationParams[index]; - // The actual bar widget that will be animated - return Container( - width: barWidth, - // Set initial height to the max potential height - // The scaleY animation will control the visible height - height: maxHeight, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(barWidth / 2), - ), - ) - .animate( - delay: params.initialDelay, - onPlay: (controller) => controller.repeat( - reverse: true, - ), - ) - // 1. Scale to targetHeightFactor1 - .scaleY( - begin: - widget.minHeightFactor, // Scale factor starts near min - end: params.targetHeightFactor1, - duration: params.duration1, - curve: Curves.easeInOutCirc, - alignment: scaleAlignment, // Apply chosen alignment - ) - // 2. Then scale to targetHeightFactor2 - .then() - .scaleY( - end: params.targetHeightFactor2, - duration: params.duration2, - curve: Curves.easeInOutCirc, - alignment: scaleAlignment, // Apply chosen alignment - ); - }, - growable: false, - ), - ), - ), - ); - } -} - -// Helper class: Renamed height fields for clarity -class _BarAnimationParams { - final Duration duration1; - final Duration duration2; - final double targetHeightFactor1; // Factor relative to total size - final double targetHeightFactor2; // Factor relative to total size - final Duration initialDelay; - - _BarAnimationParams({ - required this.duration1, - required this.duration2, - required this.targetHeightFactor1, - required this.targetHeightFactor2, - required this.initialDelay, - }); -} diff --git a/lib/features/player/view/widgets/speed_selector.dart b/lib/features/player/view/widgets/speed_selector.dart index ab564b1..65a3bfa 100644 --- a/lib/features/player/view/widgets/speed_selector.dart +++ b/lib/features/player/view/widgets/speed_selector.dart @@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:list_wheel_scroll_view_nls/list_wheel_scroll_view_nls.dart'; import 'package:vaani/features/player/providers/audiobook_player.dart'; import 'package:vaani/settings/app_settings_provider.dart'; +import 'package:vaani/shared/hooks.dart'; const double itemExtent = 25; diff --git a/lib/features/shake_detection/providers/shake_detector.g.dart b/lib/features/shake_detection/providers/shake_detector.g.dart index ed81aaf..0f4285f 100644 --- a/lib/features/shake_detection/providers/shake_detector.g.dart +++ b/lib/features/shake_detection/providers/shake_detector.g.dart @@ -23,4 +23,4 @@ final shakeDetectorProvider = typedef _$ShakeDetector = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart b/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart index 0b1db3f..7daade0 100644 --- a/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart +++ b/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart @@ -22,4 +22,4 @@ final sleepTimerProvider = typedef _$SleepTimer = Notifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/sleep_timer/view/sleep_timer_button.dart b/lib/features/sleep_timer/view/sleep_timer_button.dart index 9712813..075b0e4 100644 --- a/lib/features/sleep_timer/view/sleep_timer_button.dart +++ b/lib/features/sleep_timer/view/sleep_timer_button.dart @@ -11,6 +11,7 @@ import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart' import 'package:vaani/main.dart'; import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/duration_format.dart'; +import 'package:vaani/shared/hooks.dart'; class SleepTimerButton extends HookConsumerWidget { const SleepTimerButton({ diff --git a/lib/features/you/view/server_manager.dart b/lib/features/you/view/server_manager.dart index 75e3f43..8a385f2 100644 --- a/lib/features/you/view/server_manager.dart +++ b/lib/features/you/view/server_manager.dart @@ -2,22 +2,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/api/api_provider.dart' show makeBaseUrl; -import 'package:vaani/api/authenticated_users_provider.dart' - show authenticatedUsersProvider; -import 'package:vaani/api/server_provider.dart' - show ServerAlreadyExistsException, audiobookShelfServerProvider; -import 'package:vaani/features/onboarding/view/user_login.dart' - show UserLoginWidget; -import 'package:vaani/features/player/view/mini_player_bottom_padding.dart' - show MiniPlayerBottomPadding; -import 'package:vaani/main.dart' show appLogger; -import 'package:vaani/router/router.dart' show Routes; -import 'package:vaani/settings/api_settings_provider.dart' - show apiSettingsProvider; +import 'package:vaani/api/api_provider.dart'; +import 'package:vaani/api/authenticated_user_provider.dart'; +import 'package:vaani/api/server_provider.dart'; +import 'package:vaani/main.dart'; +import 'package:vaani/models/error_response.dart'; +import 'package:vaani/router/router.dart'; +import 'package:vaani/settings/api_settings_provider.dart'; import 'package:vaani/settings/models/models.dart' as model; -import 'package:vaani/shared/extensions/obfuscation.dart' show ObfuscateSet; -import 'package:vaani/shared/widgets/add_new_server.dart' show AddNewServer; +import 'package:vaani/shared/extensions/obfuscation.dart'; +import 'package:vaani/shared/widgets/add_new_server.dart'; class ServerManagerPage extends HookConsumerWidget { const ServerManagerPage({ @@ -26,6 +20,15 @@ class ServerManagerPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final apiSettings = ref.watch(apiSettingsProvider); + final registeredServers = ref.watch(audiobookShelfServerProvider); + final registeredServersAsList = registeredServers.toList(); + final availableUsers = ref.watch(authenticatedUserProvider); + final serverURIController = useTextEditingController(); + final formKey = GlobalKey(); + + appLogger.fine('registered servers: ${registeredServers.obfuscate()}'); + appLogger.fine('available users: ${availableUsers.obfuscate()}'); return Scaffold( appBar: AppBar( title: const Text('Manage Accounts'), @@ -33,340 +36,420 @@ class ServerManagerPage extends HookConsumerWidget { body: Center( child: Padding( padding: const EdgeInsets.all(8.0), - child: ServerManagerBody(), - ), - ), - ); - } -} - -class ServerManagerBody extends HookConsumerWidget { - const ServerManagerBody({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final registeredServers = ref.watch(audiobookShelfServerProvider); - final registeredServersAsList = registeredServers.toList(); - final availableUsers = ref.watch(authenticatedUsersProvider); - final apiSettings = ref.watch(apiSettingsProvider); - final serverURIController = useTextEditingController(); - final formKey = GlobalKey(); - - appLogger.fine('registered servers: ${registeredServers.obfuscate()}'); - appLogger.fine('available users: ${availableUsers.obfuscate()}'); - - return Column( - // crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - const Text( - 'Registered Servers', - ), - Expanded( - child: ListView.builder( - itemCount: registeredServers.length, - reverse: true, - itemBuilder: (context, index) { - var registeredServer = registeredServersAsList[index]; - return ExpansionTile( - title: Text(registeredServer.serverUrl.toString()), - subtitle: Text( - 'Users: ${availableUsers.where((element) => element.server == registeredServer).length}', - ), - // children are list of users of this server - children: availableUsers - .where( - (element) => element.server == registeredServer, - ) - .map( - (e) => AvailableUserTile(user: e), - ) - .nonNulls - .toList() - - // add buttons of delete server and add user to server at the end - ..addAll([ - AddUserTile(server: registeredServer), - DeleteServerTile(server: registeredServer), - ]), - ); - }, - ), - ), - const SizedBox(height: 20), - const Padding( - padding: EdgeInsets.all(8.0), - child: Text('Add New Server'), - ), - Form( - key: formKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: AddNewServer( - controller: serverURIController, - onPressed: () { - if (formKey.currentState!.validate()) { - try { - final newServer = model.AudiobookShelfServer( - serverUrl: makeBaseUrl(serverURIController.text), - ); - ref.read(audiobookShelfServerProvider.notifier).addServer( - newServer, - ); - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeServer: newServer, - ), - ); - serverURIController.clear(); - } on ServerAlreadyExistsException catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(e.toString()), - ), - ); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid URL'), - ), - ); - } - }, - ), - ), - MiniPlayerBottomPadding(), - ], - ); - } -} - -class DeleteServerTile extends HookConsumerWidget { - const DeleteServerTile({ - super.key, - required this.server, - }); - - final model.AudiobookShelfServer server; - - @override - Widget build(BuildContext context, WidgetRef ref) { - return ListTile( - leading: const Icon(Icons.delete), - title: const Text('Delete Server'), - onTap: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('Remove Server and Users'), - // Make content scrollable in case of smaller screens/keyboard - content: SingleChildScrollView( - child: Text.rich( - TextSpan( - children: [ - const TextSpan( - text: 'This will remove the server ', - ), - TextSpan( - text: server.serverUrl.host, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ), - const TextSpan( - text: ' and all its users\' login info from this app.', - ), - ], - ), - ), + child: Column( + // crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Text( + 'Registered Servers', ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - ref - .read( - audiobookShelfServerProvider.notifier, - ) - .removeServer( - server, - removeUsers: true, - ); - Navigator.of(context).pop(); - }, - child: const Text('Delete'), - ), - ], - ); - }, - ); - }, - ); - } -} - -class AddUserTile extends HookConsumerWidget { - const AddUserTile({ - super.key, - required this.server, - }); - - final model.AudiobookShelfServer server; - - @override - Widget build(BuildContext context, WidgetRef ref) { - return ListTile( - leading: const Icon(Icons.person_add), - title: const Text('Add User'), - onTap: () async { - await showDialog( - context: context, - // barrierDismissible: false, // Optional: prevent closing by tapping outside - builder: (dialogContext) { - // Use a different context name to avoid conflicts - return AlertDialog( - title: Text('Add User to ${server.serverUrl.host}'), - // Make content scrollable in case of smaller screens/keyboard - content: SingleChildScrollView( - child: UserLoginWidget( - server: server.serverUrl, - // Pass the callback to pop the dialog on success - onSuccess: (user) { - // Add the user to the server - ref.read(authenticatedUsersProvider.notifier).addUser(user); - Navigator.of(dialogContext).pop(); // Close the dialog - // Optional: Show a confirmation SnackBar - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('User added successfully! Switch?'), - action: SnackBarAction( - label: 'Switch', - onPressed: () { - // Switch to the new user - ref.read(apiSettingsProvider.notifier).updateState( - ref.read(apiSettingsProvider).copyWith( - activeUser: user, - ), - ); - context.goNamed(Routes.home.name); - }, - ), + Expanded( + child: ListView.builder( + itemCount: registeredServers.length, + reverse: true, + itemBuilder: (context, index) { + var registeredServer = registeredServersAsList[index]; + return ExpansionTile( + title: Text(registeredServer.serverUrl.toString()), + subtitle: Text( + 'Users: ${availableUsers.where((element) => element.server == registeredServer).length}', ), + // trailing: _DeleteServerButton( + // registeredServer: registeredServer, + // ), + // children are list of users of this server + children: availableUsers + .where( + (element) => element.server == registeredServer, + ) + .map( + (e) => ListTile( + selected: apiSettings.activeUser == e, + leading: apiSettings.activeUser == e + ? const Icon(Icons.person) + : const Icon(Icons.person_off_outlined), + title: Text(e.username ?? 'Anonymous'), + onTap: apiSettings.activeUser == e + ? null + : () { + ref + .read(apiSettingsProvider.notifier) + .updateState( + apiSettings.copyWith( + activeUser: e, + ), + ); + // pop all routes and go to the home page + // while (context.canPop()) { + // context.pop(); + // } + context.goNamed( + Routes.home.name, + ); + }, + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Delete User'), + content: const Text( + 'Are you sure you want to delete this user?', + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + ref + .read( + authenticatedUserProvider + .notifier, + ) + .removeUser(e); + Navigator.of(context).pop(); + }, + child: const Text('Delete'), + ), + ], + ); + }, + ); + }, + ), + ), + ) + .nonNulls + .toList() + + // add buttons of delete server and add user to server at the end + ..addAll([ + ListTile( + leading: const Icon(Icons.person_add), + title: const Text('Add User'), + onTap: () async { + // open a dialog to add a new user with username and password or another method using only auth token + final addedUser = await showDialog( + context: context, + builder: (context) { + return _AddUserDialog( + server: registeredServer, + ); + }, + ); + + // if (addedUser != null) { + // // show a snackbar that the user has been added and ask if change to this user + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: const Text( + // 'User added successfully, do you want to switch to this user?', + // ), + // action: SnackBarAction( + // label: 'Switch', + // onPressed: () { + // // set the active user + // ref + // .read(apiSettingsProvider.notifier) + // .updateState( + // apiSettings.copyWith( + // activeUser: addedUser, + // ), + // ); + + // context.goNamed(Routes.home.name); + // }, + // ), + // ), + // ); + // } + }, + ), + ListTile( + leading: const Icon(Icons.delete), + title: const Text('Delete Server'), + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Delete Server'), + content: const Text( + 'Are you sure you want to delete this server and all its users?', + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + ref + .read( + audiobookShelfServerProvider + .notifier, + ) + .removeServer( + registeredServer, + removeUsers: true, + ); + Navigator.of(context).pop(); + }, + child: const Text('Delete'), + ), + ], + ); + }, + ); + }, + ), + ]), ); }, ), ), - actions: [ - TextButton( + const SizedBox(height: 20), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text('Add New Server'), + ), + Form( + key: formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: AddNewServer( + controller: serverURIController, onPressed: () { - Navigator.of(dialogContext).pop(); // Close the dialog - }, - child: const Text('Cancel'), - ), - ], - ); - }, - ); - // No need for the SnackBar asking to switch user here anymore. - }, - ); - } -} - -class AvailableUserTile extends HookConsumerWidget { - const AvailableUserTile({ - super.key, - required this.user, - }); - - final model.AuthenticatedUser user; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final apiSettings = ref.watch(apiSettingsProvider); - - return ListTile( - selected: apiSettings.activeUser == user, - leading: apiSettings.activeUser == user - ? const Icon(Icons.person) - : const Icon(Icons.person_off_outlined), - title: Text(user.username ?? 'Anonymous'), - onTap: apiSettings.activeUser == user - ? null - : () { - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeUser: user, - ), - ); - // pop all routes and go to the home page - // while (context.canPop()) { - // context.pop(); - // } - context.goNamed( - Routes.home.name, - ); - }, - trailing: IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('Remove User Login'), - content: Text.rich( - TextSpan( - children: [ - const TextSpan( - text: 'This will remove login details of the user ', - ), - TextSpan( - text: user.username ?? 'Anonymous', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, + if (formKey.currentState!.validate()) { + try { + final newServer = model.AudiobookShelfServer( + serverUrl: makeBaseUrl(serverURIController.text), + ); + ref + .read(audiobookShelfServerProvider.notifier) + .addServer( + newServer, + ); + ref.read(apiSettingsProvider.notifier).updateState( + apiSettings.copyWith( + activeServer: newServer, + ), + ); + serverURIController.clear(); + } on ServerAlreadyExistsException catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(e.toString()), + ), + ); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Invalid URL'), ), - ), - const TextSpan( - text: ' from this app.', - ), - ], - ), + ); + } + }, ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - ref - .read( - authenticatedUsersProvider.notifier, - ) - .removeUser(user); - Navigator.of(context).pop(); - }, - child: const Text('Delete'), - ), - ], - ); - }, - ); - }, + ), + ], + ), + ), ), ); } } + +class _AddUserDialog extends HookConsumerWidget { + const _AddUserDialog({ + super.key, + required this.server, + }); + + final model.AudiobookShelfServer server; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final usernameController = useTextEditingController(); + final passwordController = useTextEditingController(); + final authTokensController = useTextEditingController(); + final isPasswordVisible = useState(false); + final apiSettings = ref.watch(apiSettingsProvider); + final isMethodAuth = useState(false); + final api = ref.watch(audiobookshelfApiProvider(server.serverUrl)); + + final formKey = GlobalKey(); + + final serverErrorResponse = ErrorResponseHandler(); + + /// Login to the server and save the user + Future loginAndSave() async { + model.AuthenticatedUser? authenticatedUser; + if (isMethodAuth.value) { + api.token = authTokensController.text; + final success = await api.misc.authorize( + responseErrorHandler: serverErrorResponse.storeError, + ); + if (success != null) { + authenticatedUser = model.AuthenticatedUser( + server: server, + id: success.user.id, + username: success.user.username, + authToken: api.token!, + ); + } + } else { + final username = usernameController.text; + final password = passwordController.text; + final success = await api.login( + username: username, + password: password, + responseErrorHandler: serverErrorResponse.storeError, + ); + if (success != null) { + authenticatedUser = model.AuthenticatedUser( + server: server, + id: success.user.id, + username: username, + authToken: api.token!, + ); + } + } + // add the user to the list of users + if (authenticatedUser != null) { + ref.read(authenticatedUserProvider.notifier).addUser(authenticatedUser); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Login failed. Got response: ${serverErrorResponse.response.body} (${serverErrorResponse.response.statusCode})', + ), + ), + ); + } + return authenticatedUser; + } + + return AlertDialog( + // title: Text('Add User for ${server.serverUrl}'), + title: Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'Add User for ', + style: Theme.of(context).textTheme.labelLarge, + ), + TextSpan( + text: server.serverUrl.toString(), + style: Theme.of(context).textTheme.labelLarge?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ), + content: Form( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 8.0, + children: [ + ChoiceChip( + label: const Text('Username/Password'), + selected: !isMethodAuth.value, + onSelected: (selected) { + isMethodAuth.value = !selected; + }, + ), + ChoiceChip( + label: const Text('Auth Token'), + selected: isMethodAuth.value, + onSelected: (selected) { + isMethodAuth.value = selected; + }, + ), + ], + ), + const SizedBox(height: 16), + if (isMethodAuth.value) + TextFormField( + controller: authTokensController, + decoration: const InputDecoration(labelText: 'Auth Token'), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an auth token'; + } + return null; + }, + ) + else ...[ + TextFormField( + controller: usernameController, + decoration: const InputDecoration(labelText: 'Username'), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a username'; + } + return null; + }, + ), + TextFormField( + controller: passwordController, + decoration: InputDecoration( + labelText: 'Password', + suffixIcon: IconButton( + icon: Icon( + isPasswordVisible.value + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () { + isPasswordVisible.value = !isPasswordVisible.value; + }, + ), + ), + obscureText: !isPasswordVisible.value, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a password'; + } + return null; + }, + ), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () async { + if (formKey.currentState!.validate()) { + final addedUser = await loginAndSave(); + if (addedUser != null) { + Navigator.of(context).pop(addedUser); + } + } + }, + child: const Text('Add User'), + ), + ], + ); + } +} diff --git a/lib/features/you/view/widgets/library_switch_chip.dart b/lib/features/you/view/widgets/library_switch_chip.dart deleted file mode 100644 index a673332..0000000 --- a/lib/features/you/view/widgets/library_switch_chip.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shelfsdk/audiobookshelf_api.dart' show Library; -import 'package:vaani/api/library_provider.dart'; -import 'package:vaani/settings/api_settings_provider.dart' - show apiSettingsProvider; -import 'package:vaani/shared/icons/abs_icons.dart'; -import 'dart:io' show Platform; - -import 'package:flutter/foundation.dart'; -import 'package:vaani/main.dart' show appLogger; - -class LibrarySwitchChip extends HookConsumerWidget { - const LibrarySwitchChip({ - super.key, - required this.libraries, - }); - final List libraries; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final apiSettings = ref.watch(apiSettingsProvider); - - return ActionChip( - avatar: Icon( - AbsIcons.getIconByName( - apiSettings.activeLibraryId != null - ? libraries - .firstWhere( - (lib) => lib.id == apiSettings.activeLibraryId, - ) - .icon - : libraries.first.icon, - ), - ), // Replace with your icon - label: const Text('Change Library'), - // Enable only if libraries are loaded and not empty - onPressed: libraries.isNotEmpty - ? () => showLibrarySwitcher( - context, - ref, - ) - : null, // Disable if no libraries - ); - } -} - -// --- Helper Function to Show the Switcher --- -void showLibrarySwitcher( - BuildContext context, - WidgetRef ref, -) { - final content = _LibrarySelectionContent(); - - // --- Platform-Specific UI --- - bool isDesktop = false; - if (!kIsWeb) { - // dart:io Platform is not available on web - isDesktop = Platform.isLinux || Platform.isMacOS || Platform.isWindows; - } else { - // Basic web detection (might need refinement based on screen size) - // Consider using MediaQuery for a size-based check instead for web/tablet - final size = MediaQuery.of(context).size; - isDesktop = size.width > 600; // Example threshold for "desktop-like" layout - } - - if (isDesktop) { - // --- Desktop: Use AlertDialog --- - showDialog( - context: context, - builder: (dialogContext) => AlertDialog( - title: const Text('Select Library'), - content: SizedBox( - // Constrain size for dialogs - width: 300, // Adjust as needed - // Make content scrollable if list is long - child: Scrollbar(child: content), - ), - actions: [ - TextButton( - onPressed: () { - // Invalidate the provider to trigger a refetch - ref.invalidate(librariesProvider); - Navigator.pop(dialogContext); - }, - child: const Text('Refresh'), - ), - TextButton( - onPressed: () => Navigator.pop(dialogContext), - child: const Text('Cancel'), - ), - ], - ), - ); - } else { - // --- Mobile/Tablet: Use BottomSheet --- - showModalBottomSheet( - context: context, - // Make it scrollable and control height - isScrollControlled: true, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height * 0.6, // Max 60% of screen - ), - builder: (sheetContext) => Padding( - // Add padding within the bottom sheet - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, // Take minimum necessary height - children: [ - const Text( - 'Select Library', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - const Divider(), - Flexible( - // Allow the list to take remaining space and scroll - child: Scrollbar(child: content), - ), - const SizedBox(height: 10), - ElevatedButton.icon( - icon: const Icon(Icons.refresh), - label: const Text('Refresh'), - onPressed: () { - // Invalidate the provider to trigger a refetch - ref.invalidate(librariesProvider); - }, - ), - ], - ), - ), - ); - } -} - -// --- Widget for the Selection List Content (Reusable) --- -class _LibrarySelectionContent extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final librariesAsyncValue = ref.watch(librariesProvider); - final currentLibraryId = ref.watch( - apiSettingsProvider.select((settings) => settings.activeLibraryId), - ); - final errorColor = Theme.of(context).colorScheme.error; - return librariesAsyncValue.when( - // --- Loading State --- - loading: () => const Center(child: CircularProgressIndicator()), - - // --- Error State --- - error: (error, stackTrace) => Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.error_outline, color: errorColor), - const SizedBox(height: 10), - Text( - 'Error loading libraries: $error', - textAlign: TextAlign.center, - style: TextStyle(color: errorColor), - ), - const SizedBox(height: 16), - ElevatedButton.icon( - icon: const Icon(Icons.refresh), - label: const Text('Retry'), - onPressed: () { - // Invalidate the provider to trigger a refetch - ref.invalidate(librariesProvider); - }, - ), - ], - ), - ), - ), - - // --- Data State --- - data: (libraries) { - // Handle case where data loaded successfully but is empty - if (libraries.isEmpty) { - return const Center( - child: Padding( - padding: EdgeInsets.all(16.0), - child: Text('No libraries available.'), - ), - ); - } - - // Build the list if libraries are available - return Scrollbar( - // Add scrollbar for potentially long lists - child: ListView.builder( - shrinkWrap: true, // Important for Dialog/BottomSheet sizing - itemCount: libraries.length, - itemBuilder: (context, index) { - final library = libraries[index]; - final bool isSelected = library.id == currentLibraryId; - - return ListTile( - title: Text(library.name), - leading: Icon(AbsIcons.getIconByName(library.icon)), - selected: isSelected, - trailing: isSelected ? const Icon(Icons.check) : null, - onTap: () { - appLogger.info( - 'Selected library: ${library.name} (ID: ${library.id})', - ); - // Get current settings state - final currentSettings = ref.read(apiSettingsProvider); - // Update the active library ID - ref.read(apiSettingsProvider.notifier).updateState( - currentSettings.copyWith(activeLibraryId: library.id), - ); - // Close the dialog/bottom sheet - Navigator.pop(context); - }, - ); - }, - ), - ); - }, - ); - } -} diff --git a/lib/features/you/view/you_page.dart b/lib/features/you/view/you_page.dart index ca789db..2a7bee2 100644 --- a/lib/features/you/view/you_page.dart +++ b/lib/features/you/view/you_page.dart @@ -2,14 +2,10 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/api/api_provider.dart'; -import 'package:vaani/api/library_provider.dart' show librariesProvider; -import 'package:vaani/features/player/view/mini_player_bottom_padding.dart'; -import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'; import 'package:vaani/router/router.dart'; import 'package:vaani/settings/constants.dart'; import 'package:vaani/shared/utils.dart'; import 'package:vaani/shared/widgets/not_implemented.dart'; -import 'package:vaani/shared/widgets/vaani_logo.dart'; class YouPage extends HookConsumerWidget { const YouPage({ @@ -19,10 +15,10 @@ class YouPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final api = ref.watch(authenticatedApiProvider); - final librariesAsyncValue = ref.watch(librariesProvider); return Scaffold( appBar: AppBar( // title: const Text('You'), + backgroundColor: Colors.transparent, actions: [ IconButton( tooltip: 'Logs', @@ -66,35 +62,7 @@ class YouPage extends HookConsumerWidget { context.pushNamed(Routes.userManagement.name); }, ), - librariesAsyncValue.when( - data: (libraries) => - LibrarySwitchChip(libraries: libraries), - loading: () => const ActionChip( - avatar: SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator(strokeWidth: 2), - ), - label: Text('Loading Libs...'), - onPressed: null, // Disable while loading - ), - error: (error, stack) => ActionChip( - avatar: Icon( - Icons.error_outline, - color: Theme.of(context).colorScheme.error, - ), - label: const Text('Error Loading Libs'), - onPressed: () { - // Maybe show error details or allow retry - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Failed to load libraries: $error'), - ), - ); - }, - ), - ), // ActionChip( + // ActionChip( // avatar: const Icon(Icons.logout), // label: const Text('Logout'), // onPressed: () { @@ -137,6 +105,7 @@ class YouPage extends HookConsumerWidget { showNotImplementedToast(context); }, ), + AboutListTile( icon: const Icon(Icons.info), applicationName: AppMetadata.appName, @@ -159,16 +128,13 @@ class YouPage extends HookConsumerWidget { Theme.of(context).colorScheme.primary, BlendMode.srcIn, ), - child: const VaaniLogo( - size: 48, - ), + child: const VaaniLogo(), ), ), ], ), ), ), - SliverToBoxAdapter(child: MiniPlayerBottomPadding()), ], ), ); @@ -183,10 +149,6 @@ class UserBar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final me = ref.watch(meProvider); - final api = ref.watch(authenticatedApiProvider); - - final themeData = Theme.of(context); - final textTheme = themeData.textTheme; return me.when( data: (userData) { @@ -198,30 +160,19 @@ class UserBar extends HookConsumerWidget { // first letter of the username child: Text( userData.username[0].toUpperCase(), - style: textTheme.headlineLarge?.copyWith( + style: const TextStyle( + fontSize: 32, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 16), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userData.username, - style: textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - api.baseUrl.toString(), - style: textTheme.bodyMedium?.copyWith( - color: - themeData.colorScheme.onSurface.withValues(alpha: 0.6), - ), - ), - ], + Text( + userData.username, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), ), ], ); @@ -231,3 +182,29 @@ class UserBar extends HookConsumerWidget { ); } } + +class VaaniLogo extends StatelessWidget { + const VaaniLogo({ + super.key, + this.size, + this.duration = const Duration(milliseconds: 750), + this.curve = Curves.fastOutSlowIn, + }); + + final double? size; + final Duration duration; + final Curve curve; + + @override + Widget build(BuildContext context) { + final IconThemeData iconTheme = IconTheme.of(context); + final double? iconSize = size ?? iconTheme.size; + return AnimatedContainer( + width: iconSize, + height: iconSize, + duration: duration, + curve: curve, + child: Image.asset('assets/images/vaani_logo_foreground.png'), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 6020a6a..7f54ec1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ -import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; @@ -8,15 +7,12 @@ import 'package:vaani/features/downloads/providers/download_manager.dart'; import 'package:vaani/features/logging/core/logger.dart'; import 'package:vaani/features/playback_reporting/providers/playback_reporter_provider.dart'; import 'package:vaani/features/player/core/init.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart' - show audiobookPlayerProvider, simpleAudiobookPlayerProvider; +import 'package:vaani/features/player/providers/audiobook_player.dart'; import 'package:vaani/features/shake_detection/providers/shake_detector.dart'; import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart'; import 'package:vaani/router/router.dart'; import 'package:vaani/settings/api_settings_provider.dart'; import 'package:vaani/settings/app_settings_provider.dart'; -import 'package:vaani/theme/providers/system_theme_provider.dart'; -import 'package:vaani/theme/providers/theme_from_cover_provider.dart'; import 'package:vaani/theme/theme.dart'; final appLogger = Logger('vaani'); @@ -55,88 +51,19 @@ class MyApp extends ConsumerWidget { if (needOnboarding) { routerConfig.goNamed(Routes.onboarding.name); } - final appSettings = ref.watch(appSettingsProvider); - final themeSettings = appSettings.themeSettings; - ColorScheme lightColorScheme = brandLightColorScheme; - ColorScheme darkColorScheme = brandDarkColorScheme; - - final shouldUseHighContrast = - themeSettings.highContrast || MediaQuery.of(context).highContrast; - - if (shouldUseHighContrast) { - lightColorScheme = lightColorScheme.copyWith( - surface: Colors.white, - ); - darkColorScheme = darkColorScheme.copyWith( - surface: Colors.black, - ); - } - - if (themeSettings.useMaterialThemeFromSystem) { - var themes = - ref.watch(systemThemeProvider(highContrast: shouldUseHighContrast)); - if (themes.valueOrNull != null) { - lightColorScheme = themes.valueOrNull!.$1; - darkColorScheme = themes.valueOrNull!.$2; - } - } - - if (themeSettings.useCurrentPlayerThemeThroughoutApp) { - try { - final player = ref.watch(audiobookPlayerProvider); - if (player.book != null) { - final themeLight = ref.watch( - themeOfLibraryItemProvider( - player.book!.libraryItemId, - highContrast: shouldUseHighContrast, - brightness: Brightness.light, - ), - ); - final themeDark = ref.watch( - themeOfLibraryItemProvider( - player.book!.libraryItemId, - highContrast: shouldUseHighContrast, - brightness: Brightness.dark, - ), - ); - if (themeLight.valueOrNull != null && themeDark.valueOrNull != null) { - lightColorScheme = themeLight.valueOrNull!; - darkColorScheme = themeDark.valueOrNull!; - } - } - } catch (e) { - debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); - appLogger.severe('not building with player theme'); - appLogger.severe(e.toString()); - } - } - final appThemeLight = ThemeData( - useMaterial3: true, - colorScheme: lightColorScheme.harmonized(), - ); - final appThemeDark = ThemeData( - useMaterial3: true, - colorScheme: darkColorScheme.harmonized(), - brightness: Brightness.dark, - // TODO bottom sheet theme is not working - bottomSheetTheme: BottomSheetThemeData( - backgroundColor: darkColorScheme.surface, - ), - ); try { return MaterialApp.router( // debugShowCheckedModeBanner: false, - theme: appThemeLight, - darkTheme: appThemeDark, - themeMode: themeSettings.themeMode, + theme: lightTheme, + darkTheme: darkTheme, + themeMode: ref.watch(appSettingsProvider).themeSettings.isDarkMode + ? ThemeMode.dark + : ThemeMode.light, routerConfig: routerConfig, - themeAnimationCurve: Curves.easeInOut, ); } catch (e) { debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); - appLogger.severe(e.toString()); - if (needOnboarding) { routerConfig.goNamed(Routes.onboarding.name); } @@ -163,7 +90,6 @@ class _EagerInitialization extends ConsumerWidget { ref.watch(shakeDetectorProvider); } catch (e) { debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); - appLogger.severe(e.toString()); } return child; diff --git a/lib/models/error_response.dart b/lib/models/error_response.dart index 13352be..954f3f3 100644 --- a/lib/models/error_response.dart +++ b/lib/models/error_response.dart @@ -7,18 +7,14 @@ final _logger = Logger('ErrorResponse'); class ErrorResponseHandler { String? name; http.Response _response; - bool logRawResponse; ErrorResponseHandler({ this.name, http.Response? response, - this.logRawResponse = false, }) : _response = response ?? http.Response('', 418); void storeError(http.Response response, [Object? error]) { - if (logRawResponse) { - _logger.fine('for $name got response: ${response.obfuscate()}'); - } + _logger.fine('for $name got response: ${response.obfuscate()}'); _response = response; } diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 3ba1d98..4ff81c4 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -6,8 +6,6 @@ import 'package:vaani/api/api_provider.dart'; import 'package:vaani/main.dart'; import 'package:vaani/router/router.dart'; import 'package:vaani/settings/api_settings_provider.dart'; -import 'package:vaani/settings/app_settings_provider.dart' - show appSettingsProvider; import '../shared/widgets/shelves/home_shelf.dart'; @@ -19,10 +17,9 @@ class HomePage extends HookConsumerWidget { final views = ref.watch(personalizedViewProvider); final apiSettings = ref.watch(apiSettingsProvider); final scrollController = useScrollController(); - final appSettings = ref.watch(appSettingsProvider); - final homePageSettings = appSettings.homePageSettings; return Scaffold( appBar: AppBar( + backgroundColor: Colors.transparent, title: GestureDetector( child: Text( 'Vaani', @@ -67,21 +64,9 @@ class HomePage extends HookConsumerWidget { // .where((element) => !element.id.contains('discover')) .map((shelf) { appLogger.fine('building shelf ${shelf.label}'); - // check if showPlayButton is enabled for the shelf - // using the id of the shelf - final showPlayButton = switch (shelf.id) { - 'continue-listening' => - homePageSettings.showPlayButtonOnContinueListeningShelf, - 'continue-series' => - homePageSettings.showPlayButtonOnContinueSeriesShelf, - 'listen-again' => - homePageSettings.showPlayButtonOnListenAgainShelf, - _ => homePageSettings.showPlayButtonOnAllRemainingShelves, - }; return HomeShelf( title: shelf.label, shelf: shelf, - showPlayButton: showPlayButton, ); }).toList(); return RefreshIndicator( @@ -91,7 +76,7 @@ class HomePage extends HookConsumerWidget { child: ListView.separated( itemBuilder: (context, index) => shelvesToDisplay[index], separatorBuilder: (context, index) => Divider( - color: Theme.of(context).dividerColor.withValues(alpha: 0.1), + color: Theme.of(context).dividerColor.withOpacity(0.1), indent: 16, endIndent: 16, ), diff --git a/lib/pages/library_page.dart b/lib/pages/library_page.dart index a6950a6..18eedee 100644 --- a/lib/pages/library_page.dart +++ b/lib/pages/library_page.dart @@ -61,7 +61,7 @@ class LibraryPage extends HookConsumerWidget { child: ListView.separated( itemBuilder: (context, index) => shelvesToDisplay[index], separatorBuilder: (context, index) => Divider( - color: Theme.of(context).dividerColor.withValues(alpha: 0.1), + color: Theme.of(context).dividerColor.withOpacity(0.1), indent: 16, endIndent: 16, ), diff --git a/lib/router/constants.dart b/lib/router/constants.dart index 79c4556..9d01c29 100644 --- a/lib/router/constants.dart +++ b/lib/router/constants.dart @@ -27,11 +27,6 @@ class Routes { pathName: 'config', name: 'settings', ); - static const themeSettings = _SimpleRoute( - pathName: 'theme', - name: 'themeSettings', - parentRoute: settings, - ); static const autoSleepTimerSettings = _SimpleRoute( pathName: 'autoSleepTimer', name: 'autoSleepTimerSettings', @@ -52,11 +47,6 @@ class Routes { name: 'shakeDetectorSettings', parentRoute: settings, ); - static const homePageSettings = _SimpleRoute( - pathName: 'homePage', - name: 'homePageSettings', - parentRoute: settings, - ); // search and explore static const search = _SimpleRoute( diff --git a/lib/router/models/library_item_extras.dart b/lib/router/models/library_item_extras.dart index 55b734e..0d50040 100644 --- a/lib/router/models/library_item_extras.dart +++ b/lib/router/models/library_item_extras.dart @@ -16,4 +16,5 @@ class LibraryItemExtras with _$LibraryItemExtras { BookMinified? book, @Default('') String heroTagSuffix, }) = _LibraryItemExtras; + } diff --git a/lib/router/router.dart b/lib/router/router.dart index eda348e..c5b1a67 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -17,8 +17,6 @@ import 'package:vaani/settings/view/auto_sleep_timer_settings_page.dart'; import 'package:vaani/settings/view/notification_settings_page.dart'; import 'package:vaani/settings/view/player_settings_page.dart'; import 'package:vaani/settings/view/shake_detector_settings_page.dart'; -import 'package:vaani/settings/view/theme_settings_page.dart'; -import 'package:vaani/settings/view/home_page_settings_page.dart'; import 'scaffold_with_nav_bar.dart'; import 'transitions/slide.dart'; @@ -180,13 +178,6 @@ class MyAppRouter { // builder: (context, state) => const AppSettingsPage(), pageBuilder: defaultPageBuilder(const AppSettingsPage()), routes: [ - GoRoute( - path: Routes.themeSettings.pathName, - name: Routes.themeSettings.name, - pageBuilder: defaultPageBuilder( - const ThemeSettingsPage(), - ), - ), GoRoute( path: Routes.autoSleepTimerSettings.pathName, name: Routes.autoSleepTimerSettings.name, @@ -214,13 +205,6 @@ class MyAppRouter { const ShakeDetectorSettingsPage(), ), ), - GoRoute( - path: Routes.homePageSettings.pathName, - name: Routes.homePageSettings.name, - pageBuilder: defaultPageBuilder( - const HomePageSettingsPage(), - ), - ), ], ), GoRoute( diff --git a/lib/router/scaffold_with_nav_bar.dart b/lib/router/scaffold_with_nav_bar.dart index 27c3355..0a89162 100644 --- a/lib/router/scaffold_with_nav_bar.dart +++ b/lib/router/scaffold_with_nav_bar.dart @@ -2,15 +2,12 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:miniplayer/miniplayer.dart'; -import 'package:vaani/api/library_provider.dart' show currentLibraryProvider; import 'package:vaani/features/explore/providers/search_controller.dart'; import 'package:vaani/features/player/providers/player_form.dart'; import 'package:vaani/features/player/view/audiobook_player.dart'; import 'package:vaani/features/player/view/player_when_expanded.dart'; -import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'; import 'package:vaani/main.dart'; import 'package:vaani/router/router.dart'; -import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons; // stack to track changes in navigationShell.currentIndex // home is always at index 0 and at the start and should be the last before popping @@ -114,39 +111,17 @@ class ScaffoldWithNavBar extends HookConsumerWidget { // world scenario, the items would most likely be generated from the // branches of the shell route, which can be fetched using // `navigationShell.route.branches`. - destinations: _navigationItems.map((item) { - final isDestinationLibrary = item.name == 'Library'; - var currentLibrary = - ref.watch(currentLibraryProvider).valueOrNull; - final libraryIcon = AbsIcons.getIconByName( - currentLibrary?.icon, - ); - final destinationWidget = NavigationDestination( - icon: Icon( - isDestinationLibrary ? libraryIcon ?? item.icon : item.icon, - ), - selectedIcon: Icon( - isDestinationLibrary - ? libraryIcon ?? item.activeIcon - : item.activeIcon, - ), - label: isDestinationLibrary - ? currentLibrary?.name ?? item.name - : item.name, - tooltip: item.tooltip, - ); - if (isDestinationLibrary) { - return GestureDetector( - onSecondaryTap: () => showLibrarySwitcher(context, ref), - onDoubleTap: () => showLibrarySwitcher(context, ref), - child: - destinationWidget, // Wrap the actual NavigationDestination - ); - } else { - // Return the unwrapped destination for other items - return destinationWidget; - } - }).toList(), + destinations: _navigationItems + .map( + (item) => NavigationDestination( + icon: Icon(item.icon), + selectedIcon: item.activeIcon != null + ? Icon(item.activeIcon) + : Icon(item.icon), + label: item.name, + ), + ) + .toList(), selectedIndex: navigationShell.currentIndex, onDestinationSelected: (int index) => _onTap(context, index, ref), ), @@ -216,19 +191,16 @@ const _navigationItems = [ name: 'Library', icon: Icons.book_outlined, activeIcon: Icons.book, - tooltip: 'Browse your library', ), _NavigationItem( name: 'Explore', icon: Icons.search_outlined, activeIcon: Icons.search, - tooltip: 'Search and Explore', ), _NavigationItem( name: 'You', icon: Icons.account_circle_outlined, activeIcon: Icons.account_circle, - tooltip: 'Your Profile and Settings', ), ]; @@ -236,12 +208,10 @@ class _NavigationItem { const _NavigationItem({ required this.name, required this.icon, - required this.activeIcon, - this.tooltip, + this.activeIcon, }); final String name; final IconData icon; - final IconData activeIcon; - final String? tooltip; + final IconData? activeIcon; } diff --git a/lib/settings/api_settings_provider.g.dart b/lib/settings/api_settings_provider.g.dart index eff1d41..d9a222c 100644 --- a/lib/settings/api_settings_provider.g.dart +++ b/lib/settings/api_settings_provider.g.dart @@ -22,4 +22,4 @@ final apiSettingsProvider = typedef _$ApiSettings = Notifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/settings/app_settings_provider.dart b/lib/settings/app_settings_provider.dart index b86e736..6d61774 100644 --- a/lib/settings/app_settings_provider.dart +++ b/lib/settings/app_settings_provider.dart @@ -47,6 +47,11 @@ class AppSettings extends _$AppSettings { _logger.fine('wrote settings to box: $state'); } + void toggleDarkMode() { + state = state.copyWith + .themeSettings(isDarkMode: !state.themeSettings.isDarkMode); + } + void update(model.AppSettings newSettings) { state = newSettings; } diff --git a/lib/settings/app_settings_provider.g.dart b/lib/settings/app_settings_provider.g.dart index 8f3d8f5..df95738 100644 --- a/lib/settings/app_settings_provider.g.dart +++ b/lib/settings/app_settings_provider.g.dart @@ -6,7 +6,7 @@ part of 'app_settings_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$appSettingsHash() => r'314d7936f54550f57d308056a99230402342a6d0'; +String _$appSettingsHash() => r'f51d55f117692d4fb9f4b4febf02906c0953d334'; /// See also [AppSettings]. @ProviderFor(AppSettings) @@ -39,4 +39,4 @@ final sleepTimerSettingsProvider = typedef _$SleepTimerSettings = Notifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/settings/constants.dart b/lib/settings/constants.dart index 3b836d3..9ab7b55 100644 --- a/lib/settings/constants.dart +++ b/lib/settings/constants.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart' show immutable; + @immutable class AppMetadata { const AppMetadata._(); diff --git a/lib/settings/metadata/metadata_provider.dart b/lib/settings/metadata/metadata_provider.dart index 16802da..053a67b 100644 --- a/lib/settings/metadata/metadata_provider.dart +++ b/lib/settings/metadata/metadata_provider.dart @@ -1,13 +1,12 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'metadata_provider.g.dart'; @Riverpod(keepAlive: true) -Future deviceName(Ref ref) async { +Future deviceName(DeviceNameRef ref) async { final data = await _getDeviceData(DeviceInfoPlugin()); // try different keys to get the device name @@ -28,7 +27,7 @@ Future deviceName(Ref ref) async { } @Riverpod(keepAlive: true) -Future deviceModel(Ref ref) async { +Future deviceModel(DeviceModelRef ref) async { final data = await _getDeviceData(DeviceInfoPlugin()); // try different keys to get the device model @@ -49,7 +48,7 @@ Future deviceModel(Ref ref) async { } @Riverpod(keepAlive: true) -Future deviceSdkVersion(Ref ref) async { +Future deviceSdkVersion(DeviceSdkVersionRef ref) async { final data = await _getDeviceData(DeviceInfoPlugin()); // try different keys to get the device sdk version @@ -70,7 +69,7 @@ Future deviceSdkVersion(Ref ref) async { } @Riverpod(keepAlive: true) -Future deviceManufacturer(Ref ref) async { +Future deviceManufacturer(DeviceManufacturerRef ref) async { final data = await _getDeviceData(DeviceInfoPlugin()); // try different keys to get the device manufacturer diff --git a/lib/settings/metadata/metadata_provider.g.dart b/lib/settings/metadata/metadata_provider.g.dart index b83c0f0..858c351 100644 --- a/lib/settings/metadata/metadata_provider.g.dart +++ b/lib/settings/metadata/metadata_provider.g.dart @@ -6,7 +6,7 @@ part of 'metadata_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$deviceNameHash() => r'9e38adda74e70a91851a682f05228bd759356dcc'; +String _$deviceNameHash() => r'bc206a3a8c14f3da6e257e92e1ccdc79364f4e28'; /// See also [deviceName]. @ProviderFor(deviceName) @@ -19,10 +19,8 @@ final deviceNameProvider = FutureProvider.internal( allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef DeviceNameRef = FutureProviderRef; -String _$deviceModelHash() => r'922b13d9e35b5b5c5b8e96f2f2c2ae594f4f41f2'; +String _$deviceModelHash() => r'3d7e8ef4a37b90f98e38dc8d5f16ca30f71e15b2'; /// See also [deviceModel]. @ProviderFor(deviceModel) @@ -35,10 +33,8 @@ final deviceModelProvider = FutureProvider.internal( allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef DeviceModelRef = FutureProviderRef; -String _$deviceSdkVersionHash() => r'33178d80590808d1f4cca2be8a3b52c6f6724cac'; +String _$deviceSdkVersionHash() => r'501b01ae679e02fc5082feabea81cea0fa74afd7'; /// See also [deviceSdkVersion]. @ProviderFor(deviceSdkVersion) @@ -52,11 +48,9 @@ final deviceSdkVersionProvider = FutureProvider.internal( allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef DeviceSdkVersionRef = FutureProviderRef; String _$deviceManufacturerHash() => - r'39250767deb8635fa7c7e18bae23576b9b863e04'; + r'f0a57e6a92b551fbe266d0a6a29d35dc497882a9'; /// See also [deviceManufacturer]. @ProviderFor(deviceManufacturer) @@ -70,8 +64,6 @@ final deviceManufacturerProvider = FutureProvider.internal( allTransitiveDependencies: null, ); -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element typedef DeviceManufacturerRef = FutureProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/settings/models/app_settings.dart b/lib/settings/models/app_settings.dart index fe1f7be..80262ea 100644 --- a/lib/settings/models/app_settings.dart +++ b/lib/settings/models/app_settings.dart @@ -19,7 +19,6 @@ class AppSettings with _$AppSettings { @Default(NotificationSettings()) NotificationSettings notificationSettings, @Default(ShakeDetectionSettings()) ShakeDetectionSettings shakeDetectionSettings, - @Default(HomePageSettings()) HomePageSettings homePageSettings, }) = _AppSettings; factory AppSettings.fromJson(Map json) => @@ -29,10 +28,7 @@ class AppSettings with _$AppSettings { @freezed class ThemeSettings with _$ThemeSettings { const factory ThemeSettings({ - @Default(ThemeMode.system) ThemeMode themeMode, - @Default(false) bool highContrast, - @Default(false) bool useMaterialThemeFromSystem, - @Default('#FF311B92') String customThemeColor, + @Default(true) bool isDarkMode, @Default(true) bool useMaterialThemeOnItemPage, @Default(true) bool useCurrentPlayerThemeThroughoutApp, }) = _ThemeSettings; @@ -231,16 +227,3 @@ enum ShakeAction { } enum ShakeDetectedFeedback { vibrate, beep } - -@freezed -class HomePageSettings with _$HomePageSettings { - const factory HomePageSettings({ - @Default(true) bool showPlayButtonOnContinueListeningShelf, - @Default(false) bool showPlayButtonOnContinueSeriesShelf, - @Default(false) bool showPlayButtonOnAllRemainingShelves, - @Default(false) bool showPlayButtonOnListenAgainShelf, - }) = _HomePageSettings; - - factory HomePageSettings.fromJson(Map json) => - _$HomePageSettingsFromJson(json); -} diff --git a/lib/settings/models/app_settings.freezed.dart b/lib/settings/models/app_settings.freezed.dart index 17dea47..3f8fd48 100644 --- a/lib/settings/models/app_settings.freezed.dart +++ b/lib/settings/models/app_settings.freezed.dart @@ -29,7 +29,6 @@ mixin _$AppSettings { throw _privateConstructorUsedError; ShakeDetectionSettings get shakeDetectionSettings => throw _privateConstructorUsedError; - HomePageSettings get homePageSettings => throw _privateConstructorUsedError; /// Serializes this AppSettings to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -53,8 +52,7 @@ abstract class $AppSettingsCopyWith<$Res> { SleepTimerSettings sleepTimerSettings, DownloadSettings downloadSettings, NotificationSettings notificationSettings, - ShakeDetectionSettings shakeDetectionSettings, - HomePageSettings homePageSettings}); + ShakeDetectionSettings shakeDetectionSettings}); $ThemeSettingsCopyWith<$Res> get themeSettings; $PlayerSettingsCopyWith<$Res> get playerSettings; @@ -62,7 +60,6 @@ abstract class $AppSettingsCopyWith<$Res> { $DownloadSettingsCopyWith<$Res> get downloadSettings; $NotificationSettingsCopyWith<$Res> get notificationSettings; $ShakeDetectionSettingsCopyWith<$Res> get shakeDetectionSettings; - $HomePageSettingsCopyWith<$Res> get homePageSettings; } /// @nodoc @@ -86,7 +83,6 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings> Object? downloadSettings = null, Object? notificationSettings = null, Object? shakeDetectionSettings = null, - Object? homePageSettings = null, }) { return _then(_value.copyWith( themeSettings: null == themeSettings @@ -113,10 +109,6 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings> ? _value.shakeDetectionSettings : shakeDetectionSettings // ignore: cast_nullable_to_non_nullable as ShakeDetectionSettings, - homePageSettings: null == homePageSettings - ? _value.homePageSettings - : homePageSettings // ignore: cast_nullable_to_non_nullable - as HomePageSettings, ) as $Val); } @@ -182,16 +174,6 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings> return _then(_value.copyWith(shakeDetectionSettings: value) as $Val); }); } - - /// Create a copy of AppSettings - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $HomePageSettingsCopyWith<$Res> get homePageSettings { - return $HomePageSettingsCopyWith<$Res>(_value.homePageSettings, (value) { - return _then(_value.copyWith(homePageSettings: value) as $Val); - }); - } } /// @nodoc @@ -208,8 +190,7 @@ abstract class _$$AppSettingsImplCopyWith<$Res> SleepTimerSettings sleepTimerSettings, DownloadSettings downloadSettings, NotificationSettings notificationSettings, - ShakeDetectionSettings shakeDetectionSettings, - HomePageSettings homePageSettings}); + ShakeDetectionSettings shakeDetectionSettings}); @override $ThemeSettingsCopyWith<$Res> get themeSettings; @@ -223,8 +204,6 @@ abstract class _$$AppSettingsImplCopyWith<$Res> $NotificationSettingsCopyWith<$Res> get notificationSettings; @override $ShakeDetectionSettingsCopyWith<$Res> get shakeDetectionSettings; - @override - $HomePageSettingsCopyWith<$Res> get homePageSettings; } /// @nodoc @@ -246,7 +225,6 @@ class __$$AppSettingsImplCopyWithImpl<$Res> Object? downloadSettings = null, Object? notificationSettings = null, Object? shakeDetectionSettings = null, - Object? homePageSettings = null, }) { return _then(_$AppSettingsImpl( themeSettings: null == themeSettings @@ -273,10 +251,6 @@ class __$$AppSettingsImplCopyWithImpl<$Res> ? _value.shakeDetectionSettings : shakeDetectionSettings // ignore: cast_nullable_to_non_nullable as ShakeDetectionSettings, - homePageSettings: null == homePageSettings - ? _value.homePageSettings - : homePageSettings // ignore: cast_nullable_to_non_nullable - as HomePageSettings, )); } } @@ -290,8 +264,7 @@ class _$AppSettingsImpl implements _AppSettings { this.sleepTimerSettings = const SleepTimerSettings(), this.downloadSettings = const DownloadSettings(), this.notificationSettings = const NotificationSettings(), - this.shakeDetectionSettings = const ShakeDetectionSettings(), - this.homePageSettings = const HomePageSettings()}); + this.shakeDetectionSettings = const ShakeDetectionSettings()}); factory _$AppSettingsImpl.fromJson(Map json) => _$$AppSettingsImplFromJson(json); @@ -314,13 +287,10 @@ class _$AppSettingsImpl implements _AppSettings { @override @JsonKey() final ShakeDetectionSettings shakeDetectionSettings; - @override - @JsonKey() - final HomePageSettings homePageSettings; @override String toString() { - return 'AppSettings(themeSettings: $themeSettings, playerSettings: $playerSettings, sleepTimerSettings: $sleepTimerSettings, downloadSettings: $downloadSettings, notificationSettings: $notificationSettings, shakeDetectionSettings: $shakeDetectionSettings, homePageSettings: $homePageSettings)'; + return 'AppSettings(themeSettings: $themeSettings, playerSettings: $playerSettings, sleepTimerSettings: $sleepTimerSettings, downloadSettings: $downloadSettings, notificationSettings: $notificationSettings, shakeDetectionSettings: $shakeDetectionSettings)'; } @override @@ -339,9 +309,7 @@ class _$AppSettingsImpl implements _AppSettings { (identical(other.notificationSettings, notificationSettings) || other.notificationSettings == notificationSettings) && (identical(other.shakeDetectionSettings, shakeDetectionSettings) || - other.shakeDetectionSettings == shakeDetectionSettings) && - (identical(other.homePageSettings, homePageSettings) || - other.homePageSettings == homePageSettings)); + other.shakeDetectionSettings == shakeDetectionSettings)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -353,8 +321,7 @@ class _$AppSettingsImpl implements _AppSettings { sleepTimerSettings, downloadSettings, notificationSettings, - shakeDetectionSettings, - homePageSettings); + shakeDetectionSettings); /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @@ -379,8 +346,7 @@ abstract class _AppSettings implements AppSettings { final SleepTimerSettings sleepTimerSettings, final DownloadSettings downloadSettings, final NotificationSettings notificationSettings, - final ShakeDetectionSettings shakeDetectionSettings, - final HomePageSettings homePageSettings}) = _$AppSettingsImpl; + final ShakeDetectionSettings shakeDetectionSettings}) = _$AppSettingsImpl; factory _AppSettings.fromJson(Map json) = _$AppSettingsImpl.fromJson; @@ -397,8 +363,6 @@ abstract class _AppSettings implements AppSettings { NotificationSettings get notificationSettings; @override ShakeDetectionSettings get shakeDetectionSettings; - @override - HomePageSettings get homePageSettings; /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @@ -414,10 +378,7 @@ ThemeSettings _$ThemeSettingsFromJson(Map json) { /// @nodoc mixin _$ThemeSettings { - ThemeMode get themeMode => throw _privateConstructorUsedError; - bool get highContrast => throw _privateConstructorUsedError; - bool get useMaterialThemeFromSystem => throw _privateConstructorUsedError; - String get customThemeColor => throw _privateConstructorUsedError; + bool get isDarkMode => throw _privateConstructorUsedError; bool get useMaterialThemeOnItemPage => throw _privateConstructorUsedError; bool get useCurrentPlayerThemeThroughoutApp => throw _privateConstructorUsedError; @@ -439,10 +400,7 @@ abstract class $ThemeSettingsCopyWith<$Res> { _$ThemeSettingsCopyWithImpl<$Res, ThemeSettings>; @useResult $Res call( - {ThemeMode themeMode, - bool highContrast, - bool useMaterialThemeFromSystem, - String customThemeColor, + {bool isDarkMode, bool useMaterialThemeOnItemPage, bool useCurrentPlayerThemeThroughoutApp}); } @@ -462,30 +420,15 @@ class _$ThemeSettingsCopyWithImpl<$Res, $Val extends ThemeSettings> @pragma('vm:prefer-inline') @override $Res call({ - Object? themeMode = null, - Object? highContrast = null, - Object? useMaterialThemeFromSystem = null, - Object? customThemeColor = null, + Object? isDarkMode = null, Object? useMaterialThemeOnItemPage = null, Object? useCurrentPlayerThemeThroughoutApp = null, }) { return _then(_value.copyWith( - themeMode: null == themeMode - ? _value.themeMode - : themeMode // ignore: cast_nullable_to_non_nullable - as ThemeMode, - highContrast: null == highContrast - ? _value.highContrast - : highContrast // ignore: cast_nullable_to_non_nullable + isDarkMode: null == isDarkMode + ? _value.isDarkMode + : isDarkMode // ignore: cast_nullable_to_non_nullable as bool, - useMaterialThemeFromSystem: null == useMaterialThemeFromSystem - ? _value.useMaterialThemeFromSystem - : useMaterialThemeFromSystem // ignore: cast_nullable_to_non_nullable - as bool, - customThemeColor: null == customThemeColor - ? _value.customThemeColor - : customThemeColor // ignore: cast_nullable_to_non_nullable - as String, useMaterialThemeOnItemPage: null == useMaterialThemeOnItemPage ? _value.useMaterialThemeOnItemPage : useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable @@ -508,10 +451,7 @@ abstract class _$$ThemeSettingsImplCopyWith<$Res> @override @useResult $Res call( - {ThemeMode themeMode, - bool highContrast, - bool useMaterialThemeFromSystem, - String customThemeColor, + {bool isDarkMode, bool useMaterialThemeOnItemPage, bool useCurrentPlayerThemeThroughoutApp}); } @@ -529,30 +469,15 @@ class __$$ThemeSettingsImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? themeMode = null, - Object? highContrast = null, - Object? useMaterialThemeFromSystem = null, - Object? customThemeColor = null, + Object? isDarkMode = null, Object? useMaterialThemeOnItemPage = null, Object? useCurrentPlayerThemeThroughoutApp = null, }) { return _then(_$ThemeSettingsImpl( - themeMode: null == themeMode - ? _value.themeMode - : themeMode // ignore: cast_nullable_to_non_nullable - as ThemeMode, - highContrast: null == highContrast - ? _value.highContrast - : highContrast // ignore: cast_nullable_to_non_nullable + isDarkMode: null == isDarkMode + ? _value.isDarkMode + : isDarkMode // ignore: cast_nullable_to_non_nullable as bool, - useMaterialThemeFromSystem: null == useMaterialThemeFromSystem - ? _value.useMaterialThemeFromSystem - : useMaterialThemeFromSystem // ignore: cast_nullable_to_non_nullable - as bool, - customThemeColor: null == customThemeColor - ? _value.customThemeColor - : customThemeColor // ignore: cast_nullable_to_non_nullable - as String, useMaterialThemeOnItemPage: null == useMaterialThemeOnItemPage ? _value.useMaterialThemeOnItemPage : useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable @@ -570,10 +495,7 @@ class __$$ThemeSettingsImplCopyWithImpl<$Res> @JsonSerializable() class _$ThemeSettingsImpl implements _ThemeSettings { const _$ThemeSettingsImpl( - {this.themeMode = ThemeMode.system, - this.highContrast = false, - this.useMaterialThemeFromSystem = false, - this.customThemeColor = '#FF311B92', + {this.isDarkMode = true, this.useMaterialThemeOnItemPage = true, this.useCurrentPlayerThemeThroughoutApp = true}); @@ -582,16 +504,7 @@ class _$ThemeSettingsImpl implements _ThemeSettings { @override @JsonKey() - final ThemeMode themeMode; - @override - @JsonKey() - final bool highContrast; - @override - @JsonKey() - final bool useMaterialThemeFromSystem; - @override - @JsonKey() - final String customThemeColor; + final bool isDarkMode; @override @JsonKey() final bool useMaterialThemeOnItemPage; @@ -601,7 +514,7 @@ class _$ThemeSettingsImpl implements _ThemeSettings { @override String toString() { - return 'ThemeSettings(themeMode: $themeMode, highContrast: $highContrast, useMaterialThemeFromSystem: $useMaterialThemeFromSystem, customThemeColor: $customThemeColor, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage, useCurrentPlayerThemeThroughoutApp: $useCurrentPlayerThemeThroughoutApp)'; + return 'ThemeSettings(isDarkMode: $isDarkMode, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage, useCurrentPlayerThemeThroughoutApp: $useCurrentPlayerThemeThroughoutApp)'; } @override @@ -609,16 +522,8 @@ class _$ThemeSettingsImpl implements _ThemeSettings { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ThemeSettingsImpl && - (identical(other.themeMode, themeMode) || - other.themeMode == themeMode) && - (identical(other.highContrast, highContrast) || - other.highContrast == highContrast) && - (identical(other.useMaterialThemeFromSystem, - useMaterialThemeFromSystem) || - other.useMaterialThemeFromSystem == - useMaterialThemeFromSystem) && - (identical(other.customThemeColor, customThemeColor) || - other.customThemeColor == customThemeColor) && + (identical(other.isDarkMode, isDarkMode) || + other.isDarkMode == isDarkMode) && (identical(other.useMaterialThemeOnItemPage, useMaterialThemeOnItemPage) || other.useMaterialThemeOnItemPage == @@ -631,14 +536,8 @@ class _$ThemeSettingsImpl implements _ThemeSettings { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - themeMode, - highContrast, - useMaterialThemeFromSystem, - customThemeColor, - useMaterialThemeOnItemPage, - useCurrentPlayerThemeThroughoutApp); + int get hashCode => Object.hash(runtimeType, isDarkMode, + useMaterialThemeOnItemPage, useCurrentPlayerThemeThroughoutApp); /// Create a copy of ThemeSettings /// with the given fields replaced by the non-null parameter values. @@ -658,10 +557,7 @@ class _$ThemeSettingsImpl implements _ThemeSettings { abstract class _ThemeSettings implements ThemeSettings { const factory _ThemeSettings( - {final ThemeMode themeMode, - final bool highContrast, - final bool useMaterialThemeFromSystem, - final String customThemeColor, + {final bool isDarkMode, final bool useMaterialThemeOnItemPage, final bool useCurrentPlayerThemeThroughoutApp}) = _$ThemeSettingsImpl; @@ -669,13 +565,7 @@ abstract class _ThemeSettings implements ThemeSettings { _$ThemeSettingsImpl.fromJson; @override - ThemeMode get themeMode; - @override - bool get highContrast; - @override - bool get useMaterialThemeFromSystem; - @override - String get customThemeColor; + bool get isDarkMode; @override bool get useMaterialThemeOnItemPage; @override @@ -2863,248 +2753,3 @@ abstract class _ShakeDetectionSettings implements ShakeDetectionSettings { _$$ShakeDetectionSettingsImplCopyWith<_$ShakeDetectionSettingsImpl> get copyWith => throw _privateConstructorUsedError; } - -HomePageSettings _$HomePageSettingsFromJson(Map json) { - return _HomePageSettings.fromJson(json); -} - -/// @nodoc -mixin _$HomePageSettings { - bool get showPlayButtonOnContinueListeningShelf => - throw _privateConstructorUsedError; - bool get showPlayButtonOnContinueSeriesShelf => - throw _privateConstructorUsedError; - bool get showPlayButtonOnAllRemainingShelves => - throw _privateConstructorUsedError; - bool get showPlayButtonOnListenAgainShelf => - throw _privateConstructorUsedError; - - /// Serializes this HomePageSettings to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of HomePageSettings - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $HomePageSettingsCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $HomePageSettingsCopyWith<$Res> { - factory $HomePageSettingsCopyWith( - HomePageSettings value, $Res Function(HomePageSettings) then) = - _$HomePageSettingsCopyWithImpl<$Res, HomePageSettings>; - @useResult - $Res call( - {bool showPlayButtonOnContinueListeningShelf, - bool showPlayButtonOnContinueSeriesShelf, - bool showPlayButtonOnAllRemainingShelves, - bool showPlayButtonOnListenAgainShelf}); -} - -/// @nodoc -class _$HomePageSettingsCopyWithImpl<$Res, $Val extends HomePageSettings> - implements $HomePageSettingsCopyWith<$Res> { - _$HomePageSettingsCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of HomePageSettings - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? showPlayButtonOnContinueListeningShelf = null, - Object? showPlayButtonOnContinueSeriesShelf = null, - Object? showPlayButtonOnAllRemainingShelves = null, - Object? showPlayButtonOnListenAgainShelf = null, - }) { - return _then(_value.copyWith( - showPlayButtonOnContinueListeningShelf: null == - showPlayButtonOnContinueListeningShelf - ? _value.showPlayButtonOnContinueListeningShelf - : showPlayButtonOnContinueListeningShelf // ignore: cast_nullable_to_non_nullable - as bool, - showPlayButtonOnContinueSeriesShelf: null == - showPlayButtonOnContinueSeriesShelf - ? _value.showPlayButtonOnContinueSeriesShelf - : showPlayButtonOnContinueSeriesShelf // ignore: cast_nullable_to_non_nullable - as bool, - showPlayButtonOnAllRemainingShelves: null == - showPlayButtonOnAllRemainingShelves - ? _value.showPlayButtonOnAllRemainingShelves - : showPlayButtonOnAllRemainingShelves // ignore: cast_nullable_to_non_nullable - as bool, - showPlayButtonOnListenAgainShelf: null == showPlayButtonOnListenAgainShelf - ? _value.showPlayButtonOnListenAgainShelf - : showPlayButtonOnListenAgainShelf // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$HomePageSettingsImplCopyWith<$Res> - implements $HomePageSettingsCopyWith<$Res> { - factory _$$HomePageSettingsImplCopyWith(_$HomePageSettingsImpl value, - $Res Function(_$HomePageSettingsImpl) then) = - __$$HomePageSettingsImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {bool showPlayButtonOnContinueListeningShelf, - bool showPlayButtonOnContinueSeriesShelf, - bool showPlayButtonOnAllRemainingShelves, - bool showPlayButtonOnListenAgainShelf}); -} - -/// @nodoc -class __$$HomePageSettingsImplCopyWithImpl<$Res> - extends _$HomePageSettingsCopyWithImpl<$Res, _$HomePageSettingsImpl> - implements _$$HomePageSettingsImplCopyWith<$Res> { - __$$HomePageSettingsImplCopyWithImpl(_$HomePageSettingsImpl _value, - $Res Function(_$HomePageSettingsImpl) _then) - : super(_value, _then); - - /// Create a copy of HomePageSettings - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? showPlayButtonOnContinueListeningShelf = null, - Object? showPlayButtonOnContinueSeriesShelf = null, - Object? showPlayButtonOnAllRemainingShelves = null, - Object? showPlayButtonOnListenAgainShelf = null, - }) { - return _then(_$HomePageSettingsImpl( - showPlayButtonOnContinueListeningShelf: null == - showPlayButtonOnContinueListeningShelf - ? _value.showPlayButtonOnContinueListeningShelf - : showPlayButtonOnContinueListeningShelf // ignore: cast_nullable_to_non_nullable - as bool, - showPlayButtonOnContinueSeriesShelf: null == - showPlayButtonOnContinueSeriesShelf - ? _value.showPlayButtonOnContinueSeriesShelf - : showPlayButtonOnContinueSeriesShelf // ignore: cast_nullable_to_non_nullable - as bool, - showPlayButtonOnAllRemainingShelves: null == - showPlayButtonOnAllRemainingShelves - ? _value.showPlayButtonOnAllRemainingShelves - : showPlayButtonOnAllRemainingShelves // ignore: cast_nullable_to_non_nullable - as bool, - showPlayButtonOnListenAgainShelf: null == showPlayButtonOnListenAgainShelf - ? _value.showPlayButtonOnListenAgainShelf - : showPlayButtonOnListenAgainShelf // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$HomePageSettingsImpl implements _HomePageSettings { - const _$HomePageSettingsImpl( - {this.showPlayButtonOnContinueListeningShelf = true, - this.showPlayButtonOnContinueSeriesShelf = false, - this.showPlayButtonOnAllRemainingShelves = false, - this.showPlayButtonOnListenAgainShelf = false}); - - factory _$HomePageSettingsImpl.fromJson(Map json) => - _$$HomePageSettingsImplFromJson(json); - - @override - @JsonKey() - final bool showPlayButtonOnContinueListeningShelf; - @override - @JsonKey() - final bool showPlayButtonOnContinueSeriesShelf; - @override - @JsonKey() - final bool showPlayButtonOnAllRemainingShelves; - @override - @JsonKey() - final bool showPlayButtonOnListenAgainShelf; - - @override - String toString() { - return 'HomePageSettings(showPlayButtonOnContinueListeningShelf: $showPlayButtonOnContinueListeningShelf, showPlayButtonOnContinueSeriesShelf: $showPlayButtonOnContinueSeriesShelf, showPlayButtonOnAllRemainingShelves: $showPlayButtonOnAllRemainingShelves, showPlayButtonOnListenAgainShelf: $showPlayButtonOnListenAgainShelf)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$HomePageSettingsImpl && - (identical(other.showPlayButtonOnContinueListeningShelf, - showPlayButtonOnContinueListeningShelf) || - other.showPlayButtonOnContinueListeningShelf == - showPlayButtonOnContinueListeningShelf) && - (identical(other.showPlayButtonOnContinueSeriesShelf, - showPlayButtonOnContinueSeriesShelf) || - other.showPlayButtonOnContinueSeriesShelf == - showPlayButtonOnContinueSeriesShelf) && - (identical(other.showPlayButtonOnAllRemainingShelves, - showPlayButtonOnAllRemainingShelves) || - other.showPlayButtonOnAllRemainingShelves == - showPlayButtonOnAllRemainingShelves) && - (identical(other.showPlayButtonOnListenAgainShelf, - showPlayButtonOnListenAgainShelf) || - other.showPlayButtonOnListenAgainShelf == - showPlayButtonOnListenAgainShelf)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - showPlayButtonOnContinueListeningShelf, - showPlayButtonOnContinueSeriesShelf, - showPlayButtonOnAllRemainingShelves, - showPlayButtonOnListenAgainShelf); - - /// Create a copy of HomePageSettings - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$HomePageSettingsImplCopyWith<_$HomePageSettingsImpl> get copyWith => - __$$HomePageSettingsImplCopyWithImpl<_$HomePageSettingsImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$HomePageSettingsImplToJson( - this, - ); - } -} - -abstract class _HomePageSettings implements HomePageSettings { - const factory _HomePageSettings( - {final bool showPlayButtonOnContinueListeningShelf, - final bool showPlayButtonOnContinueSeriesShelf, - final bool showPlayButtonOnAllRemainingShelves, - final bool showPlayButtonOnListenAgainShelf}) = _$HomePageSettingsImpl; - - factory _HomePageSettings.fromJson(Map json) = - _$HomePageSettingsImpl.fromJson; - - @override - bool get showPlayButtonOnContinueListeningShelf; - @override - bool get showPlayButtonOnContinueSeriesShelf; - @override - bool get showPlayButtonOnAllRemainingShelves; - @override - bool get showPlayButtonOnListenAgainShelf; - - /// Create a copy of HomePageSettings - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$HomePageSettingsImplCopyWith<_$HomePageSettingsImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/settings/models/app_settings.g.dart b/lib/settings/models/app_settings.g.dart index 131458c..90b317e 100644 --- a/lib/settings/models/app_settings.g.dart +++ b/lib/settings/models/app_settings.g.dart @@ -32,10 +32,6 @@ _$AppSettingsImpl _$$AppSettingsImplFromJson(Map json) => ? const ShakeDetectionSettings() : ShakeDetectionSettings.fromJson( json['shakeDetectionSettings'] as Map), - homePageSettings: json['homePageSettings'] == null - ? const HomePageSettings() - : HomePageSettings.fromJson( - json['homePageSettings'] as Map), ); Map _$$AppSettingsImplToJson(_$AppSettingsImpl instance) => @@ -46,17 +42,11 @@ Map _$$AppSettingsImplToJson(_$AppSettingsImpl instance) => 'downloadSettings': instance.downloadSettings, 'notificationSettings': instance.notificationSettings, 'shakeDetectionSettings': instance.shakeDetectionSettings, - 'homePageSettings': instance.homePageSettings, }; _$ThemeSettingsImpl _$$ThemeSettingsImplFromJson(Map json) => _$ThemeSettingsImpl( - themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? - ThemeMode.system, - highContrast: json['highContrast'] as bool? ?? false, - useMaterialThemeFromSystem: - json['useMaterialThemeFromSystem'] as bool? ?? false, - customThemeColor: json['customThemeColor'] as String? ?? '#FF311B92', + isDarkMode: json['isDarkMode'] as bool? ?? true, useMaterialThemeOnItemPage: json['useMaterialThemeOnItemPage'] as bool? ?? true, useCurrentPlayerThemeThroughoutApp: @@ -65,21 +55,12 @@ _$ThemeSettingsImpl _$$ThemeSettingsImplFromJson(Map json) => Map _$$ThemeSettingsImplToJson(_$ThemeSettingsImpl instance) => { - 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, - 'highContrast': instance.highContrast, - 'useMaterialThemeFromSystem': instance.useMaterialThemeFromSystem, - 'customThemeColor': instance.customThemeColor, + 'isDarkMode': instance.isDarkMode, 'useMaterialThemeOnItemPage': instance.useMaterialThemeOnItemPage, 'useCurrentPlayerThemeThroughoutApp': instance.useCurrentPlayerThemeThroughoutApp, }; -const _$ThemeModeEnumMap = { - ThemeMode.system: 'system', - ThemeMode.light: 'light', - ThemeMode.dark: 'dark', -}; - _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map json) => _$PlayerSettingsImpl( miniPlayerSettings: json['miniPlayerSettings'] == null @@ -356,29 +337,3 @@ const _$ShakeDetectedFeedbackEnumMap = { ShakeDetectedFeedback.vibrate: 'vibrate', ShakeDetectedFeedback.beep: 'beep', }; - -_$HomePageSettingsImpl _$$HomePageSettingsImplFromJson( - Map json) => - _$HomePageSettingsImpl( - showPlayButtonOnContinueListeningShelf: - json['showPlayButtonOnContinueListeningShelf'] as bool? ?? true, - showPlayButtonOnContinueSeriesShelf: - json['showPlayButtonOnContinueSeriesShelf'] as bool? ?? false, - showPlayButtonOnAllRemainingShelves: - json['showPlayButtonOnAllRemainingShelves'] as bool? ?? false, - showPlayButtonOnListenAgainShelf: - json['showPlayButtonOnListenAgainShelf'] as bool? ?? false, - ); - -Map _$$HomePageSettingsImplToJson( - _$HomePageSettingsImpl instance) => - { - 'showPlayButtonOnContinueListeningShelf': - instance.showPlayButtonOnContinueListeningShelf, - 'showPlayButtonOnContinueSeriesShelf': - instance.showPlayButtonOnContinueSeriesShelf, - 'showPlayButtonOnAllRemainingShelves': - instance.showPlayButtonOnAllRemainingShelves, - 'showPlayButtonOnListenAgainShelf': - instance.showPlayButtonOnListenAgainShelf, - }; diff --git a/lib/settings/models/authenticated_user.dart b/lib/settings/models/authenticated_user.dart index 321c885..04f2bd0 100644 --- a/lib/settings/models/authenticated_user.dart +++ b/lib/settings/models/authenticated_user.dart @@ -10,8 +10,9 @@ class AuthenticatedUser with _$AuthenticatedUser { const factory AuthenticatedUser({ required AudiobookShelfServer server, required String authToken, - required String id, + String? id, String? username, + String? password, }) = _AuthenticatedUser; factory AuthenticatedUser.fromJson(Map json) => diff --git a/lib/settings/models/authenticated_user.freezed.dart b/lib/settings/models/authenticated_user.freezed.dart index 2a7ce53..e582928 100644 --- a/lib/settings/models/authenticated_user.freezed.dart +++ b/lib/settings/models/authenticated_user.freezed.dart @@ -22,8 +22,9 @@ AuthenticatedUser _$AuthenticatedUserFromJson(Map json) { mixin _$AuthenticatedUser { AudiobookShelfServer get server => throw _privateConstructorUsedError; String get authToken => throw _privateConstructorUsedError; - String get id => throw _privateConstructorUsedError; + String? get id => throw _privateConstructorUsedError; String? get username => throw _privateConstructorUsedError; + String? get password => throw _privateConstructorUsedError; /// Serializes this AuthenticatedUser to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -44,8 +45,9 @@ abstract class $AuthenticatedUserCopyWith<$Res> { $Res call( {AudiobookShelfServer server, String authToken, - String id, - String? username}); + String? id, + String? username, + String? password}); $AudiobookShelfServerCopyWith<$Res> get server; } @@ -67,8 +69,9 @@ class _$AuthenticatedUserCopyWithImpl<$Res, $Val extends AuthenticatedUser> $Res call({ Object? server = null, Object? authToken = null, - Object? id = null, + Object? id = freezed, Object? username = freezed, + Object? password = freezed, }) { return _then(_value.copyWith( server: null == server @@ -79,14 +82,18 @@ class _$AuthenticatedUserCopyWithImpl<$Res, $Val extends AuthenticatedUser> ? _value.authToken : authToken // ignore: cast_nullable_to_non_nullable as String, - id: null == id + id: freezed == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as String, + as String?, username: freezed == username ? _value.username : username // ignore: cast_nullable_to_non_nullable as String?, + password: freezed == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } @@ -112,8 +119,9 @@ abstract class _$$AuthenticatedUserImplCopyWith<$Res> $Res call( {AudiobookShelfServer server, String authToken, - String id, - String? username}); + String? id, + String? username, + String? password}); @override $AudiobookShelfServerCopyWith<$Res> get server; @@ -134,8 +142,9 @@ class __$$AuthenticatedUserImplCopyWithImpl<$Res> $Res call({ Object? server = null, Object? authToken = null, - Object? id = null, + Object? id = freezed, Object? username = freezed, + Object? password = freezed, }) { return _then(_$AuthenticatedUserImpl( server: null == server @@ -146,14 +155,18 @@ class __$$AuthenticatedUserImplCopyWithImpl<$Res> ? _value.authToken : authToken // ignore: cast_nullable_to_non_nullable as String, - id: null == id + id: freezed == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as String, + as String?, username: freezed == username ? _value.username : username // ignore: cast_nullable_to_non_nullable as String?, + password: freezed == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -164,8 +177,9 @@ class _$AuthenticatedUserImpl implements _AuthenticatedUser { const _$AuthenticatedUserImpl( {required this.server, required this.authToken, - required this.id, - this.username}); + this.id, + this.username, + this.password}); factory _$AuthenticatedUserImpl.fromJson(Map json) => _$$AuthenticatedUserImplFromJson(json); @@ -175,13 +189,15 @@ class _$AuthenticatedUserImpl implements _AuthenticatedUser { @override final String authToken; @override - final String id; + final String? id; @override final String? username; + @override + final String? password; @override String toString() { - return 'AuthenticatedUser(server: $server, authToken: $authToken, id: $id, username: $username)'; + return 'AuthenticatedUser(server: $server, authToken: $authToken, id: $id, username: $username, password: $password)'; } @override @@ -194,12 +210,15 @@ class _$AuthenticatedUserImpl implements _AuthenticatedUser { other.authToken == authToken) && (identical(other.id, id) || other.id == id) && (identical(other.username, username) || - other.username == username)); + other.username == username) && + (identical(other.password, password) || + other.password == password)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, server, authToken, id, username); + int get hashCode => + Object.hash(runtimeType, server, authToken, id, username, password); /// Create a copy of AuthenticatedUser /// with the given fields replaced by the non-null parameter values. @@ -222,8 +241,9 @@ abstract class _AuthenticatedUser implements AuthenticatedUser { const factory _AuthenticatedUser( {required final AudiobookShelfServer server, required final String authToken, - required final String id, - final String? username}) = _$AuthenticatedUserImpl; + final String? id, + final String? username, + final String? password}) = _$AuthenticatedUserImpl; factory _AuthenticatedUser.fromJson(Map json) = _$AuthenticatedUserImpl.fromJson; @@ -233,9 +253,11 @@ abstract class _AuthenticatedUser implements AuthenticatedUser { @override String get authToken; @override - String get id; + String? get id; @override String? get username; + @override + String? get password; /// Create a copy of AuthenticatedUser /// with the given fields replaced by the non-null parameter values. diff --git a/lib/settings/models/authenticated_user.g.dart b/lib/settings/models/authenticated_user.g.dart index 4ff5a06..0752807 100644 --- a/lib/settings/models/authenticated_user.g.dart +++ b/lib/settings/models/authenticated_user.g.dart @@ -12,8 +12,9 @@ _$AuthenticatedUserImpl _$$AuthenticatedUserImplFromJson( server: AudiobookShelfServer.fromJson(json['server'] as Map), authToken: json['authToken'] as String, - id: json['id'] as String, + id: json['id'] as String?, username: json['username'] as String?, + password: json['password'] as String?, ); Map _$$AuthenticatedUserImplToJson( @@ -23,4 +24,5 @@ Map _$$AuthenticatedUserImplToJson( 'authToken': instance.authToken, 'id': instance.id, 'username': instance.username, + 'password': instance.password, }; diff --git a/lib/settings/view/app_settings_page.dart b/lib/settings/view/app_settings_page.dart index 15386fe..77afaae 100644 --- a/lib/settings/view/app_settings_page.dart +++ b/lib/settings/view/app_settings_page.dart @@ -7,10 +7,11 @@ import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; +import 'package:vaani/api/authenticated_user_provider.dart'; +import 'package:vaani/api/server_provider.dart'; import 'package:vaani/router/router.dart'; import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/settings/models/app_settings.dart' as model; -import 'package:vaani/settings/view/buttons.dart'; import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/settings/view/widgets/navigation_with_switch_tile.dart'; @@ -22,11 +23,58 @@ class AppSettingsPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final appSettings = ref.watch(appSettingsProvider); + final registeredServers = ref.watch(audiobookShelfServerProvider); + final registeredServersAsList = registeredServers.toList(); + final availableUsers = ref.watch(authenticatedUserProvider); + final serverURIController = useTextEditingController(); final sleepTimerSettings = appSettings.sleepTimerSettings; return SimpleSettingsPage( title: const Text('App Settings'), sections: [ + // Appearance section + SettingsSection( + margin: const EdgeInsetsDirectional.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + title: Text( + 'Appearance', + style: Theme.of(context).textTheme.titleLarge, + ), + tiles: [ + SettingsTile.switchTile( + initialValue: appSettings.themeSettings.isDarkMode, + title: const Text('Dark Mode'), + description: const Text('we all know dark mode is better'), + leading: appSettings.themeSettings.isDarkMode + ? const Icon(Icons.dark_mode) + : const Icon(Icons.light_mode), + onToggle: (value) { + ref.read(appSettingsProvider.notifier).toggleDarkMode(); + }, + ), + SettingsTile.switchTile( + initialValue: + appSettings.themeSettings.useMaterialThemeOnItemPage, + title: const Text('Adaptive Theme on Item Page'), + description: const Text( + 'get fancy with the colors on the item page at the cost of some performance', + ), + leading: appSettings.themeSettings.useMaterialThemeOnItemPage + ? const Icon(Icons.auto_fix_high) + : const Icon(Icons.auto_fix_off), + onToggle: (value) { + ref.read(appSettingsProvider.notifier).update( + appSettings.copyWith.themeSettings( + useMaterialThemeOnItemPage: value, + ), + ); + }, + ), + ], + ), + // General section SettingsSection( margin: const EdgeInsetsDirectional.symmetric( @@ -38,16 +86,6 @@ class AppSettingsPage extends HookConsumerWidget { style: Theme.of(context).textTheme.titleLarge, ), tiles: [ - SettingsTile( - title: const Text('Player Settings'), - leading: const Icon(Icons.play_arrow), - description: const Text( - 'Customize the player settings', - ), - onPressed: (context) { - context.pushNamed(Routes.playerSettings.name); - }, - ), NavigationWithSwitchTile( title: const Text('Auto Turn On Sleep Timer'), description: const Text( @@ -68,6 +106,26 @@ class AppSettingsPage extends HookConsumerWidget { ); }, ), + SettingsTile( + title: const Text('Notification Media Player'), + leading: const Icon(Icons.play_lesson), + description: const Text( + 'Customize the media player in notifications', + ), + onPressed: (context) { + context.pushNamed(Routes.notificationSettings.name); + }, + ), + SettingsTile( + title: const Text('Player Settings'), + leading: const Icon(Icons.play_arrow), + description: const Text( + 'Customize the player settings', + ), + onPressed: (context) { + context.pushNamed(Routes.playerSettings.name); + }, + ), NavigationWithSwitchTile( title: const Text('Shake Detector'), leading: const Icon(Icons.vibration), @@ -88,51 +146,6 @@ class AppSettingsPage extends HookConsumerWidget { ), ], ), - - // Appearance section - SettingsSection( - margin: const EdgeInsetsDirectional.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - title: Text( - 'Appearance', - style: Theme.of(context).textTheme.titleLarge, - ), - tiles: [ - SettingsTile.navigation( - leading: const Icon(Icons.color_lens), - title: const Text('Theme Settings'), - description: const Text( - 'Customize the app theme', - ), - onPressed: (context) { - context.pushNamed(Routes.themeSettings.name); - }, - ), - SettingsTile( - title: const Text('Notification Media Player'), - leading: const Icon(Icons.play_lesson), - description: const Text( - 'Customize the media player in notifications', - ), - onPressed: (context) { - context.pushNamed(Routes.notificationSettings.name); - }, - ), - SettingsTile.navigation( - leading: const Icon(Icons.home_filled), - title: const Text('Home Page Settings'), - description: const Text( - 'Customize the home page', - ), - onPressed: (context) { - context.pushNamed(Routes.homePageSettings.name); - }, - ), - ], - ), - // Backup and Restore section SettingsSection( margin: const EdgeInsetsDirectional.symmetric( @@ -172,11 +185,61 @@ class AppSettingsPage extends HookConsumerWidget { 'Restore the app settings from the backup', ), onPressed: (context) { + final formKey = GlobalKey(); // show a dialog to get the backup showDialog( context: context, builder: (context) { - return RestoreDialogue(); + return AlertDialog( + title: const Text('Restore Backup'), + content: Form( + key: formKey, + child: TextFormField( + controller: serverURIController, + decoration: const InputDecoration( + labelText: 'Backup', + hintText: 'Paste the backup here', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please paste the backup here'; + } + return null; + }, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + if (formKey.currentState!.validate()) { + final backup = serverURIController.text; + final newSettings = model.AppSettings.fromJson( + // decode the backup as json + jsonDecode(backup), + ); + ref + .read(appSettingsProvider.notifier) + .update(newSettings); + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Settings restored'), + ), + ); + // clear the backup + serverURIController.clear(); + } + }, + child: const Text('Restore'), + ), + ], + ); }, ); }, @@ -229,83 +292,3 @@ class AppSettingsPage extends HookConsumerWidget { ); } } - -class RestoreDialogue extends HookConsumerWidget { - const RestoreDialogue({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final formKey = useMemoized(() => GlobalKey()); - final settings = useState(null); - - final settingsInputController = useTextEditingController(); - return AlertDialog( - title: const Text('Restore Backup'), - content: Form( - key: formKey, - child: TextFormField( - autofocus: true, - decoration: InputDecoration( - labelText: 'Backup', - hintText: 'Paste the backup here', - // clear button - suffixIcon: IconButton( - icon: Icon(Icons.clear), - onPressed: () { - settingsInputController.clear(); - }, - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please paste the backup here'; - } - try { - // try to decode the backup - settings.value = model.AppSettings.fromJson( - jsonDecode(value), - ); - } catch (e) { - return 'Invalid backup'; - } - return null; - }, - ), - ), - actions: [ - CancelButton(), - TextButton( - onPressed: () { - if (formKey.currentState!.validate()) { - if (settings.value == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid backup'), - ), - ); - return; - } - ref.read(appSettingsProvider.notifier).update(settings.value!); - settingsInputController.clear(); - Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Settings restored'), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid backup'), - ), - ); - } - }, - child: const Text('Restore'), - ), - ], - ); - } -} diff --git a/lib/settings/view/buttons.dart b/lib/settings/view/buttons.dart index 4a15b41..0dea11f 100644 --- a/lib/settings/view/buttons.dart +++ b/lib/settings/view/buttons.dart @@ -20,16 +20,12 @@ class OkButton extends StatelessWidget { class CancelButton extends StatelessWidget { const CancelButton({ super.key, - this.onPressed, }); - final void Function()? onPressed; - @override Widget build(BuildContext context) { return TextButton( onPressed: () { - onPressed?.call(); Navigator.of(context).pop(); }, child: const Text('Cancel'), diff --git a/lib/settings/view/home_page_settings_page.dart b/lib/settings/view/home_page_settings_page.dart deleted file mode 100644 index 327044d..0000000 --- a/lib/settings/view/home_page_settings_page.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:flutter_settings_ui/flutter_settings_ui.dart'; -import 'package:vaani/settings/app_settings_provider.dart'; -import 'package:vaani/settings/view/simple_settings_page.dart' - show SimpleSettingsPage; - -class HomePageSettingsPage extends HookConsumerWidget { - const HomePageSettingsPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final appSettings = ref.watch(appSettingsProvider); - final appSettingsNotifier = ref.read(appSettingsProvider.notifier); - - return SimpleSettingsPage( - title: Text('Home Page Settings'), - sections: [ - SettingsSection( - title: const Text('Quick Play'), - margin: const EdgeInsetsDirectional.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - tiles: [ - SettingsTile.switchTile( - initialValue: appSettings - .homePageSettings.showPlayButtonOnContinueListeningShelf, - title: const Text('Continue Listening'), - leading: const Icon(Icons.play_arrow), - description: const Text( - 'Show play button for books in currently listening shelf', - ), - onToggle: (value) { - appSettingsNotifier.update( - appSettings.copyWith( - homePageSettings: appSettings.homePageSettings.copyWith( - showPlayButtonOnContinueListeningShelf: value, - ), - ), - ); - }, - ), - SettingsTile.switchTile( - title: const Text('Continue Series'), - leading: const Icon(Icons.play_arrow), - description: const Text( - 'Show play button for books in continue series shelf', - ), - initialValue: appSettings - .homePageSettings.showPlayButtonOnContinueSeriesShelf, - onToggle: (value) { - appSettingsNotifier.update( - appSettings.copyWith( - homePageSettings: appSettings.homePageSettings.copyWith( - showPlayButtonOnContinueSeriesShelf: value, - ), - ), - ); - }, - ), - SettingsTile.switchTile( - title: const Text('Other shelves'), - leading: const Icon(Icons.all_inclusive), - description: const Text( - 'Show play button for all books in all remaining shelves', - ), - initialValue: appSettings - .homePageSettings.showPlayButtonOnAllRemainingShelves, - onToggle: (value) { - appSettingsNotifier.update( - appSettings.copyWith( - homePageSettings: appSettings.homePageSettings.copyWith( - showPlayButtonOnAllRemainingShelves: value, - ), - ), - ); - }, - ), - SettingsTile.switchTile( - title: const Text('Listen Again'), - leading: const Icon(Icons.replay), - description: const Text( - 'Show play button for all books in listen again shelf', - ), - initialValue: - appSettings.homePageSettings.showPlayButtonOnListenAgainShelf, - onToggle: (value) { - appSettingsNotifier.update( - appSettings.copyWith( - homePageSettings: appSettings.homePageSettings.copyWith( - showPlayButtonOnListenAgainShelf: value, - ), - ), - ); - }, - ), - ], - ), - ], - ); - } -} diff --git a/lib/settings/view/simple_settings_page.dart b/lib/settings/view/simple_settings_page.dart index ea9107d..471b022 100644 --- a/lib/settings/view/simple_settings_page.dart +++ b/lib/settings/view/simple_settings_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/features/player/view/mini_player_bottom_padding.dart'; class SimpleSettingsPage extends HookConsumerWidget { const SimpleSettingsPage({ @@ -49,7 +48,6 @@ class SimpleSettingsPage extends HookConsumerWidget { ), // some padding at the bottom const SliverPadding(padding: EdgeInsets.only(bottom: 20)), - SliverToBoxAdapter(child: MiniPlayerBottomPadding()), ], ), ); diff --git a/lib/settings/view/theme_settings_page.dart b/lib/settings/view/theme_settings_page.dart deleted file mode 100644 index 315e4fa..0000000 --- a/lib/settings/view/theme_settings_page.dart +++ /dev/null @@ -1,209 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_settings_ui/flutter_settings_ui.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/settings/app_settings_provider.dart'; -import 'package:vaani/settings/view/buttons.dart'; -import 'package:vaani/settings/view/simple_settings_page.dart'; -import 'package:vaani/shared/extensions/enum.dart'; - -class ThemeSettingsPage extends HookConsumerWidget { - const ThemeSettingsPage({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final appSettings = ref.watch(appSettingsProvider); - final themeSettings = appSettings.themeSettings; - final primaryColor = Theme.of(context).colorScheme.primary; - - return SimpleSettingsPage( - title: const Text('Theme Settings'), - sections: [ - SettingsSection( - margin: const EdgeInsetsDirectional.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - tiles: [ - // choose system , light or dark theme - SettingsTile( - title: const Text('Theme Mode'), - description: SegmentedButton( - expandedInsets: const EdgeInsets.only(top: 8.0), - showSelectedIcon: true, - selectedIcon: const Icon(Icons.check), - selected: {themeSettings.themeMode}, - onSelectionChanged: (newSelection) { - ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.themeSettings( - themeMode: newSelection.first, - ), - ); - }, - segments: [ - ButtonSegment( - value: ThemeMode.light, - icon: Icon(Icons.light_mode), - label: const Text('Light'), - ), - ButtonSegment( - value: ThemeMode.system, - icon: Icon(Icons.auto_awesome), - label: const Text('System'), - ), - ButtonSegment( - value: ThemeMode.dark, - icon: Icon(Icons.dark_mode), - label: const Text('Dark'), - ), - ], - ), - leading: Icon( - themeSettings.themeMode == ThemeMode.light - ? Icons.light_mode - : themeSettings.themeMode == ThemeMode.dark - ? Icons.dark_mode - : Icons.auto_awesome, - ), - ), - - // high contrast mode - SettingsTile.switchTile( - leading: themeSettings.highContrast - ? const Icon(Icons.accessibility) - : const Icon(Icons.accessibility_new_outlined), - initialValue: themeSettings.highContrast, - title: const Text('High Contrast Mode'), - description: const Text( - 'Increase the contrast between the background and the text', - ), - onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.themeSettings( - highContrast: value, - ), - ); - }, - ), - - // use material theme from system - SettingsTile.switchTile( - initialValue: themeSettings.useMaterialThemeFromSystem, - title: Platform.isAndroid - ? const Text('Use Material You') - : const Text('Material Theme from System'), - description: const Text( - 'Use the system theme colors for the app', - ), - leading: themeSettings.useMaterialThemeFromSystem - ? const Icon(Icons.auto_awesome) - : const Icon(Icons.auto_fix_off), - onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.themeSettings( - useMaterialThemeFromSystem: value, - ), - ); - }, - ), - - // TODO choose the primary color - // SettingsTile.navigation( - // title: const Text('Primary Color'), - // description: const Text( - // 'Choose the primary color for the app', - // ), - // leading: const Icon(Icons.colorize), - // trailing: Icon( - // Icons.circle, - // color: themeSettings.customThemeColor.toColor(), - // ), - // onPressed: (context) async { - // final selectedColor = await showDialog( - // context: context, - // builder: (context) { - // return SimpleDialog( - // title: const Text('Select Primary Color'), - // children: [ - // for (final color in Colors.primaries) - // SimpleDialogOption( - // onPressed: () { - // Navigator.pop(context, color); - // }, - // child: Container( - // color: color, - // height: 48, - // ), - // ), - // ], - // ); - // }, - // ); - // if (selectedColor != null) { - // ref.read(appSettingsProvider.notifier).update( - // appSettings.copyWith.themeSettings( - // customThemeColor: selectedColor.toHexString(), - // ), - // ); - // } - // }, - // ), - - // use theme throughout the app when playing item - SettingsTile.switchTile( - initialValue: themeSettings.useCurrentPlayerThemeThroughoutApp, - title: const Text('Adapt theme from currently playing item'), - description: const Text( - 'Use the theme colors from the currently playing item for the app', - ), - leading: themeSettings.useCurrentPlayerThemeThroughoutApp - ? const Icon(Icons.auto_fix_high) - : const Icon(Icons.auto_fix_off), - onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.themeSettings( - useCurrentPlayerThemeThroughoutApp: value, - ), - ); - }, - ), - - SettingsTile.switchTile( - initialValue: themeSettings.useMaterialThemeOnItemPage, - title: const Text('Adaptive Theme on Item Page'), - description: const Text( - 'get fancy with the colors on the item page at the cost of some performance', - ), - leading: themeSettings.useMaterialThemeOnItemPage - ? const Icon(Icons.auto_fix_high) - : const Icon(Icons.auto_fix_off), - onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.themeSettings( - useMaterialThemeOnItemPage: value, - ), - ); - }, - ), - ], - ), - ], - ); - } -} - -extension ColorExtension on Color { - String toHexString() { - return '#${value.toRadixString(16).substring(2)}'; - } -} - -extension StringExtension on String { - Color toColor() { - return Color(int.parse('0xff$substring(1)')); - } -} diff --git a/lib/settings/view/widgets/navigation_with_switch_tile.dart b/lib/settings/view/widgets/navigation_with_switch_tile.dart index 8851fa1..dbc8ecf 100644 --- a/lib/settings/view/widgets/navigation_with_switch_tile.dart +++ b/lib/settings/view/widgets/navigation_with_switch_tile.dart @@ -40,7 +40,7 @@ class NavigationWithSwitchTile extends AbstractSettingsTile { child: Row( children: [ VerticalDivider( - color: Theme.of(context).dividerColor.withValues(alpha: 0.5), + color: Theme.of(context).dividerColor.withOpacity(0.5), indent: 8.0, endIndent: 8.0, ), diff --git a/lib/shared/extensions/obfuscation.dart b/lib/shared/extensions/obfuscation.dart index 6ff85fe..c70715a 100644 --- a/lib/shared/extensions/obfuscation.dart +++ b/lib/shared/extensions/obfuscation.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; -import 'package:shelfsdk/audiobookshelf_api.dart' as shelfsdk; import 'package:vaani/settings/models/api_settings.dart'; import 'package:vaani/settings/models/audiobookshelf_server.dart'; import 'package:vaani/settings/models/authenticated_user.dart'; @@ -68,6 +67,7 @@ extension ObfuscateAuthenticatedUser on AuthenticatedUser { return this; } return copyWith( + password: password == null ? null : 'passwordObfuscated', username: username == null ? null : 'usernameObfuscated', authToken: 'authTokenObfuscated', server: server.obfuscate(), @@ -116,54 +116,10 @@ extension ObfuscateResponse on http.Response { return this; } return http.Response( - obfuscateBody(), + body, statusCode, headers: headers, request: request?.obfuscate(), ); } - - String obfuscateBody() { - if (!kReleaseMode) { - return body; - } - // replace any email addresses with emailObfuscated - // replace any phone numbers with phoneObfuscated - // replace any urls with urlObfuscated - // replace any tokens with tokenObfuscated - // token regex is `"token": "..."` - return body - .replaceAll( - RegExp(r'(\b\w+@\w+\.\w+\b)|' - r'(\b\d{3}-\d{3}-\d{4}\b)|' - r'(\bhttps?://\S+\b)'), - 'obfuscated', - ) - .replaceAll( - RegExp(r'"?token"?:?\s*"[^"]+"'), - '"token": "tokenObfuscated"', - ); - } -} - -extension ObfuscateLoginResponse on shelfsdk.LoginResponse { - shelfsdk.LoginResponse obfuscate() { - if (!kReleaseMode) { - return this; - } - return copyWith( - user: user.obfuscate(), - ); - } -} - -extension ObfuscateUser on shelfsdk.User { - shelfsdk.User obfuscate() { - if (!kReleaseMode) { - return this; - } - return shelfsdk.User.fromJson( - toJson()..['token'] = 'tokenObfuscated', - ); - } } diff --git a/lib/shared/hooks.dart b/lib/shared/hooks.dart index 8e27b3a..62c6116 100644 --- a/lib/shared/hooks.dart +++ b/lib/shared/hooks.dart @@ -28,3 +28,64 @@ void useTimer(VoidCallback callback, Duration delay) { [delay], ); } + +/// Creates [FixedExtentScrollController] that will be disposed automatically. +/// +/// See also: +/// - [FixedExtentScrollController] +FixedExtentScrollController useFixedExtentScrollController({ + String? debugLabel, + List? keys, + int initialItem = 0, + void Function(ScrollPosition)? onAttach, + void Function(ScrollPosition)? onDetach, +}) { + return use( + _FixedExtentScrollControllerHook( + debugLabel: debugLabel, + keys: keys, + initialItem: initialItem, + onAttach: onAttach, + onDetach: onDetach, + ), + ); +} + +class _FixedExtentScrollControllerHook + extends Hook { + const _FixedExtentScrollControllerHook({ + this.debugLabel, + super.keys, + required this.initialItem, + this.onAttach, + this.onDetach, + }); + + final int initialItem; + final void Function(ScrollPosition)? onAttach; + final void Function(ScrollPosition)? onDetach; + + final String? debugLabel; + + @override + HookState> + createState() => _FixedExtentScrollControllerHookState(); +} + +class _FixedExtentScrollControllerHookState extends HookState< + FixedExtentScrollController, _FixedExtentScrollControllerHook> { + late final controller = FixedExtentScrollController( + initialItem: hook.initialItem, + onAttach: hook.onAttach, + onDetach: hook.onDetach, + ); + + @override + FixedExtentScrollController build(BuildContext context) => controller; + + @override + void dispose() => controller.dispose(); + + @override + String get debugLabel => 'useFixedExtentScrollController'; +} diff --git a/lib/shared/icons/abs_icons.dart b/lib/shared/icons/abs_icons.dart deleted file mode 100644 index eca3e1a..0000000 --- a/lib/shared/icons/abs_icons.dart +++ /dev/null @@ -1,103 +0,0 @@ -/// Flutter icons AbsIcons -/// Copyright (C) 2025 by original authors @ fluttericon.com, fontello.com -/// This font was generated by FlutterIcon.com, which is derived from Fontello. -/// -/// To use this font, place it in your fonts/ directory and include the -/// following in your pubspec.yaml -/// -/// flutter: -/// fonts: -/// - family: AbsIcons -/// fonts: -/// - asset: fonts/AbsIcons.ttf -/// -/// -/// -library; -// ignore_for_file: constant_identifier_names - -import 'package:flutter/widgets.dart' show IconData; - -class AbsIcons { - AbsIcons._(); - - static const _kFontFam = 'AbsIcons'; - static const String? _kFontPkg = null; - - static const IconData audiobookshelf = - IconData(0xe900, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData microphone_2 = - IconData(0xe901, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData microphone_1 = - IconData(0xe902, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData radio = - IconData(0xe903, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData podcast = - IconData(0xe904, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData books_1 = - IconData(0xe905, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData database_2 = - IconData(0xe906, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData headphones = - IconData(0xe910, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData music = - IconData(0xe911, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData video = - IconData(0xe914, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData microphone_3 = - IconData(0xe91e, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData book = - IconData(0xe91f, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData books_2 = - IconData(0xe920, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData file_picture = - IconData(0xe927, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData database_1 = - IconData(0xe964, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData rocket = - IconData(0xe9a5, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData power = - IconData(0xe9b5, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData star = - IconData(0xe9d9, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData heart = - IconData(0xe9da, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData rss = - IconData(0xea9b, fontFamily: _kFontFam, fontPackage: _kFontPkg); - - static final Map _iconMap = { - 'audiobookshelf': audiobookshelf, - 'microphone_2': microphone_2, - 'microphone_1': microphone_1, - 'radio': radio, - 'podcast': podcast, - 'books_1': books_1, - 'database_2': database_2, - 'headphones': headphones, - 'music': music, - 'video': video, - 'microphone_3': microphone_3, - 'book': book, - 'books_2': books_2, - 'file_picture': file_picture, - 'database_1': database_1, - 'rocket': rocket, - 'power': power, - 'star': star, - 'heart': heart, - 'rss': rss, - }; - - /// Returns the IconData corresponding to the [iconName] string. - /// - /// If the [iconName] is not found in the map, returns null. - /// Considers null or empty strings as invalid. - static IconData? getIconByName(String? iconName) { - if (iconName == null || iconName.isEmpty) { - return null; - } - return _iconMap[iconName.toLowerCase()]; - } - - static Map get iconMap => _iconMap; -} diff --git a/lib/shared/utils.dart b/lib/shared/utils.dart index 3d600b4..6fce1a0 100644 --- a/lib/shared/utils.dart +++ b/lib/shared/utils.dart @@ -1,3 +1,4 @@ + import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/shared/widgets/add_new_server.dart b/lib/shared/widgets/add_new_server.dart index 8c31fc1..c04d8fd 100644 --- a/lib/shared/widgets/add_new_server.dart +++ b/lib/shared/widgets/add_new_server.dart @@ -2,9 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/api/api_provider.dart'; -import 'package:vaani/main.dart'; - -final httpUrlRegExp = RegExp('https?://'); class AddNewServer extends HookConsumerWidget { const AddNewServer({ @@ -28,8 +25,7 @@ class AddNewServer extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final myController = - controller ?? useTextEditingController(text: 'https://'); + final myController = controller ?? useTextEditingController(); var newServerURI = useValueListenable(myController); final isServerAlive = ref.watch(isServerAliveProvider(newServerURI.text)); bool isServerAliveValue = isServerAlive.when( @@ -38,42 +34,21 @@ class AddNewServer extends HookConsumerWidget { error: (error, _) => false, ); - Uri parsedUri = Uri.parse(''); - - try { - parsedUri = Uri.parse(newServerURI.text); - } on FormatException { - // prepend https:// if not present - if (!newServerURI.text.startsWith(httpUrlRegExp)) { - myController.text = 'https://${newServerURI.text}'; - parsedUri = Uri.parse(myController.text); - } - } catch (e) { - // do nothing - appLogger.severe('Error parsing URI: $e'); - } - final canSubmit = !readOnly && - (isServerAliveValue || (allowEmpty && newServerURI.text.isEmpty)); return TextFormField( readOnly: readOnly, controller: controller, keyboardType: TextInputType.url, autofillHints: const [AutofillHints.url], - textInputAction: TextInputAction.done, - onFieldSubmitted: canSubmit - ? (_) { - onPressed?.call(); - } - : null, + textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Server URI', labelStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.8), + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8), ), border: const OutlineInputBorder(), prefixText: - myController.text.startsWith(httpUrlRegExp) ? '' : 'https://', - prefixIcon: ServerAliveIcon(server: parsedUri), + myController.text.startsWith(RegExp('https?://')) ? '' : 'https://', + prefixIcon: ServerAliveIcon(server: Uri.parse(newServerURI.text)), // add server button suffixIcon: onPressed == null @@ -87,10 +62,10 @@ class AddNewServer extends HookConsumerWidget { focusColor: Theme.of(context).colorScheme.onSurface, // should be enabled when - onPressed: canSubmit - ? () { - onPressed?.call(); - } + onPressed: !readOnly && + (isServerAliveValue || + (allowEmpty && newServerURI.text.isEmpty)) + ? onPressed : null, // disable button if server is not alive ), ), diff --git a/lib/shared/widgets/drawer.dart b/lib/shared/widgets/drawer.dart index 12aa1c2..be224e9 100644 --- a/lib/shared/widgets/drawer.dart +++ b/lib/shared/widgets/drawer.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:vaani/features/you/view/server_manager.dart'; import 'package:vaani/router/router.dart'; + class MyDrawer extends StatelessWidget { const MyDrawer({ super.key, diff --git a/lib/shared/widgets/expandable_description.dart b/lib/shared/widgets/expandable_description.dart index 203c7b0..06605e4 100644 --- a/lib/shared/widgets/expandable_description.dart +++ b/lib/shared/widgets/expandable_description.dart @@ -39,7 +39,7 @@ class ExpandableDescription extends HookWidget { // header with carrot icon is tapable InkWell( borderRadius: BorderRadius.circular(8), - + onTap: () { isDescExpanded.value = !isDescExpanded.value; if (isDescExpanded.value) { diff --git a/lib/shared/widgets/shelves/book_shelf.dart b/lib/shared/widgets/shelves/book_shelf.dart index 4fad4be..692d3b3 100644 --- a/lib/shared/widgets/shelves/book_shelf.dart +++ b/lib/shared/widgets/shelves/book_shelf.dart @@ -17,7 +17,7 @@ import 'package:vaani/router/router.dart'; import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/widgets/shelves/home_shelf.dart'; -import 'package:vaani/theme/providers/theme_from_cover_provider.dart'; +import 'package:vaani/theme/theme_from_cover_provider.dart'; /// A shelf that displays books on the home page class BookHomeShelf extends HookConsumerWidget { @@ -25,12 +25,10 @@ class BookHomeShelf extends HookConsumerWidget { super.key, required this.shelf, required this.title, - this.showPlayButton = false, }); final String title; final LibraryItemShelf shelf; - final bool showPlayButton; @override Widget build(BuildContext context, WidgetRef ref) { @@ -43,7 +41,6 @@ class BookHomeShelf extends HookConsumerWidget { item: item, key: ValueKey(shelf.id + item.id), heroTagSuffix: shelf.id, - showPlayButton: showPlayButton, ), _ => Container(), }, @@ -117,41 +114,38 @@ class BookOnShelf extends HookConsumerWidget { heroTagSuffix, child: ClipRRect( borderRadius: BorderRadius.circular(10), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: coverImage.when( - data: (image) { - // return const BookCoverSkeleton(); - if (image.isEmpty) { - return const Icon(Icons.error); - } - var imageWidget = Image.memory( - image, - fit: BoxFit.fill, - cacheWidth: (height * - 1.2 * - MediaQuery.of(context) - .devicePixelRatio) - .round(), - ); - return Container( - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, - ), - child: imageWidget, - ); - }, - loading: () { - return const Center( - child: BookCoverSkeleton(), - ); - }, - error: (error, stack) { + child: coverImage.when( + data: (image) { + // return const BookCoverSkeleton(); + if (image.isEmpty) { return const Icon(Icons.error); - }, - ), + } + var imageWidget = Image.memory( + image, + fit: BoxFit.fill, + cacheWidth: (height * + 1.2 * + MediaQuery.of(context) + .devicePixelRatio) + .round(), + ); + return Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + ), + child: imageWidget, + ); + }, + loading: () { + return const Center( + child: BookCoverSkeleton(), + ); + }, + error: (error, stack) { + return const Icon(Icons.error); + }, ), ), ), @@ -203,6 +197,7 @@ class BookOnShelf extends HookConsumerWidget { class _BookOnShelfPlayButton extends HookConsumerWidget { const _BookOnShelfPlayButton({ + super.key, required this.libraryItemId, }); @@ -258,10 +253,8 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { child: CircularProgressIndicator( value: userProgress.progress, strokeWidth: strokeWidth, - backgroundColor: Theme.of(context) - .colorScheme - .onPrimary - .withValues(alpha: 0.8), + backgroundColor: + Theme.of(context).colorScheme.onPrimary.withOpacity(0.8), valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.primary, ), @@ -279,10 +272,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { const Size(size, size), ), backgroundColor: WidgetStateProperty.all( - Theme.of(context) - .colorScheme - .onPrimary - .withValues(alpha: 0.9), + Theme.of(context).colorScheme.onPrimary.withOpacity(0.9), ), ), onPressed: () async { @@ -324,10 +314,9 @@ class BookCoverSkeleton extends StatelessWidget { child: SizedBox( width: 150, child: Shimmer.fromColors( - baseColor: - Theme.of(context).colorScheme.surface.withValues(alpha: 0.3), + baseColor: Theme.of(context).colorScheme.surface.withOpacity(0.3), highlightColor: - Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.1), + Theme.of(context).colorScheme.onSurface.withOpacity(0.1), child: Container( color: Theme.of(context).colorScheme.surface, ), diff --git a/lib/shared/widgets/shelves/home_shelf.dart b/lib/shared/widgets/shelves/home_shelf.dart index 9a59a54..ed22f8d 100644 --- a/lib/shared/widgets/shelves/home_shelf.dart +++ b/lib/shared/widgets/shelves/home_shelf.dart @@ -15,12 +15,10 @@ class HomeShelf extends HookConsumerWidget { super.key, required this.shelf, required this.title, - this.showPlayButton = false, }); final String title; final Shelf shelf; - final bool showPlayButton; @override Widget build(BuildContext context, WidgetRef ref) { @@ -28,7 +26,6 @@ class HomeShelf extends HookConsumerWidget { ShelfType.book => BookHomeShelf( title: title, shelf: shelf.asLibraryItemShelf, - showPlayButton: showPlayButton, ), ShelfType.authors => AuthorHomeShelf( title: title, diff --git a/lib/shared/widgets/vaani_logo.dart b/lib/shared/widgets/vaani_logo.dart deleted file mode 100644 index a5b4e11..0000000 --- a/lib/shared/widgets/vaani_logo.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; - -class VaaniLogo extends StatelessWidget { - const VaaniLogo({ - super.key, - this.size, - this.duration = const Duration(milliseconds: 750), - this.curve = Curves.fastOutSlowIn, - }); - - final double? size; - final Duration duration; - final Curve curve; - - @override - Widget build(BuildContext context) { - final IconThemeData iconTheme = IconTheme.of(context); - final double? iconSize = size ?? iconTheme.size; - return AnimatedContainer( - width: iconSize, - height: iconSize, - duration: duration, - curve: curve, - child: Image.asset('assets/images/vaani_logo_foreground.png'), - ); - } -} diff --git a/lib/theme/dark.dart b/lib/theme/dark.dart new file mode 100644 index 0000000..5473632 --- /dev/null +++ b/lib/theme/dark.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; +import 'package:vaani/theme/theme.dart'; + +final ThemeData darkTheme = ThemeData( + brightness: Brightness.dark, + colorScheme: ColorScheme.fromSeed( + seedColor: brandColor, + brightness: Brightness.dark, + ), +); diff --git a/lib/theme/light.dart b/lib/theme/light.dart new file mode 100644 index 0000000..aa6ba4d --- /dev/null +++ b/lib/theme/light.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; +import 'package:vaani/theme/theme.dart'; + +final ThemeData lightTheme = ThemeData( + brightness: Brightness.light, + colorScheme: ColorScheme.fromSeed( + seedColor: brandColor, + brightness: Brightness.light, + ), +); diff --git a/lib/theme/providers/system_theme_provider.dart b/lib/theme/providers/system_theme_provider.dart deleted file mode 100644 index 529306e..0000000 --- a/lib/theme/providers/system_theme_provider.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:dynamic_color/dynamic_color.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:logging/logging.dart'; -import 'package:material_color_utilities/material_color_utilities.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'system_theme_provider.g.dart'; - -final _logger = Logger('SystemThemeProvider'); - -/// copied from [DynamicColorBuilder] -@Riverpod(keepAlive: true) -FutureOr<(ColorScheme light, ColorScheme dark)?> systemTheme( - Ref ref, { - bool highContrast = false, -}) async { - _logger.fine('Generating system theme'); - ColorScheme? schemeLight; - ColorScheme? schemeDark; - // Platform messages may fail, so we use a try/catch PlatformException. - try { - CorePalette? corePalette = await DynamicColorPlugin.getCorePalette(); - - if (corePalette != null) { - _logger.fine('dynamic_color: Core palette detected.'); - schemeLight = corePalette.toColorScheme(brightness: Brightness.light); - schemeDark = corePalette.toColorScheme(brightness: Brightness.dark); - } - } on PlatformException { - _logger.warning('dynamic_color: Failed to obtain core palette.'); - } - - if (schemeLight == null || schemeDark == null) { - try { - final Color? accentColor = await DynamicColorPlugin.getAccentColor(); - - if (accentColor != null) { - _logger.fine('dynamic_color: Accent color detected.'); - schemeLight = ColorScheme.fromSeed( - seedColor: accentColor, - brightness: Brightness.light, - ); - schemeDark = ColorScheme.fromSeed( - seedColor: accentColor, - brightness: Brightness.dark, - ); - } - } on PlatformException { - _logger.warning('dynamic_color: Failed to obtain accent color.'); - } - } - - if (schemeLight == null || schemeDark == null) { - _logger - .warning('dynamic_color: Dynamic color not detected on this device.'); - return null; - } - // set high contrast theme - if (highContrast) { - schemeLight = schemeLight - .copyWith( - surface: Colors.white, - ) - .harmonized(); - schemeDark = schemeDark - .copyWith( - surface: Colors.black, - ) - .harmonized(); - } - return (schemeLight, schemeDark); -} diff --git a/lib/theme/providers/system_theme_provider.g.dart b/lib/theme/providers/system_theme_provider.g.dart deleted file mode 100644 index 5685c95..0000000 --- a/lib/theme/providers/system_theme_provider.g.dart +++ /dev/null @@ -1,179 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'system_theme_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$systemThemeHash() => r'c78d3d94683624a80b296594268c5fd4295e77a3'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -/// copied from [DynamicColorBuilder] -/// -/// Copied from [systemTheme]. -@ProviderFor(systemTheme) -const systemThemeProvider = SystemThemeFamily(); - -/// copied from [DynamicColorBuilder] -/// -/// Copied from [systemTheme]. -class SystemThemeFamily - extends Family> { - /// copied from [DynamicColorBuilder] - /// - /// Copied from [systemTheme]. - const SystemThemeFamily(); - - /// copied from [DynamicColorBuilder] - /// - /// Copied from [systemTheme]. - SystemThemeProvider call({ - bool highContrast = false, - }) { - return SystemThemeProvider( - highContrast: highContrast, - ); - } - - @override - SystemThemeProvider getProviderOverride( - covariant SystemThemeProvider provider, - ) { - return call( - highContrast: provider.highContrast, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'systemThemeProvider'; -} - -/// copied from [DynamicColorBuilder] -/// -/// Copied from [systemTheme]. -class SystemThemeProvider - extends FutureProvider<(ColorScheme light, ColorScheme dark)?> { - /// copied from [DynamicColorBuilder] - /// - /// Copied from [systemTheme]. - SystemThemeProvider({ - bool highContrast = false, - }) : this._internal( - (ref) => systemTheme( - ref as SystemThemeRef, - highContrast: highContrast, - ), - from: systemThemeProvider, - name: r'systemThemeProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$systemThemeHash, - dependencies: SystemThemeFamily._dependencies, - allTransitiveDependencies: - SystemThemeFamily._allTransitiveDependencies, - highContrast: highContrast, - ); - - SystemThemeProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.highContrast, - }) : super.internal(); - - final bool highContrast; - - @override - Override overrideWith( - FutureOr<(ColorScheme light, ColorScheme dark)?> Function( - SystemThemeRef provider) - create, - ) { - return ProviderOverride( - origin: this, - override: SystemThemeProvider._internal( - (ref) => create(ref as SystemThemeRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - highContrast: highContrast, - ), - ); - } - - @override - FutureProviderElement<(ColorScheme light, ColorScheme dark)?> - createElement() { - return _SystemThemeProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is SystemThemeProvider && other.highContrast == highContrast; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, highContrast.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin SystemThemeRef - on FutureProviderRef<(ColorScheme light, ColorScheme dark)?> { - /// The parameter `highContrast` of this provider. - bool get highContrast; -} - -class _SystemThemeProviderElement - extends FutureProviderElement<(ColorScheme light, ColorScheme dark)?> - with SystemThemeRef { - _SystemThemeProviderElement(super.provider); - - @override - bool get highContrast => (origin as SystemThemeProvider).highContrast; -} -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 35f4ad8..72e81b1 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -1,15 +1,9 @@ -import 'package:flutter/material.dart'; +import 'dart:ui'; + +export 'dark.dart'; +export 'light.dart'; + // brand color rgb(49, 27, 146) rgb(96, 76, 236) const brandColor = Color(0xFF311B92); const brandColorLight = Color(0xFF604CEC); - -final brandLightColorScheme = ColorScheme.fromSeed( - seedColor: brandColor, - brightness: Brightness.light, -); - -final brandDarkColorScheme = ColorScheme.fromSeed( - seedColor: brandColor, - brightness: Brightness.dark, -); diff --git a/lib/theme/providers/theme_from_cover_provider.dart b/lib/theme/theme_from_cover_provider.dart similarity index 78% rename from lib/theme/providers/theme_from_cover_provider.dart rename to lib/theme/theme_from_cover_provider.dart index 3e9a667..a535f2f 100644 --- a/lib/theme/providers/theme_from_cover_provider.dart +++ b/lib/theme/theme_from_cover_provider.dart @@ -1,7 +1,5 @@ -import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:vaani/api/image_provider.dart'; @@ -12,28 +10,18 @@ final _logger = Logger('ThemeFromCoverProvider'); @Riverpod(keepAlive: true) Future> themeFromCover( - Ref ref, + ThemeFromCoverRef ref, ImageProvider img, { Brightness brightness = Brightness.dark, - bool highContrast = false, }) async { // ! add deliberate delay to simulate a long running task as it interferes with other animations await Future.delayed(500.ms); _logger.fine('Generating color scheme from cover image'); - var theme = await ColorScheme.fromImageProvider( + return ColorScheme.fromImageProvider( provider: img, brightness: brightness, ); - // set high contrast theme - if (highContrast) { - theme = theme - .copyWith( - surface: brightness == Brightness.light ? Colors.white : Colors.black, - ) - .harmonized(); - } - return theme; // TODO isolate is not working // see https://github.com/flutter/flutter/issues/119207 // use isolate to generate the color scheme @@ -59,21 +47,17 @@ Future> themeFromCover( @Riverpod(keepAlive: true) FutureOr themeOfLibraryItem( - Ref ref, + ThemeOfLibraryItemRef ref, String? itemId, { Brightness brightness = Brightness.dark, - bool highContrast = false, }) async { if (itemId == null) { return null; } final coverImage = await ref.watch(coverImageProvider(itemId).future); final val = await ref.watch( - themeFromCoverProvider( - MemoryImage(coverImage), - brightness: brightness, - highContrast: highContrast, - ).future, + themeFromCoverProvider(MemoryImage(coverImage), brightness: brightness) + .future, ); return val; // coverImage.when( diff --git a/lib/theme/providers/theme_from_cover_provider.g.dart b/lib/theme/theme_from_cover_provider.g.dart similarity index 84% rename from lib/theme/providers/theme_from_cover_provider.g.dart rename to lib/theme/theme_from_cover_provider.g.dart index 1289765..61a86a9 100644 --- a/lib/theme/providers/theme_from_cover_provider.g.dart +++ b/lib/theme/theme_from_cover_provider.g.dart @@ -6,7 +6,7 @@ part of 'theme_from_cover_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$themeFromCoverHash() => r'afdeddc4bfe2fe46a4185143d3a88a23565e33f4'; +String _$themeFromCoverHash() => r'a549513a0dcdff76be94488baf38a8b886ce63eb'; /// Copied from Dart SDK class _SystemHash { @@ -42,12 +42,10 @@ class ThemeFromCoverFamily extends Family>> { ThemeFromCoverProvider call( ImageProvider img, { Brightness brightness = Brightness.dark, - bool highContrast = false, }) { return ThemeFromCoverProvider( img, brightness: brightness, - highContrast: highContrast, ); } @@ -58,7 +56,6 @@ class ThemeFromCoverFamily extends Family>> { return call( provider.img, brightness: provider.brightness, - highContrast: provider.highContrast, ); } @@ -83,13 +80,11 @@ class ThemeFromCoverProvider extends FutureProvider> { ThemeFromCoverProvider( ImageProvider img, { Brightness brightness = Brightness.dark, - bool highContrast = false, }) : this._internal( (ref) => themeFromCover( ref as ThemeFromCoverRef, img, brightness: brightness, - highContrast: highContrast, ), from: themeFromCoverProvider, name: r'themeFromCoverProvider', @@ -102,7 +97,6 @@ class ThemeFromCoverProvider extends FutureProvider> { ThemeFromCoverFamily._allTransitiveDependencies, img: img, brightness: brightness, - highContrast: highContrast, ); ThemeFromCoverProvider._internal( @@ -114,12 +108,10 @@ class ThemeFromCoverProvider extends FutureProvider> { required super.from, required this.img, required this.brightness, - required this.highContrast, }) : super.internal(); final ImageProvider img; final Brightness brightness; - final bool highContrast; @override Override overrideWith( @@ -137,7 +129,6 @@ class ThemeFromCoverProvider extends FutureProvider> { debugGetCreateSourceHash: null, img: img, brightness: brightness, - highContrast: highContrast, ), ); } @@ -151,8 +142,7 @@ class ThemeFromCoverProvider extends FutureProvider> { bool operator ==(Object other) { return other is ThemeFromCoverProvider && other.img == img && - other.brightness == brightness && - other.highContrast == highContrast; + other.brightness == brightness; } @override @@ -160,23 +150,17 @@ class ThemeFromCoverProvider extends FutureProvider> { var hash = _SystemHash.combine(0, runtimeType.hashCode); hash = _SystemHash.combine(hash, img.hashCode); hash = _SystemHash.combine(hash, brightness.hashCode); - hash = _SystemHash.combine(hash, highContrast.hashCode); return _SystemHash.finish(hash); } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin ThemeFromCoverRef on FutureProviderRef> { /// The parameter `img` of this provider. ImageProvider get img; /// The parameter `brightness` of this provider. Brightness get brightness; - - /// The parameter `highContrast` of this provider. - bool get highContrast; } class _ThemeFromCoverProviderElement @@ -188,12 +172,10 @@ class _ThemeFromCoverProviderElement ImageProvider get img => (origin as ThemeFromCoverProvider).img; @override Brightness get brightness => (origin as ThemeFromCoverProvider).brightness; - @override - bool get highContrast => (origin as ThemeFromCoverProvider).highContrast; } String _$themeOfLibraryItemHash() => - r'0b2df397b2938003a9de6beb6d4204401a05370c'; + r'a1d0e5d81f4debe88d5a6ce46c3af28623ad4273'; /// See also [themeOfLibraryItem]. @ProviderFor(themeOfLibraryItem) @@ -208,12 +190,10 @@ class ThemeOfLibraryItemFamily extends Family> { ThemeOfLibraryItemProvider call( String? itemId, { Brightness brightness = Brightness.dark, - bool highContrast = false, }) { return ThemeOfLibraryItemProvider( itemId, brightness: brightness, - highContrast: highContrast, ); } @@ -224,7 +204,6 @@ class ThemeOfLibraryItemFamily extends Family> { return call( provider.itemId, brightness: provider.brightness, - highContrast: provider.highContrast, ); } @@ -249,13 +228,11 @@ class ThemeOfLibraryItemProvider extends FutureProvider { ThemeOfLibraryItemProvider( String? itemId, { Brightness brightness = Brightness.dark, - bool highContrast = false, }) : this._internal( (ref) => themeOfLibraryItem( ref as ThemeOfLibraryItemRef, itemId, brightness: brightness, - highContrast: highContrast, ), from: themeOfLibraryItemProvider, name: r'themeOfLibraryItemProvider', @@ -268,7 +245,6 @@ class ThemeOfLibraryItemProvider extends FutureProvider { ThemeOfLibraryItemFamily._allTransitiveDependencies, itemId: itemId, brightness: brightness, - highContrast: highContrast, ); ThemeOfLibraryItemProvider._internal( @@ -280,12 +256,10 @@ class ThemeOfLibraryItemProvider extends FutureProvider { required super.from, required this.itemId, required this.brightness, - required this.highContrast, }) : super.internal(); final String? itemId; final Brightness brightness; - final bool highContrast; @override Override overrideWith( @@ -302,7 +276,6 @@ class ThemeOfLibraryItemProvider extends FutureProvider { debugGetCreateSourceHash: null, itemId: itemId, brightness: brightness, - highContrast: highContrast, ), ); } @@ -316,8 +289,7 @@ class ThemeOfLibraryItemProvider extends FutureProvider { bool operator ==(Object other) { return other is ThemeOfLibraryItemProvider && other.itemId == itemId && - other.brightness == brightness && - other.highContrast == highContrast; + other.brightness == brightness; } @override @@ -325,23 +297,17 @@ class ThemeOfLibraryItemProvider extends FutureProvider { var hash = _SystemHash.combine(0, runtimeType.hashCode); hash = _SystemHash.combine(hash, itemId.hashCode); hash = _SystemHash.combine(hash, brightness.hashCode); - hash = _SystemHash.combine(hash, highContrast.hashCode); return _SystemHash.finish(hash); } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element mixin ThemeOfLibraryItemRef on FutureProviderRef { /// The parameter `itemId` of this provider. String? get itemId; /// The parameter `brightness` of this provider. Brightness get brightness; - - /// The parameter `highContrast` of this provider. - bool get highContrast; } class _ThemeOfLibraryItemProviderElement @@ -353,8 +319,6 @@ class _ThemeOfLibraryItemProviderElement @override Brightness get brightness => (origin as ThemeOfLibraryItemProvider).brightness; - @override - bool get highContrast => (origin as ThemeOfLibraryItemProvider).highContrast; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 606c5a6..879195f 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,15 +6,11 @@ #include "generated_plugin_registrant.h" -#include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) dynamic_color_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); - dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 6023074..026cbff 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - dynamic_color isar_flutter_libs media_kit_libs_linux url_launcher_linux diff --git a/linux/my_application.cc b/linux/my_application.cc index 4c7da0c..8a52c8b 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -17,14 +17,6 @@ G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); - - GList *windows = gtk_application_get_windows(GTK_APPLICATION(application)); - if (windows) - { - gtk_window_present(GTK_WINDOW(windows->data)); - return; - } - GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); @@ -48,11 +40,11 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "Vaani"); + gtk_header_bar_set_title(header_bar, "vaani"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "Vaani"); + gtk_window_set_title(window, "vaani"); } gtk_window_set_default_size(window, 1280, 720); @@ -86,7 +78,7 @@ static gboolean my_application_local_command_line(GApplication* application, gch g_application_activate(application); *exit_status = 0; - return FALSE; + return TRUE; } // Implements GApplication::startup. @@ -127,6 +119,6 @@ static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, - "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN, + "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } diff --git a/linux/packaging/appimage/make_config.yaml b/linux/packaging/appimage/make_config.yaml deleted file mode 100644 index b1183ae..0000000 --- a/linux/packaging/appimage/make_config.yaml +++ /dev/null @@ -1,34 +0,0 @@ -display_name: Vaani -package_name: vaani - -maintainer: - name: Dr.Blank - email: drblankdev@gmail.com - -priority: optional - -section: x11 - -installed_size: 75700 - -essential: false - -icon: assets/icon/logo.png - -postuninstall_scripts: - - echo "Sorry to see you go." - -keywords: - - Audiobook - - Audiobook Player - - Audiobookshelf - -generic_name: Audiobook Player - -categories: - - AudioVideo - - Audio - - Player - -startup_notify: true -# TODO: Review and update fields for AppImage specifics (e.g., icon, metadata). diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml deleted file mode 100644 index 5579be4..0000000 --- a/linux/packaging/deb/make_config.yaml +++ /dev/null @@ -1,51 +0,0 @@ -display_name: Vaani -package_name: vaani - -maintainer: - name: Dr.Blank - email: drblankdev@gmail.com - -priority: optional - -section: x11 - -installed_size: 75700 - -essential: false - -icon: assets/icon/logo.png - -description: - short: Beautiful, Fast and Functional Audiobook Player for your Audiobookshelf server. - long: | - Vaani is a client for your (self-hosted) Audiobookshelf server. - - Features: - - Functional Player: Speed Control, Sleep Timer, Shake to Control Player - - Save data with Offline listening and caching - - Material Design - - Extensive Settings to customize every tiny detail - - Note: you need an Audiobookshelf server setup for this app to work. - Please see https://www.audiobookshelf.org/ on how to setup one if not already. - -postuninstall_scripts: - - echo "Sorry to see you go." - -keywords: - - Audiobook - - Audiobook Player - - Audiobookshelf - -generic_name: Audiobook Player - -categories: - - AudioVideo - - Audio - - Player - -startup_notify: true - -# https://github.com/llfbandit/app_links/blob/051f53fa6039cbfaef0fcde73df20fef9e248cab/doc/README_linux.md -supported_mime_type: - - x-scheme-handler/vaani diff --git a/pubspec.lock b/pubspec.lock index 216ce34..b6de7a9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "72.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.3" + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "6.7.0" analyzer_plugin: dependency: transitive description: @@ -50,58 +50,58 @@ packages: dependency: "direct main" description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.5.0" async: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.11.0" audio_service: dependency: "direct main" description: name: audio_service - sha256: cb122c7c2639d2a992421ef96b67948ad88c5221da3365ccef1031393a76e044 + sha256: "9dd5ba7e77567b290c35908b1950d61485b4dfdd3a0ac398e98cfeec04651b75" url: "https://pub.dev" source: hosted - version: "0.18.18" + version: "0.18.15" audio_service_platform_interface: dependency: transitive description: name: audio_service_platform_interface - sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777" + sha256: "8431a455dac9916cc9ee6f7da5620a666436345c906ad2ebb7fa41d18b3c1bf4" url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.1.1" audio_service_web: dependency: transitive description: name: audio_service_web - sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df + sha256: "4cdc2127cd4562b957fb49227dc58e3303fafb09bde2573bc8241b938cf759d9" url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.1.3" audio_session: dependency: "direct main" description: name: audio_session - sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" + sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" url: "https://pub.dev" source: hosted - version: "0.1.25" + version: "0.1.21" audio_video_progress_bar: dependency: "direct main" description: @@ -122,66 +122,66 @@ packages: dependency: "direct main" description: name: background_downloader - sha256: d3016a9eb584f6cb16384c8b4a008943c39119730d60046044349b5dbbda4ccb + sha256: "6b73fa5d20c47e855f6ef3ed6fb3e0d164141d8ae7d43ca0a42c78f90eaa15e7" url: "https://pub.dev" source: hosted - version: "9.2.2" + version: "8.5.6" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" build: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.1" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "7.3.2" built_collection: dependency: transitive description: @@ -194,10 +194,10 @@ packages: dependency: transitive description: name: built_value - sha256: "7193c909c8608d3e1a263093ff045f7140bcc1bf3f7de2c5ec7ad027891d2d22" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.10.0" + version: "8.9.2" cached_network_image: dependency: "direct main" description: @@ -222,22 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" - chalkdart: - dependency: transitive - description: - name: chalkdart - sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86" - url: "https://pub.dev" - source: hosted - version: "3.0.5" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.3.0" checked_yaml: dependency: transitive description: @@ -258,18 +250,18 @@ packages: dependency: transitive description: name: cli_util - sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.4.1" clock: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.1" coast: dependency: "direct main" description: @@ -282,26 +274,26 @@ packages: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.10.0" collection: dependency: "direct main" description: name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.1" + version: "1.18.0" convert: dependency: transitive description: name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.1" cross_file: dependency: transitive description: @@ -314,10 +306,10 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.5" cupertino_icons: dependency: "direct main" description: @@ -330,74 +322,66 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "3486c470bb93313a9417f926c7dd694a2e349220992d7b9d14534dc49c15bba9" + sha256: "6e1ec47427ca968f22bce734d00028ae7084361999b41673291138945c5baca0" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.7" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "42cdc41994eeeddab0d7a722c7093ec52bd0761921eeb2cbdbf33d192a234759" + sha256: ba2f90fff4eff71d202d097eb14b14f87087eaaef742e956208c0eb9d3a40a21 url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.7" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5" + sha256: "4ddbbdaa774265de44c97054dcec058a83d9081d071785ece601e348c18c267d" url: "https://pub.dev" source: hosted - version: "0.7.0" - custom_lint_visitor: - dependency: transitive - description: - name: custom_lint_visitor - sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 - url: "https://pub.dev" - source: hosted - version: "1.0.0+6.11.0" + version: "0.6.5" dart_style: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "2.3.7" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53" + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 url: "https://pub.dev" source: hosted - version: "11.4.0" + version: "10.1.2" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.1" dio: dependency: transitive description: name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" url: "https://pub.dev" source: hosted - version: "5.8.0+1" + version: "5.7.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.0.0" duration_picker: dependency: "direct main" description: @@ -406,14 +390,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - dynamic_color: - dependency: "direct main" - description: - name: dynamic_color - sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d - url: "https://pub.dev" - source: hosted - version: "1.7.0" easy_stepper: dependency: "direct main" description: @@ -426,42 +402,42 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.1" ffi: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.0" file_picker: dependency: "direct main" description: name: file_picker - sha256: "77f8e81d22d2a07d0dee2c62e1dda71dc1da73bf43bb2d45af09727406167964" + sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" url: "https://pub.dev" source: hosted - version: "10.1.9" + version: "8.1.2" fixnum: dependency: transitive description: name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -471,10 +447,10 @@ packages: dependency: "direct main" description: name: flutter_animate - sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.0" flutter_cache_manager: dependency: "direct main" description: @@ -483,14 +459,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" + flutter_colorpicker: + dependency: transitive + description: + name: flutter_colorpicker + sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter_hooks: dependency: "direct main" description: name: flutter_hooks - sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d + sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 url: "https://pub.dev" source: hosted - version: "0.21.2" + version: "0.20.5" flutter_lints: dependency: "direct dev" description: @@ -499,22 +483,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_material_pickers: + dependency: "direct main" + description: + name: flutter_material_pickers + sha256: "1f0977df9d3977c6621fff602f6956107cf5ff0df58d3441459e5b2e37256131" + url: "https://pub.dev" + source: hosted + version: "3.7.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.28" + version: "2.0.22" flutter_riverpod: dependency: transitive description: name: flutter_riverpod - sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.5.1" flutter_settings_ui: dependency: "direct main" description: @@ -545,10 +537,10 @@ packages: dependency: "direct main" description: name: font_awesome_flutter - sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a + sha256: "275ff26905134bcb59417cf60ad979136f1f8257f2f449914b2c3e05bbb4cd6f" url: "https://pub.dev" source: hosted - version: "10.8.0" + version: "10.7.0" freezed: dependency: "direct dev" description: @@ -577,18 +569,18 @@ packages: dependency: transitive description: name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.2" go_router: dependency: "direct main" description: name: go_router - sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + sha256: "5cf5fdcf853b0629deb35891c7af643be900c3dcaed7489009f9e7dbcfe55ab6" url: "https://pub.dev" source: hosted - version: "14.8.1" + version: "14.2.8" graphs: dependency: transitive description: @@ -609,50 +601,50 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966" + sha256: "97266a91c994951a06ef0ff3a1c7fb261e52ec7f74e87f0614ea0b7411b859b2" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.5.2" hotreloader: dependency: transitive description: name: hotreloader - sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.2.0" http: dependency: transitive description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.2.2" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" url: "https://pub.dev" source: hosted - version: "4.1.2" + version: "4.0.2" image: dependency: transitive description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.2.0" infinite_listview: dependency: transitive description: @@ -665,18 +657,18 @@ packages: dependency: transitive description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.19.0" io: dependency: transitive description: name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.4" isar: dependency: "direct main" description: @@ -713,67 +705,67 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b url: "https://pub.dev" source: hosted - version: "6.9.0" + version: "6.8.0" just_audio: dependency: "direct main" description: name: just_audio - sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e + sha256: d8e8aaf417d33e345299c17f6457f72bd4ba0c549dc34607abb5183a354edc4d url: "https://pub.dev" source: hosted - version: "0.9.46" + version: "0.9.40" just_audio_background: dependency: "direct main" description: path: just_audio_background ref: media-notification-config - resolved-ref: fce45f334f0838cb6f630548efb65fec40ff17b4 + resolved-ref: "79ac48a7d322d5b8db8847b35ed0c8555fa249bc" url: "https://github.com/Dr-Blank/just_audio" source: git - version: "0.0.1-beta.15" + version: "0.0.1-beta.13" just_audio_media_kit: dependency: "direct main" description: name: just_audio_media_kit - sha256: f3cf04c3a50339709e87e90b4e841eef4364ab4be2bdbac0c54cc48679f84d23 + sha256: "7f57d317fafa04cb3e70b924e8f632ffb7eca7a97a369e1e44738ed89fbd5da1" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.0.5" just_audio_platform_interface: dependency: transitive description: name: just_audio_platform_interface - sha256: "4cd94536af0219fa306205a58e78d67e02b0555283c1c094ee41e402a14a5c4a" + sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.3.0" just_audio_web: dependency: transitive description: name: just_audio_web - sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + sha256: "9a98035b8b24b40749507687520ec5ab404e291d2b0937823ff45d92cb18d448" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.4.13" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -786,10 +778,10 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.0.0" list_wheel_scroll_view_nls: dependency: "direct main" description: @@ -802,10 +794,10 @@ packages: dependency: "direct main" description: name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" logging_appenders: dependency: "direct main" description: @@ -818,28 +810,28 @@ packages: dependency: "direct main" description: name: lottie - sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 + sha256: "6a24ade5d3d918c306bb1c21a6b9a04aab0489d51a2582522eea820b4093b62b" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.1.2" macros: dependency: transitive description: name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" url: "https://pub.dev" source: hosted - version: "0.1.3-main.0" + version: "0.1.2-main.4" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.16+1" material_color_utilities: - dependency: "direct main" + dependency: transitive description: name: material_color_utilities sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec @@ -850,26 +842,26 @@ packages: dependency: "direct main" description: name: material_symbols_icons - sha256: "7c50901b39d1ad645ee25d920aed008061e1fd541a897b4ebf2c01d966dbf16b" + sha256: "66416c4e30bd363508e12669634fc4f3250b83b69e862de67f4f9c480cf42414" url: "https://pub.dev" source: hosted - version: "4.2815.1" + version: "4.2785.1" media_kit: dependency: transitive description: name: media_kit - sha256: "48c10c3785df5d88f0eef970743f8c99b2e5da2b34b9d8f9876e598f62d9e776" + sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.1.11" media_kit_libs_linux: dependency: "direct main" description: name: media_kit_libs_linux - sha256: "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf" + sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.1.3" media_kit_libs_windows_audio: dependency: "direct main" description: @@ -882,18 +874,18 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.0.6" miniplayer: dependency: "direct main" description: @@ -923,58 +915,58 @@ packages: dependency: transitive description: name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "8.0.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.0.1" path: dependency: "direct main" description: name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.9.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.17" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -999,70 +991,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" - url: "https://pub.dev" - source: hosted - version: "11.4.0" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc - url: "https://pub.dev" - source: hosted - version: "12.1.0" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.dev" - source: hosted - version: "9.4.7" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.dev" - source: hosted - version: "0.1.3+5" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.dev" - source: hosted - version: "4.3.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.dev" - source: hosted - version: "0.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.6" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -1079,70 +1023,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - posix: - dependency: transitive - description: - name: posix - sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 - url: "https://pub.dev" - source: hosted - version: "6.0.2" pub_semver: dependency: transitive description: name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.3.0" riverpod: dependency: transitive description: name: riverpod - sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.5.1" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8 + sha256: ac28d7bc678471ec986b42d88e5a0893513382ff7542c7ac9634463b044ac72c url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.5.4" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.3.5" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188" + sha256: "63311e361ffc578d655dfc31b48dfa4ed3bc76fd06f9be845e9bf97c5c11a429" url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.4.3" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "83e4caa337a9840469b7b9bd8c2351ce85abad80f570d84146911b32086fbd99" + sha256: a35a92f2c2a4b7a5d95671c96c5432b42c20f26bb3e985e83d0b186471b61a85 url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.3.13" rxdart: dependency: transitive description: @@ -1155,10 +1091,10 @@ packages: dependency: transitive description: name: safe_local_storage - sha256: e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236 + sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "1.0.2" scroll_loop_auto_scroll: dependency: "direct main" description: @@ -1171,50 +1107,50 @@ packages: dependency: "direct main" description: name: sensors_plus - sha256: "905282c917c6bb731c242f928665c2ea15445aa491249dea9d98d7c79dc8fd39" + sha256: "90f2d38471ca75625f6569d1044d783e0add43548692fbe6e53b008a38a8313a" url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.0.1" sensors_plus_platform_interface: dependency: transitive description: name: sensors_plus_platform_interface - sha256: "58815d2f5e46c0c41c40fb39375d3f127306f7742efe3b891c0b1c87e2b5cd5d" + sha256: b6cacfe243cbeb16403ba688cb0d7054ad4dccb946dcd1254bebdf345fe4b187 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.0" share_plus: dependency: "direct main" description: name: share_plus - sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + sha256: "468c43f285207c84bcabf5737f33b914ceb8eb38398b91e5e3ad1698d1b72a52" url: "https://pub.dev" source: hosted - version: "10.1.4" + version: "10.0.2" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + sha256: "6ababf341050edff57da8b6990f11f4e99eaba837865e2e6defe16d039619db5" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.0" shelf: dependency: transitive description: name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.4.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "2.0.0" shelfsdk: dependency: "direct main" description: @@ -1234,23 +1170,23 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" socket_io_client: dependency: transitive description: name: socket_io_client - sha256: c8471c2c6843cf308a5532ff653f2bcdb7fa9ae79d84d1179920578a06624f0d + sha256: "543842390db2c1d1b02e1ad0e6167db2f8872c7a6669051cb89e2559b11a5aeb" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.0.0-beta.4" socket_io_common: dependency: transitive description: name: socket_io_common - sha256: "162fbaecbf4bf9a9372a62a341b3550b51dcef2f02f3e5830a297fd48203d45b" + sha256: "392c3613c88ad3ee0e15911db2e7e9dbd675622c2589ce99383630603b097619" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.0.0" source_gen: dependency: transitive description: @@ -1263,18 +1199,18 @@ packages: dependency: transitive description: name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" url: "https://pub.dev" source: hosted - version: "1.3.5" + version: "1.3.4" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.0" sprintf: dependency: transitive description: @@ -1287,50 +1223,26 @@ packages: dependency: transitive description: name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + sha256: ff5a2436ef8ebdfda748fbfe957f9981524cb5ff11e7bafa8c42771840e8a788 url: "https://pub.dev" source: hosted - version: "2.4.2" - sqflite_android: - dependency: transitive - description: - name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" - url: "https://pub.dev" - source: hosted - version: "2.4.1" + version: "2.3.3+2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + sha256: "2d8e607db72e9cb7748c9c6e739e2c9618320a5517de693d5a24609c4671b1a4" url: "https://pub.dev" source: hosted - version: "2.5.5" - sqflite_darwin: - dependency: transitive - description: - name: sqflite_darwin - sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - sqflite_platform_interface: - dependency: transitive - description: - name: sqflite_platform_interface - sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" - source: hosted - version: "2.4.0" + version: "2.5.4+4" stack_trace: dependency: transitive description: name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.1" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1343,66 +1255,66 @@ packages: dependency: transitive description: name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.2" stream_transform: dependency: transitive description: name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.2.0" synchronized: dependency: transitive description: name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.3.0+3" term_glyph: dependency: transitive description: name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.2" timing: dependency: transitive description: name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.1" typed_data: dependency: transitive description: name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.3.2" universal_platform: dependency: transitive description: @@ -1415,50 +1327,50 @@ packages: dependency: transitive description: name: uri_parser - sha256: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270 + sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "2.0.2" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.3.16" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.3.3" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: @@ -1471,18 +1383,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.2" uuid: dependency: transitive description: @@ -1503,82 +1415,82 @@ packages: dependency: "direct main" description: name: vibration - sha256: "804ee8f9628f31ee71fbe6137a2bc6206a64e101ec22cd9dd6d3a7dc0272591b" + sha256: fe8f90e1827f86a4f722b819799ecac8a24789a39c6d562ea316bcaeb8b1ec61 url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "2.0.0" vibration_platform_interface: dependency: transitive description: name: vibration_platform_interface - sha256: "03e9deaa4df48a1a6212e281bfee5f610d62e9247929dd2f26f4efd4fa5e225c" + sha256: "735a5fef0f284de0ad9449a5ed7d36ba017c6f59b5b20ac64418af4a6bd35ee7" url: "https://pub.dev" source: hosted - version: "0.1.0" + version: "0.0.1" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "14.2.5" watcher: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.0" web: dependency: transitive description: name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.0" web_socket: dependency: transitive description: name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.1" win32: dependency: transitive description: name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "5.5.4" win32_registry: dependency: transitive description: name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "1.1.5" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.0.4" xml: dependency: transitive description: @@ -1591,10 +1503,10 @@ packages: dependency: transitive description: name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.2" sdks: - dart: ">=3.7.0 <4.0.0" - flutter: ">=3.32.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index daa3a2e..3ce174f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,11 +16,10 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.0.18+9 +version: 0.0.11+2 environment: sdk: ">=3.3.4 <4.0.0" - flutter: 3.32.0 isar_version: &isar_version ^4.0.0-dev.13 # define the version to be used @@ -33,26 +32,26 @@ isar_version: &isar_version ^4.0.0-dev.13 # define the version to be used dependencies: animated_list_plus: ^0.5.2 animated_theme_switcher: ^2.0.10 - archive: ^4.0.5 + archive: ^3.6.1 audio_service: ^0.18.15 - audio_session: ^0.1.23 + audio_session: ^0.1.19 audio_video_progress_bar: ^2.0.2 auto_scroll_text: ^0.0.7 - background_downloader: ^9.2.0 + background_downloader: ^8.5.2 cached_network_image: ^3.3.1 coast: ^2.0.2 collection: ^1.18.0 cupertino_icons: ^1.0.6 - device_info_plus: ^11.3.3 + device_info_plus: ^10.1.0 duration_picker: ^1.2.0 - dynamic_color: ^1.7.0 easy_stepper: ^0.8.4 - file_picker: ^10.0.0 + file_picker: ^8.1.2 flutter: sdk: flutter flutter_animate: ^4.5.0 flutter_cache_manager: ^3.3.2 - flutter_hooks: ^0.21.2 + flutter_hooks: ^0.20.5 + flutter_material_pickers: ^3.6.0 flutter_settings_ui: ^3.0.1 font_awesome_flutter: ^10.7.0 freezed_annotation: ^2.4.1 @@ -74,7 +73,6 @@ dependencies: logging: ^1.2.0 logging_appenders: ^1.3.1 lottie: ^3.1.0 - material_color_utilities: ^0.11.1 material_symbols_icons: ^4.2785.1 media_kit_libs_linux: any media_kit_libs_windows_audio: any @@ -86,7 +84,6 @@ dependencies: package_info_plus: ^8.0.0 path: ^1.9.0 path_provider: ^2.1.0 - permission_handler: ^11.3.1 riverpod_annotation: ^2.3.5 scroll_loop_auto_scroll: ^0.0.5 sensors_plus: ^6.0.1 @@ -95,10 +92,10 @@ dependencies: path: ./shelfsdk shimmer: ^3.0.0 url_launcher: ^6.2.6 - vibration: ^3.1.3 + vibration: ^2.0.0 dev_dependencies: build_runner: ^2.4.9 - custom_lint: ^0.7.0 + custom_lint: ^0.6.4 flutter_lints: ^5.0.0 flutter_test: sdk: flutter @@ -122,7 +119,6 @@ flutter: - assets/animations/ - assets/sounds/ - assets/images/ - - assets/fonts/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see @@ -148,7 +144,3 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages - fonts: - - family: AbsIcons - fonts: - - asset: assets/fonts/AbsIcons.ttf diff --git a/shelfsdk b/shelfsdk deleted file mode 160000 index e1848a4..0000000 --- a/shelfsdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e1848a42c27257146015a33e9427f197f522fe03 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index de21556..8d09818 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,22 +6,16 @@ #include "generated_plugin_registrant.h" -#include #include #include -#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { - DynamicColorPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); IsarFlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitLibsWindowsAudioPluginCApi")); - PermissionHandlerWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 13d504d..51689fc 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,10 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST - dynamic_color isar_flutter_libs media_kit_libs_windows_audio - permission_handler_windows share_plus url_launcher_windows )