diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index cf174cb..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Report a bug -description: | - You detected something wrong on the application. -labels: ["bug"] -body: - - type: textarea - id: steps - attributes: - label: Steps to reproduce - description: Steps to reproduce the problem you are running into. - placeholder: | - 1. ... - 2. ... - 3. ... - validations: - required: true - - type: textarea - id: expected-results - attributes: - label: Expected results - description: What is expected to happen. - validations: - required: true - - type: textarea - id: actual-results - attributes: - label: Actual results - description: What is actually happening. - validations: - required: true - - type: textarea - id: screenshots - attributes: - label: Screenshots or Video - description: | - Upload any screenshots or video of the bug. - value: | - Screenshots or video demonstration - validations: - required: true - - type: textarea - id: app-os-details - attributes: - label: App and OS details - description: | - Some details about the app version and the OS where you are running the application. - value: | - - App version: (eg: v2.16.0) - - Device OS version: (eg: Android 14) - validations: - required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request_agh.yml b/.github/ISSUE_TEMPLATE/feature_request_agh.yml deleted file mode 100644 index 55972db..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request_agh.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Request a new feature that has been added to AdGuard Home -description: | - The AdGuard Home team added recently a new feature that you want on the app. -labels: ["agh feature request"] -body: - - type: textarea - id: description - attributes: - label: Description - description: Describe the feature that you want on the app. - placeholder: | - Write the details here... - validations: - required: true - - type: textarea - id: screenshots - attributes: - label: Screenshots - description: Attach some screenshots of where that new feature is located on the web administration panel. - placeholder: | - Screenshots here. - validations: - required: true - - type: textarea - id: version-introduced - attributes: - label: Version where feature was introduced - description: Version of the AdGuard Home server where this feature was introduced. - placeholder: | - - Version: (eg: v0.107.44) - validations: - required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request_app.yml b/.github/ISSUE_TEMPLATE/feature_request_app.yml deleted file mode 100644 index 3cf08e6..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request_app.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Request a new feature or an improvement for the app itself -description: | - You want a new feature for the application, or an improvement for an existing one. -labels: ["app feature request"] -body: - - type: markdown - id: important-info - attributes: - value: | - ℹ️ IMPORTANT INFO ℹ️ - Please note that if the functionality is related to data representation, it may not be implemented, as AdGuard Home Manager depends on the capabilities of the AdGuard Home API. - - type: textarea - id: description - attributes: - label: Description - description: Describe the feature that you want on the app. - placeholder: | - Write the details here... - validations: - required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml deleted file mode 100644 index 805783f..0000000 --- a/.github/ISSUE_TEMPLATE/question.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: You have a question about the app -description: | - You want ask something that's not related with a feature request or a bug. -labels: ["question"] -body: - - type: markdown - id: important-info - attributes: - value: | - Having problems while adding a connection to a server? Check out [this guide](https://github.com/JGeek00/adguard-home-manager/wiki/Create-a-connection). - - type: textarea - id: question - attributes: - label: Question - description: Write your question here giving the maximum detail possible. - placeholder: | - Write your question here... - validations: - required: true \ No newline at end of file diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml new file mode 100644 index 0000000..8f72ac4 --- /dev/null +++ b/.github/workflows/release-beta.yaml @@ -0,0 +1,123 @@ +name: Compile and release beta build + +on: + workflow_dispatch: + branches: + - beta + +jobs: + build-android: + name: Build Android .apk and .aab + runs-on: ubuntu-latest + env: + ANDROID_AAB_RELEASE_PATH: build/app/outputs/bundle/release + ANDROID_APK_RELEASE_PATH: build/app/outputs/apk/release + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} + steps: + - uses: actions/checkout@v3 + with: + ref: beta + - name: Decode android/app/keystore.jks + run: echo "${{ secrets.KEYSTORE_JKS }}" | base64 --decode > android/app/keystore.jks + - name: Decode android/key.properties + run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties + - name: Decode .env + run: echo "${{ secrets.ENV }}" | base64 --decode > .env + - name: Read pubspec.yaml + uses: adore-me/read-yaml@v1.0.0 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Save version on env variable + id: save_version + run: | + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT + - name: Update KeyStore password in gradle properties + run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties + - name: Update KeyStore key password in gradle properties + run: sed -i 's/#{KEYSTORE_KEY_PASS}#/${{ secrets.KEYSTORE_KEY_PASS }}/g' android/key.properties + - uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '18.x' + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + - run: flutter clean + - run: flutter pub get + - run: flutter build apk --release + - run: flutter build appbundle --release + - name: Rename apk + run: mv $ANDROID_APK_RELEASE_PATH/app-release.apk $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk + - name: Rename aab + run: mv $ANDROID_AAB_RELEASE_PATH/app-release.aab $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + - name: Copy apk to project root + run: cp $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk + - name: Copy aab to project root + run: cp $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: android + path: | + AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk + release-builds-github: + name: Release beta build to GitHub + runs-on: ubuntu-latest + needs: [build-android] + env: + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} + VERSION_NUMBER: ${{ needs.build-android.outputs.VERSION_NUMBER }} + steps: + - uses: actions/checkout@v3 + with: + ref: beta + - name: Create builds directory + run: mkdir releases + - name: Download Android artifacts + uses: actions/download-artifact@v3 + with: + name: android + path: releases/ + - name: Release to GitHub + uses: ncipollo/release-action@v1 + with: + artifacts: "releases/*" + token: ${{ secrets.GH_TOKEN }} + tag: '${{ env.VERSION_NAME }}_(${{ env.VERSION_NUMBER }})' + name: v${{ env.VERSION_NAME }} + draft: true + prerelease: true + commit: ${{ github.sha }} + release-build-google-play: + name: Release Android beta build to the Google Play Store + runs-on: ubuntu-latest + needs: [build-android] + env: + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} + steps: + - uses: actions/checkout@v3 + with: + ref: beta + - name: Download Android artifacts + uses: actions/download-artifact@v3 + with: + name: android + - name: Release app to Google Play + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }} + packageName: com.jgeek00.adguard_home_manager + releaseFiles: AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + track: beta + status: draft + releaseName: ${{ env.VERSION_NAME }} \ No newline at end of file diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml new file mode 100644 index 0000000..2d8f0e8 --- /dev/null +++ b/.github/workflows/release-stable.yaml @@ -0,0 +1,302 @@ +name: Compile and release production build + +on: + workflow_dispatch: + branches: + - master + +jobs: + build-android: + name: Build Android .apk and .aab + runs-on: ubuntu-latest + env: + ANDROID_AAB_RELEASE_PATH: build/app/outputs/bundle/release + ANDROID_APK_RELEASE_PATH: build/app/outputs/apk/release + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} + steps: + - uses: actions/checkout@v3 + - name: Decode android/app/keystore.jks + run: echo "${{ secrets.KEYSTORE_JKS }}" | base64 --decode > android/app/keystore.jks + - name: Decode android/key.properties + run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties + - name: Decode .env + run: echo "${{ secrets.ENV }}" | base64 --decode > .env + - name: Read pubspec.yaml + uses: adore-me/read-yaml@v1.0.0 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Save version on env variable + id: save_version + run: | + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT + - name: Update KeyStore password in gradle properties + run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties + - name: Update KeyStore key password in gradle properties + run: sed -i 's/#{KEYSTORE_KEY_PASS}#/${{ secrets.KEYSTORE_KEY_PASS }}/g' android/key.properties + - uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '18.x' + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + - run: flutter clean + - run: flutter pub get + - run: flutter build apk --release + - run: flutter build appbundle --release + - name: Rename apk + run: mv $ANDROID_APK_RELEASE_PATH/app-release.apk $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk + - name: Rename aab + run: mv $ANDROID_AAB_RELEASE_PATH/app-release.aab $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + - name: Copy apk to project root + run: cp $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk + - name: Copy aab to project root + run: cp $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: android + path: | + AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk + build-macos: + name: Build macOS .dmg + runs-on: macos-latest + env: + MACOS_APP_RELEASE_PATH: build/macos/Build/Products/Release + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} + steps: + - uses: actions/checkout@v3 + - name: Decode .env + run: echo "${{ secrets.ENV }}" | base64 --decode > .env + - name: Read pubspec.yaml + uses: adore-me/read-yaml@v1.0.0 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Save version on env variable + id: save_version + run: | + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + - run: flutter clean + - run: flutter pub get + - run: flutter build macos --release + - name: Install the Apple certificate and sign the application + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PWD: ${{ secrets.APPLE_CERTIFICATE_PWD }} + APPLE_KEYCHAIN_PWD: ${{ secrets.APPLE_KEYCHAIN_PWD }} + APPLE_IDENTITY_ID: ${{ secrets.APPLE_IDENTITY_ID }} + run: | + echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 + security create-keychain -p $APPLE_KEYCHAIN_PWD build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p $APPLE_KEYCHAIN_PWD build.keychain + security import certificate.p12 -k build.keychain -P $APPLE_CERTIFICATE_PWD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $APPLE_KEYCHAIN_PWD build.keychain + /usr/bin/codesign --force -s "$APPLE_IDENTITY_ID" $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager.app -v + - name: Create folder to build dmg + run: mkdir $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager + - name: Copy app into folder + run: cp -r $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager.app $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager/AdGuard\ Home\ Manager.app + - name: Generate symbolic link to Applications dir + run: ln -s /Applications $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager + - name: Generate dmg + run: hdiutil create -srcfolder $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg + - name: Copy dmg to project root + run: cp $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: macos + path: AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg + build-linux: + name: Build Linux .tar.gz and .deb + runs-on: ubuntu-latest + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} + steps: + - uses: actions/checkout@v3 + - name: Decode .env + run: echo "${{ secrets.ENV }}" | base64 --decode > .env + - name: Read pubspec.yaml + uses: adore-me/read-yaml@v1.0.0 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Save version on env variable + id: save_version + run: | + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT + - name: Update version in debian.yaml + run: sed -i 's//${{ env.VERSION_NAME }}/g' debian/debian.yaml + - name: Update dependencies list + run: sudo apt-get update + - name: Install dependencies + run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + - run: flutter clean + - run: flutter pub get + - run: flutter build linux --release + - name: Install flutter_to_debian + run: dart pub global activate flutter_to_debian + - name: Generate .deb package + run: flutter_to_debian + - name: Move .deb package to project root + run: mv debian/packages/AdGuardHomeManager_${{ env.VERSION_NAME }}_amd64.deb AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb + - name: Generate .tar.gz package + uses: a7ul/tar-action@v1.1.3 + id: compress + with: + command: c + cwd: build/linux/x64/release/bundle + files: | + ./data + ./lib + ./AdGuardHomeManager + outPath: AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux.tar.gz + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: linux + path: | + AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb + AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux.tar.gz + build-windows: + name: Build Windows installer + runs-on: windows-latest + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} + steps: + - uses: actions/checkout@v3 + - name: Decode .env + shell: pwsh + run: | + [IO.File]::WriteAllBytes('.env', [Convert]::FromBase64String('${{ secrets.ENV }}')) + - name: Read pubspec.yaml + uses: adore-me/read-yaml@v1.0.0 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Save version on env variable + shell: bash + id: save_version + run: | + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT + - name: Update version in innosetup config file + shell: pwsh + run: | + (Get-Content windows/innosetup_installer_builder.iss) -replace '', '${{ env.VERSION_NAME }}' | Out-File -encoding ASCII windows/innosetup_installer_builder.iss + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + - run: flutter clean + - run: flutter pub get + - run: flutter build windows --release + - name: Build installer witn innosetup + run: iscc /Q windows/innosetup_installer_builder.iss + - name: Move installer file to root directory + run: move build/windows/aghm_installer.exe AdGuardHomeManager_${{ env.VERSION_NAME }}_Windows_x64.exe + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: windows + path: AdGuardHomeManager_${{ env.VERSION_NAME }}_Windows_x64.exe + release-builds-github: + name: Release builds to GitHub + runs-on: ubuntu-latest + needs: [build-android, build-macos, build-linux, build-windows] + env: + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} + VERSION_NUMBER: ${{ needs.build-android.outputs.VERSION_NUMBER }} + steps: + - uses: actions/checkout@v3 + - name: Create builds directory + run: mkdir releases + - name: Download Android artifacts + uses: actions/download-artifact@v3 + with: + name: android + path: releases/ + - name: Download macOS artifacts + uses: actions/download-artifact@v3 + with: + name: macos + path: releases/ + - name: Download Linux artifacts + uses: actions/download-artifact@v3 + with: + name: linux + path: releases/ + - name: Download Windows artifacts + uses: actions/download-artifact@v3 + with: + name: windows + path: releases/ + - name: Release to GitHub + uses: ncipollo/release-action@v1 + with: + artifacts: "releases/*" + token: ${{ secrets.GH_TOKEN }} + tag: '${{ env.VERSION_NAME }}_(${{ env.VERSION_NUMBER }})' + name: v${{ env.VERSION_NAME }} + draft: true + prerelease: false + commit: ${{ github.sha }} + release-build-google-play: + name: Release Android build to the Google Play Store + runs-on: ubuntu-latest + needs: [build-android, build-macos, build-linux, build-windows] + env: + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} + steps: + - uses: actions/checkout@v3 + - name: Download Android artifacts + uses: actions/download-artifact@v3 + with: + name: android + - name: Release app to Google Play + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }} + packageName: com.jgeek00.adguard_home_manager + releaseFiles: AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + track: production + status: draft + releaseName: ${{ env.VERSION_NAME }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e9d1504..6f4a17f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,9 @@ *.swp .DS_Store .atom/ -.build/ .buildlog/ .history .svn/ -.swiftpm/ migrate_working_dir/ # Env @@ -50,6 +48,4 @@ app.*.map.json /debian/packages -untranslated.json - -android/app/.cxx +untranslated.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index fab9821..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "editor.formatOnSave": false, - "editor.formatOnPaste": false, - "editor.formatOnType": false, - "editor.defaultFormatter": "Dart-Code.flutter", - "dart.lineLength": 120, - "[dart]": { - "editor.rulers": [ - 120 - ], - "editor.defaultFormatter": "Dart-Code.dart-code", - "editor.formatOnSave": false, - "editor.formatOnPaste": false, - "editor.formatOnType": false - }, - "cSpell.ignorePaths": [ - "/pubspec.yaml", - "/.github/workflows" - ], -} \ No newline at end of file diff --git a/README.md b/README.md index 1dcbc67..0a4af9d 100644 --- a/README.md +++ b/README.md @@ -40,41 +40,22 @@ Check the privacy policy [here](https://github.com/JGeek00/adguard-home-manager/ This is an unofficial application. The AdGuard Home team and the development of the AdGuard Home software is not related in any way with this application. ## Recommended configuration and lists -On [this repository](https://github.com/juanico10/Pihole_list) you can find a recommended configuration for AdGuard Home and some lists. Thanks to [juanico10](https://github.com/juanico10). - -## Donations -If you like the project and you want to contribute with the development, you can [become a sponsor on GitHub](https://github.com/sponsors/JGeek00), or you can donate using PayPal. - -
- - Donate with PayPal - -
+On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a recommended configuration for AdGuard Home and some lists. Thanks to [JuanRodenas](https://github.com/JuanRodenas). ## Generate production build
  • Windows
      -
    1. Run flutter build windows --release.
    2. +
    3. flutter clean
    4. +
    5. flutter pub get
    6. +
    7. flutter build windows
    8. Open Inno Setup Compiler application and load the script
    9. -
    10. The script is located at windows/innosetup_installer_builder.iss
    11. +
    12. The script is located at windows/innosetup_installer_builder.iss
    13. Update the version number and save the changes
    14. Click on the Compile button
    15. -
    16. The installer will be generated at build/windows/aghm_installer.exe.
    17. +
    18. The installer will be generated at build/windows/aghm_installer.exe
  • @@ -113,6 +96,7 @@ If you like the project and you want to contribute with the development, you can - [expandable](https://pub.dev/packages/expandable) - [package info plus](https://pub.dev/packages/package_info_plus) - [flutter phoenix](https://pub.dev/packages/flutter_phoenix) +- [flutter displaymode](https://pub.dev/packages/flutter_displaymode) - [flutter launcher icons](https://pub.dev/packages/flutter_launcher_icons) - [flutter native splash](https://pub.dev/packages/flutter_native_splash) - [intl](https://pub.dev/packages/intl) @@ -120,8 +104,10 @@ If you like the project and you want to contribute with the development, you can - [dynamic color](https://pub.dev/packages/dynamic_color) - [device info](https://pub.dev/packages/device_info) - [fl chart](https://pub.dev/packages/fl_chart) +- [flutter web browser](https://pub.dev/packages/flutter_web_browser) - [flutter svg](https://pub.dev/packages/flutter_svg) - [percent indicator](https://pub.dev/packages/percent_indicator) +- [store checker](https://pub.dev/packages/store_checker) - [flutter markdown](https://pub.dev/packages/flutter_markdown) - [markdown](https://pub.dev/packages/markdown) - [html](https://pub.dev/packages/html) @@ -136,11 +122,6 @@ If you like the project and you want to contribute with the development, you can - [flutter reorderable list](https://pub.dev/packages/flutter_reorderable_list) - [pie chart](https://pub.dev/packages/pie_chart) - [segmented button slide](https://pub.dev/packages/segmented_button_slide) -- [timezone](https://pub.dev/packages/timezone) -- [url launcher](https://pub.dev/packages/url_launcher) -- [flutter custom tabs](https://pub.dev/packages/flutter_custom_tabs) -- [shared preferences](https://pub.dev/packages/shared_preferences) -- [window manager](https://pub.dev/packages/window_manager)
    diff --git a/android/app/build.gradle b/android/app/build.gradle index ab6291c..099c059 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,9 +1,3 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) { } } +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -22,6 +21,10 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { @@ -29,9 +32,8 @@ if (keystorePropertiesFile.exists()) { } android { - namespace "com.jgeek00.adguard_home_manager" - compileSdkVersion 35 - ndkVersion "26.1.10909125" + compileSdkVersion 33 + ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -47,9 +49,12 @@ android { } defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.jgeek00.adguard_home_manager" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 26 - targetSdkVersion 35 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } @@ -75,5 +80,5 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png index 0952f84..a042f2b 100644 Binary files a/android/app/src/main/res/drawable-hdpi/splash.png and b/android/app/src/main/res/drawable-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png index b8c6efb..28271d4 100644 Binary files a/android/app/src/main/res/drawable-mdpi/splash.png and b/android/app/src/main/res/drawable-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-hdpi/splash.png b/android/app/src/main/res/drawable-night-hdpi/splash.png index 0952f84..a042f2b 100644 Binary files a/android/app/src/main/res/drawable-night-hdpi/splash.png and b/android/app/src/main/res/drawable-night-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-mdpi/splash.png b/android/app/src/main/res/drawable-night-mdpi/splash.png index b8c6efb..28271d4 100644 Binary files a/android/app/src/main/res/drawable-night-mdpi/splash.png and b/android/app/src/main/res/drawable-night-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-xhdpi/splash.png b/android/app/src/main/res/drawable-night-xhdpi/splash.png index 09c5fe0..46169c6 100644 Binary files a/android/app/src/main/res/drawable-night-xhdpi/splash.png and b/android/app/src/main/res/drawable-night-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxhdpi/splash.png b/android/app/src/main/res/drawable-night-xxhdpi/splash.png index 7251f53..27ee599 100644 Binary files a/android/app/src/main/res/drawable-night-xxhdpi/splash.png and b/android/app/src/main/res/drawable-night-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/splash.png b/android/app/src/main/res/drawable-night-xxxhdpi/splash.png old mode 100755 new mode 100644 index 539c85b..09a5539 Binary files a/android/app/src/main/res/drawable-night-xxxhdpi/splash.png and b/android/app/src/main/res/drawable-night-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png index 09c5fe0..46169c6 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/splash.png and b/android/app/src/main/res/drawable-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/splash.png b/android/app/src/main/res/drawable-xxhdpi/splash.png index 7251f53..27ee599 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/splash.png and b/android/app/src/main/res/drawable-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png old mode 100755 new mode 100644 index 539c85b..09a5539 Binary files a/android/app/src/main/res/drawable-xxxhdpi/splash.png and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6c2fdb0..ce2f8b0 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,7 +1,7 @@ - - + + - + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09b..0000000 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..620121e Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index ee4fa4d..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp deleted file mode 100644 index 4d52a73..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index ee4fa4d..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..4b33d20 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 2ae3642..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp deleted file mode 100644 index 8a39f50..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 2ae3642..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..3128163 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 1c206bb..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp deleted file mode 100644 index e490246..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1c206bb..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..7eb67dc Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index aefe0b6..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 3599460..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index aefe0b6..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..7eadefb Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index 68173f9..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 7d57305..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 68173f9..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..ab98328 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #ffffff + \ No newline at end of file diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index c5d5899..0000000 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FFFFFF - \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index bc157bd..09b245a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,16 @@ +buildscript { + ext.kotlin_version = '1.8.20' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + allprojects { repositories { google() diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 19cfad9..cb24abd 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/android/key.properties.sample b/android/key.properties.sample deleted file mode 100644 index b51dc2c..0000000 --- a/android/key.properties.sample +++ /dev/null @@ -1,4 +0,0 @@ -storePassword= # keystore password # -keyPassword= # keystore key password # -keyAlias= # key alias # -storeFile= # ./keystore-file-name.jks # \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index e8481f5..44e62bc 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,26 +1,11 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() +include ':app' - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.6.0' apply false - id "org.jetbrains.kotlin.android" version "1.8.20" apply false -} - -include ":app" +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/debian/debian.yaml b/debian/debian.yaml index 43a90f9..876c66e 100644 --- a/debian/debian.yaml +++ b/debian/debian.yaml @@ -5,7 +5,7 @@ flutter_app: control: Package: AdGuardHomeManager - Version: 2.20.1 + Version: Architecture: amd64 Essential: no Priority: optional diff --git a/ios/.gitignore b/ios/.gitignore old mode 100755 new mode 100644 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist old mode 100755 new mode 100644 index 7c56964..9625e10 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 11.0 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig old mode 100755 new mode 100644 diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig old mode 100755 new mode 100644 diff --git a/ios/Podfile b/ios/Podfile old mode 100755 new mode 100644 index 279576f..88359b2 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2c2d827..ed1a57c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2,53 +2,60 @@ PODS: - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) - - flutter_custom_tabs_ios (2.0.0): - - Flutter - flutter_native_splash (0.0.1): - Flutter + - flutter_web_browser (0.17.1): + - Flutter + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) - package_info_plus (0.4.5): - Flutter - - Sentry/HybridSDK (8.18.0): - - SentryPrivate (= 8.18.0) + - Sentry/HybridSDK (8.9.1): + - SentryPrivate (= 8.9.1) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.18.0) - - SentryPrivate (8.18.0) + - Sentry/HybridSDK (= 8.9.1) + - SentryPrivate (8.9.1) - sqflite (0.0.3): - Flutter - - FlutterMacOS - - sqlite3 (3.45.0): - - sqlite3/common (= 3.45.0) - - sqlite3/common (3.45.0) - - sqlite3/fts5 (3.45.0): + - FMDB (>= 2.7.5) + - sqlite3 (3.43.1): + - sqlite3/common (= 3.43.1) + - sqlite3/common (3.43.1) + - sqlite3/fts5 (3.43.1): - sqlite3/common - - sqlite3/perf-threadsafe (3.45.0): + - sqlite3/perf-threadsafe (3.43.1): - sqlite3/common - - sqlite3/rtree (3.45.0): + - sqlite3/rtree (3.43.1): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - - sqlite3 (~> 3.45.0) + - sqlite3 (~> 3.43.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree - store_checker (0.0.1): - Flutter + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - - flutter_custom_tabs_ios (from `.symlinks/plugins/flutter_custom_tabs_ios/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) + - flutter_web_browser (from `.symlinks/plugins/flutter_web_browser/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) - store_checker (from `.symlinks/plugins/store_checker/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: + - FMDB - Sentry - SentryPrivate - sqlite3 @@ -58,35 +65,39 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter - flutter_custom_tabs_ios: - :path: ".symlinks/plugins/flutter_custom_tabs_ios/ios" flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" + flutter_web_browser: + :path: ".symlinks/plugins/flutter_web_browser/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" sentry_flutter: :path: ".symlinks/plugins/sentry_flutter/ios" sqflite: - :path: ".symlinks/plugins/sqflite/darwin" + :path: ".symlinks/plugins/sqflite/ios" sqlite3_flutter_libs: :path: ".symlinks/plugins/sqlite3_flutter_libs/ios" store_checker: :path: ".symlinks/plugins/store_checker/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_custom_tabs_ios: 62439c843b2691aae516fd50119a01eb9755fff7 + device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - Sentry: 8984a4ffb2b9bd2894d74fb36e6f5833865bc18e - sentry_flutter: c87a0556eeb6cbf7f9f924d30e878bdedf22d364 - SentryPrivate: 2f0c9ba4c3fc993f70eab6ca95673509561e0085 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - sqlite3: f307b6291c4db7b5086c38d6237446b98a738581 - sqlite3_flutter_libs: aeb4d37509853dfa79d9b59386a2dac5dd079428 + flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 + Sentry: e3203780941722a1fcfee99e351de14244c7f806 + sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c + SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13 + sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a + sqlite3: e0a0623a33a20a47cb5921552aebc6e9e437dc91 + sqlite3_flutter_libs: 878ccbdcfd7b7cb41a774ec238223d876880c5ec store_checker: 359c5051d9ec30ff0a8fa39eb5ec9df021bb745d + url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 -PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.14.3 +COCOAPODS: 1.12.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj old mode 100755 new mode 100644 index 00edeae..4f766b9 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ 65533F0C0783FDE34AE79B0A /* Pods-Runner.release.xcconfig */, 69C2CC4A6DE17506FC5C0F13 /* Pods-Runner.profile.xcconfig */, ); + name = Pods; path = Pods; sourceTree = ""; }; @@ -155,7 +156,6 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -325,7 +325,6 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -336,7 +335,6 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -345,7 +343,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -363,22 +361,15 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; - ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -406,7 +397,6 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -417,7 +407,6 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -432,7 +421,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -463,7 +452,6 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -474,7 +462,6 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -483,7 +470,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -503,23 +490,16 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; - ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -533,22 +513,15 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; - ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata old mode 100755 new mode 100644 diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist old mode 100755 new mode 100644 diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings old mode 100755 new mode 100644 diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme old mode 100755 new mode 100644 diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata old mode 100755 new mode 100644 diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist old mode 100755 new mode 100644 diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings old mode 100755 new mode 100644 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md old mode 100755 new mode 100644 diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard old mode 100755 new mode 100644 diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard old mode 100755 new mode 100644 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist old mode 100755 new mode 100644 index 55fe6b7..f9aeb27 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,53 +1,53 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - AdGuard Home - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - adguardHomeManager - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + AdGuard Home Manager + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + adguard_home_manager + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + UIStatusBarHidden + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h old mode 100755 new mode 100644 diff --git a/lib/config/theme.dart b/lib/config/theme.dart index eba0b9c..8a8aadf 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -15,24 +15,6 @@ ThemeData lightTheme(ColorScheme? dynamicColorScheme) => ThemeData( textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), ), - cardTheme: CardTheme( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - popupMenuTheme: PopupMenuThemeData( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - navigationBarTheme: NavigationBarThemeData( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - dialogTheme: DialogTheme( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - // DISABLE PREDICTIVE BACK GESTURE - // pageTransitionsTheme: const PageTransitionsTheme( - // builders: { - // TargetPlatform.android: PredictiveBackPageTransitionsBuilder() - // } - // ) ); ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData( @@ -51,24 +33,6 @@ ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData( textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), ), - cardTheme: CardTheme( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - popupMenuTheme: PopupMenuThemeData( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - navigationBarTheme: NavigationBarThemeData( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - dialogTheme: DialogTheme( - surfaceTintColor: dynamicColorScheme?.surfaceTint - ), - // DISABLE PREDICTIVE BACK GESTURE - // pageTransitionsTheme: const PageTransitionsTheme( - // builders: { - // TargetPlatform.android: PredictiveBackPageTransitionsBuilder() - // } - // ) ); ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData( @@ -87,12 +51,6 @@ ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData( iconColor: Color.fromRGBO(117, 117, 117, 1), ), brightness: Brightness.light, - // DISABLE PREDICTIVE BACK GESTURE - // pageTransitionsTheme: const PageTransitionsTheme( - // builders: { - // TargetPlatform.android: PredictiveBackPageTransitionsBuilder() - // } - // ) ); ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData( @@ -114,10 +72,4 @@ ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData( iconColor: Color.fromRGBO(187, 187, 187, 1), ), brightness: Brightness.dark, - // DISABLE PREDICTIVE BACK GESTURE - // pageTransitionsTheme: const PageTransitionsTheme( - // builders: { - // TargetPlatform.android: PredictiveBackPageTransitionsBuilder() - // } - // ) ); \ No newline at end of file diff --git a/lib/constants/enums.dart b/lib/constants/enums.dart index 569db37..f54e665 100644 --- a/lib/constants/enums.dart +++ b/lib/constants/enums.dart @@ -1,3 +1,2 @@ enum LoadStatus { loading, loaded, error } -enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime } -enum CustomRulesSorting { topBottom, bottomTop } \ No newline at end of file +enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime } \ No newline at end of file diff --git a/lib/constants/regexps.dart b/lib/constants/regexps.dart deleted file mode 100644 index e882913..0000000 --- a/lib/constants/regexps.dart +++ /dev/null @@ -1,12 +0,0 @@ -class Regexps { - static final wildcardDomain = RegExp(r'^(\*\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,10}$'); - static final domain = RegExp(r'^((?:(?:[a-zA-Z]{1})|(?:[a-zA-Z]{1}[a-zA-Z]{1})|(?:[a-zA-Z]{1}[0-9]{1})|(?:[0-9]{1}[a-zA-Z]{1})|(?:[a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.)+([a-zA-Z]{2,10}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,10})$'); - static final ipv4Address = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); - static final ipv6Address = RegExp(r'(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))'); - static final subroute = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$'); - static final macAddress = RegExp(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$'); - static final url = RegExp(r'^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})$'); - static final certificate = RegExp(r'(-{3,}(\bBEGIN CERTIFICATE\b))|(-{3,}-{3,}(\END CERTIFICATE\b)-{3,})', multiLine: true); - static final privateKey = RegExp(r'(-{3,}(\bBEGIN\b).*(PRIVATE KEY\b))|(-{3,}-{3,}(\bEND\b).*(PRIVATE KEY\b)-{3,})', multiLine: true); - static final path = RegExp(r'^(\/{0,1}(?!\/))[A-Za-z0-9\/\-_]+(\.([a-zA-Z]+))?$'); -} \ No newline at end of file diff --git a/lib/constants/urls.dart b/lib/constants/urls.dart index 0a8ece3..b7eb069 100644 --- a/lib/constants/urls.dart +++ b/lib/constants/urls.dart @@ -7,6 +7,4 @@ class Urls { static const String adGuardHomeReleasesTags = "https://api.github.com/repos/AdGuardTeam/AdGuardHome/releases/tags"; static const String googleSearchUrl = "https://www.google.com/search"; static const String connectionInstructions = "https://github.com/JGeek00/adguard-home-manager/wiki/Create-a-connection"; - static const String appDetailsWebpage = "https://apps.jgeek00.com/jlfed8mcgyz6laf"; - static const String jgeek00AppsWebpage = "https://apps.jgeek00.com"; } \ No newline at end of file diff --git a/lib/functions/check_app_updates.dart b/lib/functions/check_app_updates.dart new file mode 100644 index 0000000..e002556 --- /dev/null +++ b/lib/functions/check_app_updates.dart @@ -0,0 +1,61 @@ +import 'dart:io'; + +import 'package:store_checker/store_checker.dart'; + +import 'package:adguard_home_manager/functions/compare_versions.dart'; +import 'package:adguard_home_manager/services/external_requests.dart'; +import 'package:adguard_home_manager/models/github_release.dart'; + +Future checkAppUpdates({ + required String currentBuildNumber, + required void Function(GitHubRelease?) setUpdateAvailable, + required Source installationSource, + required bool isBeta +}) async { + var result = isBeta + ? await ExternalRequests.getReleasesGitHub() + : await ExternalRequests.getReleaseData(); + + if (result.successful == true) { + late GitHubRelease gitHubRelease; + if (isBeta) { + gitHubRelease = (result.content as List).firstWhere((r) => r.prerelease == true); + } + else { + gitHubRelease = result.content as GitHubRelease; + } + + final update = gitHubUpdateExists( + currentBuildNumber: currentBuildNumber, + gitHubRelease: gitHubRelease, + isBeta: isBeta + ); +print(update); + if (update == true) { + setUpdateAvailable(gitHubRelease); + + if (Platform.isAndroid) { + if ( + installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE || + installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER || + installationSource == Source.UNKNOWN + ) { + return gitHubRelease; + } + else { + return null; + } + } + else if (Platform.isIOS) { + return null; + } + else { + return gitHubRelease; + } + } + else { + setUpdateAvailable(null); + } + } + return null; +} \ No newline at end of file diff --git a/lib/functions/is_ip.dart b/lib/functions/is_ip.dart deleted file mode 100644 index 73ee1e9..0000000 --- a/lib/functions/is_ip.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; - -bool isIpAddress(String value) { - if (Regexps.ipv4Address.hasMatch(value) || Regexps.ipv6Address.hasMatch(value)) { - return true; - } - else { - return false; - } -} \ No newline at end of file diff --git a/lib/functions/open_url.dart b/lib/functions/open_url.dart index 599eea0..b435e47 100644 --- a/lib/functions/open_url.dart +++ b/lib/functions/open_url.dart @@ -1,43 +1,30 @@ import 'dart:io'; -import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as flutter_custom_tabs; -import 'package:url_launcher/url_launcher.dart' as url_launcher; -import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:flutter_web_browser/flutter_web_browser.dart'; +import 'package:url_launcher/url_launcher.dart'; void openUrl(String url) async { - if (!(url.startsWith("http") || url.startsWith("https"))) { - try { - url_launcher.launchUrl(Uri.parse(url)); - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); - } - return; - } - if (Platform.isAndroid || Platform.isIOS) { - try { - await flutter_custom_tabs.launchUrl( - Uri.parse(url), - customTabsOptions: const flutter_custom_tabs.CustomTabsOptions( - shareState: flutter_custom_tabs.CustomTabsShareState.browserDefault, - urlBarHidingEnabled: true, - showTitle: true, - ), - safariVCOptions: const flutter_custom_tabs.SafariViewControllerOptions( - barCollapsingEnabled: true, - dismissButtonStyle: flutter_custom_tabs.SafariViewControllerDismissButtonStyle.close, - ), - ); - } catch (e, stackTrace) { - url_launcher.launchUrl(Uri.parse(url)); - Sentry.captureException(e, stackTrace: stackTrace); - } + FlutterWebBrowser.openWebPage( + url: url, + customTabsOptions: const CustomTabsOptions( + instantAppsEnabled: true, + showTitle: true, + urlBarHidingEnabled: false, + ), + safariVCOptions: const SafariViewControllerOptions( + barCollapsingEnabled: true, + dismissButtonStyle: SafariViewControllerDismissButtonStyle.close, + modalPresentationCapturesStatusBarAppearance: true, + ) + ); } else { - try { - url_launcher.launchUrl(Uri.parse(url)); - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + throw 'Could not launch $url'; } } } \ No newline at end of file diff --git a/lib/functions/snackbar.dart b/lib/functions/snackbar.dart index d0d7bc0..123ef3f 100644 --- a/lib/functions/snackbar.dart +++ b/lib/functions/snackbar.dart @@ -5,16 +5,14 @@ import 'package:flutter/material.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/config/globals.dart'; -void showSnackbar({ +void showSnacbkar({ required AppConfigProvider appConfigProvider, required String label, required Color color, - Color? labelColor, - GlobalKey? key, + Color? labelColor }) async { - final GlobalKey scaffoldKey = key ?? scaffoldMessengerKey; if (appConfigProvider.showingSnackbar == true) { - scaffoldKey.currentState?.clearSnackBars(); + scaffoldMessengerKey.currentState?.clearSnackBars(); await Future.delayed(const Duration(milliseconds: 500)); } appConfigProvider.setShowingSnackbar(true); @@ -28,7 +26,7 @@ void showSnackbar({ ), backgroundColor: color, ); - scaffoldKey.currentState?.showSnackBar(snackBar).closed.then( + scaffoldMessengerKey.currentState?.showSnackBar(snackBar).closed.then( (value) => appConfigProvider.setShowingSnackbar(false) ); } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f923e8b..b9ba629 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -24,7 +24,7 @@ "invalidUsernamePassword": "Invalid username or password", "tooManyAttempts": "Too many attempts. Try again later.", "cantReachServer": "Can't reach server. Check connection data.", - "sslError": "Handshake exception. Cannot establish a secure connection with the server. This can be a SSL error. Go to Settings > Advanced settings and enable Override SSL validation.", + "sslError": "SSL error. Go to Settings > Advanced settings and enable Override SSL validation.", "unknownError": "Unknown error", "connectionNotCreated": "Connection couldn't be created", "connecting": "Connecting...", @@ -688,123 +688,5 @@ "yourVersion": "Your version: {version}", "minimumRequiredVersion": "Minimum required version: {version}", "topUpstreams": "Top upstreams", - "averageUpstreamResponseTime": "Average upstream response time", - "dhcpNotAvailable": "The DHCP server is not available.", - "osServerInstalledIncompatible": "The OS where the server is installed is not compatible with this feature.", - "resetSettings": "Reset settings", - "resetEncryptionSettingsDescription": "Are you sure you want to reset to default values the encryption settings?", - "resettingConfig": "Resetting configuration...", - "configurationResetSuccessfully": "Configuration resetted successfully", - "configurationResetError": "The configuration couldn't be resetted", - "testUpstreamDnsServers": "Test upstream DNS servers", - "errorTestUpstreamDns": "Error when testing upstream DNS servers.", - "useCustomIpEdns": "Use custom IP for EDNS", - "useCustomIpEdnsDescription": "Allow to use custom IP for EDNS", - "sortingOptions": "Sorting options", - "fromHighestToLowest": "From highest to lowest", - "fromLowestToHighest": "From lowest to highest", - "queryLogsAndStatistics": "Query logs and statistics", - "ignoreClientQueryLog": "Ignore this client in query log", - "ignoreClientStatistics": "Ignore this client in statistics", - "savingChanges": "Saving changes...", - "fallbackDnsServers": "Fallback DNS servers", - "fallbackDnsServersDescription": "Configure fallback DNS servers", - "fallbackDnsServersInfo": "List of fallback DNS servers used when upstream DNS servers are not responding. The syntax is the same as in the main upstreams field above.", - "noFallbackDnsAdded": "No fallback DNS servers added.", - "blockedResponseTtl": "Blocked response TTL", - "blockedResponseTtlDescription": "Specifies for how many seconds the clients should cache a filtered response", - "invalidValue": "Invalid value", - "noDataChart": "There's no data to display this chart.", - "noData": "No data", - "unblockClient": "Unblock client", - "blockingClient": "Blocking client...", - "unblockingClient": "Unblocking client...", - "upstreamDnsCacheConfiguration": "DNS upstream cache configuration", - "enableDnsCachingClient": "Enable DNS caching for this client", - "dnsCacheSize": "DNS cache size", - "nameInvalid": "Name is required", - "oneIdentifierRequired": "At least one identifier is required", - "dnsCacheNumber": "DNS cache size must be a number", - "errors": "Errors", - "redirectHttpsWarning": "If you have enabled \"Redirect to HTTPS automatically\" on your AdGuard Home server, you must select an HTTPS connection and use the HTTPS port of your server.", - "logsSettingsDescription": "Configure query logs", - "ignoredDomains": "Ignored domains", - "noIgnoredDomainsAdded": "No domains to ignore added", - "pauseServiceBlocking": "Pause service blocking", - "newSchedule": "New schedule", - "editSchedule": "Edit schedule", - "timezone": "Timezone", - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "saturday": "Saturday", - "sunday": "Sunday", - "from": "From", - "to": "To", - "selectStartTime": "Select start time", - "selectEndTime": "Select end time", - "startTimeBeforeEndTime": "Start time must be before end time.", - "noBlockingScheduleThisDevice": "There's no blocking schedule for this device.", - "selectTimezone": "Select a timezone", - "selectClientsFiltersInfo": "Select the clients you want to display. If no clients are selected, all will be displayed.", - "noDataThisSection": "There's no data for this section.", - "statisticsSettings": "Statistics settings", - "statisticsSettingsDescription": "Configure data collection for statistics", - "loadingStatisticsSettings": "Loading statistics settings...", - "statisticsSettingsLoadError": "An error occured when loading statistics settings.", - "statisticsConfigUpdated": "Statistics settings updated successfully", - "statisticsConfigNotUpdated": "Statistics settings couldn't be updated", - "customTimeInHours": "Custom time (in hours)", - "invalidTime": "Invalid time", - "removeDomain": "Remove domain", - "addDomain": "Add domain", - "notLess1Hour": "Time cannot be less than 1 hour", - "rateLimit": "Rate limit", - "subnetPrefixLengthIpv4": "Subnet prefix length for IPv4", - "subnetPrefixLengthIpv6": "Subnet prefix length for IPv6", - "rateLimitAllowlist": "Rate limit allowlist", - "rateLimitAllowlistDescription": "IP addresses excluded from rate limiting", - "dnsOptions": "DNS options", - "editor": "Editor", - "editCustomRules": "Edit custom rules", - "savingCustomRules": "Saving custom rules...", - "customRulesUpdatedSuccessfully": "Custom rules updated successfully", - "customRulesNotUpdated": "Custom rules could not be updated", - "reorder": "Reorder", - "showHide": "Show/hide", - "noElementsReorderMessage": "Enable some elements on the show/hide tab to reorder them here.", - "enablePlainDns": "Enable plain DNS", - "enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol.", - "date": "Date", - "loadingChangelog": "Loading changelog...", - "invalidIpOrUrl": "Invalid IP address or URL", - "addPersistentClient": "Add as a persistent client", - "blockThisClientOnly": "Block for this client only", - "unblockThisClientOnly": "Unblock for this client only", - "domainBlockedThisClient": "{domain} blocked for this client", - "domainUnblockedThisClient": "{domain} unblocked for this client", - "disallowThisClient": "Disallow this client", - "allowThisClient": "Allow this client", - "clientAllowedSuccessfully": "Client allowed successfully", - "clientDisallowedSuccessfully": "Client disallowed successfully", - "changesNotSaved": "Changes could not be saved", - "allowingClient": "Allowing client...", - "disallowingClient": "Disallowing client...", - "clientIpCopied": "Client IP copied to the clipboard", - "clientNameCopied": "Client name copied to the clipboard", - "dnsServerAddressCopied": "DNS server address copied to the clipboard", - "select": "Select", - "liveLogs": "Live logs", - "hereWillAppearRealtimeLogs": "Here there will appear the logs on realtime.", - "applicationDetails": "Application details", - "applicationDetailsDescription": "App repository, stores where it's available, and more", - "myOtherApps": "My other apps", - "myOtherAppsDescription": "Check my other apps, make a donation, contact support, and more", - "topToBottom": "From top to bottom", - "bottomToTop": "From bottom to top", - "upstreamTimeout": "Upstream timeout", - "upstreamTimeoutHelper": "Specifies the number of seconds to wait for a response from the upstream server", - "fieldCannotBeEmpty": "This field cannot be empty" + "averageUpstreamResponseTime": "Average upstream response time" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index c93c501..0e1566c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -24,7 +24,7 @@ "invalidUsernamePassword": "Usuario o contraseña no válidos.", "tooManyAttempts": "Demasiados intentos. Prueba de nuevo más tarde.", "cantReachServer": "No se puede alcanzar el servidor. Comprueba los datos de conexión.", - "sslError": "Handshake exception. No se ha podido establecer una conexión segura con el servidor. Es posible que sea un error de SSL. Ve a Ajustes > Ajustes avanzados y activa No comprobar SSL.", + "sslError": "Error de SSL. Ve a Ajustes > Ajustes avanzados y activa No comprobar SSL.", "unknownError": "Error desconocido", "connectionNotCreated": "No se pudo crear la conexión", "connecting": "Conectando...", @@ -403,7 +403,7 @@ "dnsRewriteRuleDeleted": "Reescritura DNS eliminada correctamente", "dnsRewriteRuleNotDeleted": "La reescritura DNS no pudo ser eliminada", "addDnsRewrite": "Añadir reescritura DNS", - "addingRewrite": "Añadiendo reescritura...", + "addingRewrite": "Añadiend reescritura...", "dnsRewriteRuleAdded": "Regla de reescritura DNS añadida correctamente", "dnsRewriteRuleNotAdded": "La regla de reescritura DNS no ha podido ser añadida", "logsSettings": "Ajustes de registros", @@ -688,123 +688,5 @@ "yourVersion": "Tu versión: {version}", "minimumRequiredVersion": "Versión mínima requerida: {version}", "topUpstreams": "DNS de subida más frecuentes", - "averageUpstreamResponseTime": "Tiempo promedio de respuesta upstream", - "dhcpNotAvailable": "El servidor DHCP no está disponible.", - "osServerInstalledIncompatible": "El SO donde el servidor está instalado no es compatible con esta característica.", - "resetSettings": "Resetear configuración", - "resetEncryptionSettingsDescription": "Estás seguro que deseas restaurar a valores por defecto la configuración de encriptación?", - "resettingConfig": "Reseteando configuración...", - "configurationResetSuccessfully": "Configuración reseteada correctamente", - "configurationResetError": "La configuración no ha podido ser reseteada", - "testUpstreamDnsServers": "Probar servidores DNS de subida", - "errorTestUpstreamDns": "Error al probar los servidores DNS de subida.", - "useCustomIpEdns": "Usar IP personalizada para EDNS", - "useCustomIpEdnsDescription": "Permitir usar IP personalizada para EDNS", - "sortingOptions": "Opciones de ordenación", - "fromHighestToLowest": "De mayor a menor", - "fromLowestToHighest": "De menor a mayor", - "queryLogsAndStatistics": "Registro de consultas y estadísticas", - "ignoreClientQueryLog": "Ignorar este cliente en el registro de consultas", - "ignoreClientStatistics": "Ignorar este cliente en las estadísticas", - "savingChanges": "Guardando cambios...", - "fallbackDnsServers": "Servidores DNS alternativos", - "fallbackDnsServersDescription": "Configura los servidores DNS alternativos", - "fallbackDnsServersInfo": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.", - "noFallbackDnsAdded": "No hay servidores DNS alternativos añadidos.", - "blockedResponseTtl": "Respuesta TTL bloqueada", - "blockedResponseTtlDescription": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada", - "invalidValue": "Valor no válido", - "noDataChart": "No hay datos para mostrar este gráfico.", - "noData": "No hay datos", - "unblockClient": "Desbloquear cliente", - "blockingClient": "Bloqueando cliente...", - "unblockingClient": "Desbloqueando cliente...", - "upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream", - "enableDnsCachingClient": "Habilitar caché de DNS para este cliente", - "dnsCacheSize": "Tamaño de caché de DNS", - "nameInvalid": "Se requiere un nombre", - "oneIdentifierRequired": "Se require al menos un identificador", - "dnsCacheNumber": "El tamaño de caché de DNS debe ser un número", - "errors": "Errores", - "redirectHttpsWarning": "Si tienes activado \"Redireccionar a HTTPS automáticamente\" en tu servidor AdGuard Home, debes seleccionar una conexión HTTPS y utilizar el puerto de HTTPS de tu servidor.", - "logsSettingsDescription": "Configura los registros de peticiones", - "ignoredDomains": "Dominios ignorados", - "noIgnoredDomainsAdded": "No hay añadidos dominios para ignorar", - "pauseServiceBlocking": "Pausa del servicio de bloqueo", - "newSchedule": "Nueva programación", - "editSchedule": "Editar programación", - "timezone": "Zona horaria", - "monday": "Lunes", - "tuesday": "Martes", - "wednesday": "Miércoles", - "thursday": "Jueves", - "friday": "Viernes", - "saturday": "Sábado", - "sunday": "Domingo", - "from": "Desde", - "to": "Hasta", - "selectStartTime": "Seleccionar hora de inicio", - "selectEndTime": "Seleccionar hora de fin", - "startTimeBeforeEndTime": "La hora de inicio debe ser anterior a la hora de fin.", - "noBlockingScheduleThisDevice": "No hay programación de bloqueo para este dispositivo.", - "selectTimezone": "Selecciona una zona horaria", - "selectClientsFiltersInfo": "Selecciona los clientes que quieres mostrar. Si no hay clientes seleccionados, se mostrarán todos.", - "noDataThisSection": "No hay datos para esta sección.", - "statisticsSettings": "Ajustes de estadísticas", - "statisticsSettingsDescription": "Configura la recolección de datos para estadísticas", - "loadingStatisticsSettings": "Cargando ajustes de estadísticas...", - "statisticsSettingsLoadError": "Ocurrió un error al cargar los ajustes de estadísticas.", - "statisticsConfigNotUpdated": "La configuración de estadísticas no pudo ser actualizada.", - "statisticsConfigUpdated": "Configuración de estadísticas actualizada correctamente.", - "customTimeInHours": "Tiempo personalizado (en horas)", - "invalidTime": "Tiempo no válido", - "removeDomain": "Eliminar dominio", - "addDomain": "Añadir dominio", - "notLess1Hour": "El tiempo no puede ser inferior a 1 hora", - "rateLimit": "Limitación de velocidad", - "subnetPrefixLengthIpv4": "Longitud del prefijo de subred para IPv4", - "subnetPrefixLengthIpv6": "Longitud del prefijo de subred para IPv6", - "rateLimitAllowlist": "Lista de permitidos de limitación de velocidad", - "rateLimitAllowlistDescription": "Direcciones IP excluidas de la limitación de velocidad", - "dnsOptions": "Opciones de DNS", - "editor": "Editor", - "editCustomRules": "Editar reglas personalizadas", - "savingCustomRules": "Guardando reglas personalizadas...", - "customRulesUpdatedSuccessfully": "Reglas personalizadas actualizadas correctamente", - "customRulesNotUpdated": "Las reglas personalizadas no pudieron ser actualizadas", - "reorder": "Reordenar", - "showHide": "Mostrar/ocultar", - "noElementsReorderMessage": "Activa algunos elementos en la pestaña de mostrar/ocultar para reordenarlos aquí.", - "enablePlainDns": "Activar DNS simple (sin cifrado)", - "enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado.", - "date": "Fecha", - "loadingChangelog": "Cargando registro de cambios...", - "invalidIpOrUrl": "Dirección IP o URL no válida", - "addPersistentClient": "Añadir como cliente persistente", - "blockThisClientOnly": "Bloquear sólo para este cliente", - "unblockThisClientOnly": "Desbloquear sólo para este cliente", - "domainBlockedThisClient": "{domain} bloqueado para este cliente", - "domainUnblockedThisClient": "{domain} desbloqueado para este cliente", - "disallowThisClient": "No permitir este cliente", - "allowThisClient": "Permitir este cliente", - "clientAllowedSuccessfully": "Cliente permitido correctamente", - "clientDisallowedSuccessfully": "Cliente no permitido correctamente", - "changesNotSaved": "Los cambios no han podido ser guardados", - "allowingClient": "Permitiendo cliente...", - "disallowingClient": "No permitiendo cliente...", - "clientIpCopied": "Dirección IP del cliente copiada al portapapeles", - "clientNameCopied": "Nombre del cliente copiado al portapapeles", - "dnsServerAddressCopied": "Dirección del servidor DNS copiada al portapapeles", - "select": "Seleccionar", - "liveLogs": "Registros en directo", - "hereWillAppearRealtimeLogs": "Aquí aparecerán los registros en tiempo real.", - "applicationDetails": "Detalles de la aplicación", - "applicationDetailsDescription": "Repositorio de la app, tiendas donde está disponible, y más", - "myOtherApps": "Mis otras apps", - "myOtherAppsDescription": "Comprueba mis otras apps, hacer una donación, contactar al soporte, y más", - "topToBottom": "Desde arriba hacia abajo", - "bottomToTop": "Desde abajo hacia arriba", - "upstreamTimeout": "Tiempo de espera del upstream", - "upstreamTimeoutHelper": "Especifica el número de segundos que se debe esperar para recibir una respuesta del servidor upstream", - "fieldCannotBeEmpty": "El campo no puede estar vacío" + "averageUpstreamResponseTime": "Tiempo promedio de respuesta upstream" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb deleted file mode 100644 index c0a6063..0000000 --- a/lib/l10n/app_ru.arb +++ /dev/null @@ -1,730 +0,0 @@ -{ - "home": "Главная", - "settings": "Настройки", - "connect": "Подключиться", - "servers": "Серверы", - "createConnection": "Создать подключение", - "editConnection": "Edit connection", - "name": "Имя", - "ipDomain": "IP-адрес или домен", - "path": "Путь", - "port": "Порт", - "username": "Логин", - "password": "Пароль", - "defaultServer": "Сервер по умолчанию", - "general": "Основное", - "connection": "Тип подключения", - "authentication": "Аутентификация", - "other": "Прочее", - "invalidPort": "Неверный порт", - "invalidPath": "Неверный путь", - "invalidIpDomain": "Неверный IP-адрес или домен", - "ipDomainNotEmpty": "IP-адрес или домен не могут быть пустыми", - "nameNotEmpty": "Имя не может быть пустым", - "invalidUsernamePassword": "Неверный логин или пароль", - "tooManyAttempts": "Слишком много попыток. Попробуйте позднее.", - "cantReachServer": "Не удаётся установить соединение с сервером. Проверьте настройки подключения.", - "sslError": "Ошибка SSL. Перейдите в «Настройки» > «Дополнительные настройки» и активируйте «Не проверять SSL-сертификат».", - "unknownError": "Неизвестная ошибка", - "connectionNotCreated": "Не удалось создать подключение", - "connecting": "Подключение...", - "connected": "Подключено", - "selectedDisconnected": "Выбран, но отключён", - "connectionDefaultSuccessfully": "Подключение успешно установлено как «подключение по умолчанию».", - "connectionDefaultFailed": "Не удалось установить «подключением по умолчанию».", - "noSavedConnections": "Нет сохранённых подключений", - "cannotConnect": "Не удается подключиться к серверу", - "connectionRemoved": "Подключение удалено успешно", - "connectionCannotBeRemoved": "Подключение не может быть удалено.", - "remove": "Удалить", - "removeWarning": "Вы уверены, что хотите удалить соединение с этим сервером AdGuard Home?", - "cancel": "Отмена", - "defaultConnection": "Подключение по умолчанию", - "setDefault": "Подключаться по умолчанию", - "edit": "Редактировать", - "delete": "Удалить", - "save": "Сохранить", - "serverStatus": "Статус сервера", - "connectionNotUpdated": "Соединение не было обновлено", - "ruleFilteringWidget": "Правила фильтрации", - "safeBrowsingWidget": "Безопасная навигация", - "parentalFilteringWidget": "Родительский контроль", - "safeSearchWidget": "Безопасный поиск", - "ruleFiltering": "Правила фильтрации", - "safeBrowsing": "Безопасная\nнавигация", - "parentalFiltering": "Родительский\nконтроль", - "safeSearch": "Безопасный поиск", - "serverStatusNotRefreshed": "Не удалось обновить статус сервера", - "loadingStatus": "Загрузка...", - "errorLoadServerStatus": "Не удалось получить статус сервера", - "topQueriedDomains": "Часто запрашиваемые\nдомены", - "viewMore": "Показать больше", - "topClients": "Частые клиенты", - "topBlockedDomains": "Часто блокируемые домены", - "appSettings": "Настройки приложения", - "theme": "Тема", - "light": "Светлая", - "dark": "Тёмная", - "systemDefined": "Системная тема", - "close": "Закрыть", - "connectedTo": "Подключено к:", - "selectedServer": "Выбранный сервер:", - "noServerSelected": "Нет выбранных серверов", - "manageServer": "Управление сервером", - "allProtections": "Защита", - "userNotEmpty": "Логин не может быть пустым", - "passwordNotEmpty": "Пароль не может быть пустым", - "examplePath": "Например: /adguard", - "helperPath": "Если используется реверсивный прокси", - "aboutApp": "О приложении", - "appVersion": "Версия приложения", - "createdBy": "Автор", - "clients": "Клиенты", - "allowed": "Обработан", - "blocked": "Заблокировано", - "noClientsList": "Список клиентов пуст", - "activeClients": "Активные", - "removeClient": "Удалить запись", - "removeClientMessage": "Вы уверены, что хотите удалить данную запись из списка?", - "confirm": "Ок", - "removingClient": "Удаление клиента...", - "clientNotRemoved": "Клиент не может быть удалён из списка", - "addClient": "Добавить клиента", - "list": "Список", - "ipAddress": "IP-адреса", - "ipNotValid": "Недопустимый IP-адрес", - "clientAddedSuccessfully": "Запись успешно добавлена в список", - "addingClient": "Добавление клиента...", - "clientNotAdded": "Клиент не может быть внесён в список", - "clientAnotherList": "Данный клиент уже занесён в один из списков", - "noSavedLogs": "Нет сохранённых журналов", - "logs": "Журнал", - "copyLogsClipboard": "Скопировать журнал в буфер обмена", - "logsCopiedClipboard": "Журнал скопирован в буфер обмена", - "advancedSettings": "Дополнительные настройки", - "dontCheckCertificate": "Не проверять SSL-сертификат", - "dontCheckCertificateDescription": "Переопределяет проверку SSL-сертификата сервера", - "advancedSetupDescription": "Расширенные параметры", - "settingsUpdatedSuccessfully": "Настройки успешно обновлены.", - "cannotUpdateSettings": "Не удалось обновить настройки.", - "restartAppTakeEffect": "Перезапустите приложение", - "loadingLogs": "Загрузка журнала...", - "logsNotLoaded": "Не удалось загрузить журнал", - "processed": "Обработан\nБез списка", - "processedRow": "Обработан", - "blockedBlacklist": "Заблокирован\nЧёрный список", - "blockedBlacklistRow": "Заблокирован Чёрным списком", - "blockedSafeBrowsing": "Заблокирован\nБезопасная навигация", - "blockedSafeBrowsingRow": "Заблокировано Безопасной навигацией", - "blockedParental": "Заблокирован\nРодительский контроль", - "blockedParentalRow": "Заблокировано Родительским контролем", - "blockedInvalid": "Заблокировано\nНеверный", - "blockedInvalidRow": "Заблокирован (Неверный)", - "blockedSafeSearch": "Заблокирован\nБезопасный поиск", - "blockedSafeSearchRow": "Заблокировано Безопасным поиском", - "blockedService": "Заблокирован\nЗаблокированный сервис", - "blockedServiceRow": "Заблокирован (заблокированный сервис)", - "processedWhitelist": "Разрешён\nБелый список", - "processedWhitelistRow": "Разрешён Белым списком", - "processedError": "Обработан\nОшибка", - "processedErrorRow": "Обработан (ошибка)", - "rewrite": "Переписан", - "status": "Статус", - "result": "Результат", - "time": "Время", - "blocklist": "Блок-лист", - "request": "Запрос", - "domain": "Хост", - "type": "Тип", - "clas": "Класс", - "response": "Ответ", - "dnsServer": "DNS-сервер", - "elapsedTime": "Затрачено", - "responseCode": "Код ответа", - "client": "Клиент", - "deviceIp": "IP-адрес", - "deviceName": "Имя устройства", - "logDetails": "Детали запроса", - "blockingRule": "Правило блокировки", - "blockDomain": "Заблокировать домен", - "couldntGetFilteringStatus": "Не удалось получить журнал фильтрации", - "unblockDomain": "Разблокировать домен", - "userFilteringRulesNotUpdated": "Не удалось обновить пользовательские правила фильтрации", - "userFilteringRulesUpdated": "Пользовательские правила фильтрации успешно обновлены", - "savingUserFilters": "Сохранение пользовательских правил фильтрации...", - "filters": "Фильтры", - "logsOlderThan": "Записи журнала старше, чем", - "responseStatus": "Статус ответа", - "selectTime": "Выберите время", - "notSelected": "Не выбрано", - "resetFilters": "Сбросить фильтры", - "noLogsDisplay": "Нет записей в журнале", - "noLogsThatOld": "Возможно, что за это выбранное время записи журнала не сохранены. Попробуйте выбрать более позднее время.", - "apply": "Применить", - "selectAll": "Отметить все", - "unselectAll": "Отменить выбор всех", - "all": "Все", - "filtered": "Отфильтрованные", - "checkAppLogs": "Проверьте журналы приложения", - "refresh": "Обновить", - "search": "Поиск", - "dnsQueries": "DNS-запросы", - "average": "Среднее", - "blockedFilters": "Заблокировано\nфильтрами", - "malwarePhishingBlocked": "Заблокированные\nвредоносные и\nфишинговые сайты", - "blockedAdultWebsites": "Заблокированные\n«взрослые» сайты", - "generalSettings": "Основные настройки", - "generalSettingsDescription": "Различные настройки", - "hideZeroValues": "Скрывать нулевые значения", - "hideZeroValuesDescription": "Скрывать блоки с нулевыми значениями на домашнем экране", - "webAdminPanel": "Веб-панель администрирования", - "visitGooglePlay": "Посетить страницу в Google Play", - "gitHub": "Исходный код приложения доступен на GitHub", - "blockClient": "Заблокировать клиента", - "selectTags": "Выбрать теги клиента", - "noTagsSelected": "Нет выбранных тегов", - "tags": "Теги", - "identifiers": "Идентификаторы", - "identifier": "Идентификатор", - "identifierHelper": "IP-адрес, CIDR, MAC или ClientID", - "noIdentifiers": "Идентификаторы не добавлены", - "useGlobalSettings": "Глобальные настройки", - "enableFiltering": "Включить фильтрацию", - "enableSafeBrowsing": "Безопасная навигация", - "enableParentalControl": "Родительский контроль", - "enableSafeSearch": "Безопасный поиск", - "blockedServices": "Заблокированные сервисы", - "selectBlockedServices": "Заблокированные сервисы", - "noBlockedServicesSelected": "Нет заблокированных сервисов", - "services": "Сервисы", - "servicesBlocked": "в блокировке", - "tagsSelected": "выбрано", - "upstreamServers": "Upstream DNS-серверы", - "serverAddress": "Адрес сервера", - "noUpstreamServers": "Нет upstream DNS-серверов.", - "willBeUsedGeneralServers": "Будут использоваться общие upstream DNS-сервера.", - "added": "Сохранённые", - "clientUpdatedSuccessfully": "Настройки клиента успешно обновлены", - "clientNotUpdated": "Не удалось обновить настройки клиента", - "clientDeletedSuccessfully": "Клиент успешно удалён", - "clientNotDeleted": "Не удалось удалить клиента", - "options": "Параметры", - "loadingFilters": "Загрузка фильтров...", - "filtersNotLoaded": "Не удалось загрузить фильтры.", - "whitelists": "Белые списки DNS", - "blacklists": "Чёрные списки DNS", - "rules": "Количество правил", - "customRules": "Пользовательские правила фильтрации", - "enabledRules": "Активных правил", - "enabled": "Включён", - "disabled": "Отключён", - "rule": "Правило", - "addCustomRule": "Добавить пользовательское правило фильтрации", - "removeCustomRule": "Удалить пользовательское правило фильтрации", - "removeCustomRuleMessage": "Вы уверены, что хотите удалить данное пользовательское правило фильтрации?", - "updatingRules": "Обновление пользовательских правил фильтрации...", - "ruleRemovedSuccessfully": "Правило успешно удалено", - "ruleNotRemoved": "Не удаётся удалить данное правило", - "ruleAddedSuccessfully": "Правило успешно добавлено", - "ruleNotAdded": "Не удаётся добавить правило", - "noCustomFilters": "Нет пользовательских правил фильтрации", - "noBlockedClients": "Нет запрещённых клиентов", - "noBlackLists": "Нет чёрных списков", - "noWhiteLists": "Нет белых списков", - "addWhitelist": "Добавить белый список", - "addBlacklist": "Добавить чёрный список", - "urlNotValid": "Неверный URL или абсолютный путь", - "urlAbsolutePath": "URL-адрес или абсолютный путь", - "addingList": "Добавление списка...", - "listAdded": "Список успешно добавлен. Добавлены:", - "listAlreadyAdded": "Список уже был добавлен", - "listUrlInvalid": "Неверный URL-адрес списка", - "listNotAdded": "Не удаётся добавить список", - "listDetails": "Параметры списка", - "listType": "Тип", - "whitelist": "Белый список", - "blacklist": "Чёрный список", - "latestUpdate": "Последнее обновление", - "disable": "Отключить", - "enable": "Включить", - "currentStatus": "Текущий статус", - "listDataUpdated": "Параметры списка успешно обновлены", - "listDataNotUpdated": "Не удалось обновить параметры списка", - "updatingListData": "Обновление параметров списка...", - "editWhitelist": "Редактировать белый список", - "editBlacklist": "Редактировать чёрный список", - "deletingList": "Удаление списка...", - "listDeleted": "Список успешно удалён", - "listNotDeleted": "Не удалось удалить список", - "deleteList": "Удалить список", - "deleteListMessage": "Вы уверены, что хотите удалить этот список? Это действие нельзя отменить.", - "serverSettings": "Настройки сервера", - "serverInformation": "Информация о сервере", - "serverInformationDescription": "Информация о сервере и текущий статус", - "loadingServerInfo": "Загрузка информации о сервере...", - "serverInfoNotLoaded": "Не удалось загрузить информацию о сервере.", - "dnsAddresses": "DNS-адреса", - "seeDnsAddresses": "Посмотреть DNS-адреса", - "dnsPort": "DNS-порт", - "httpPort": "HTTP-порт", - "protectionEnabled": "Защита активна", - "dhcpAvailable": "DHCP доступен", - "serverRunning": "Сервер запущен", - "serverVersion": "Версия сервера", - "serverLanguage": "Язык сервера", - "yes": "Да", - "no": "Нет", - "allowedClients": "Разрешённые клиенты", - "disallowedClients": "Запрещённые клиенты", - "disallowedDomains": "Неразрешённые домены", - "accessSettings": "Настройки доступа", - "accessSettingsDescription": "Настройка правил доступа к серверу", - "loadingClients": "Загрузка клиентов...", - "clientsNotLoaded": "Не удалось загрузить список клиентов.", - "noAllowedClients": "Нет разрешённых клиентов", - "allowedClientsDescription": "Если в списке есть записи, AdGuard Home будет принимать запросы только от этих клиентов.", - "blockedClientsDescription": "Если в списке есть записи, AdGuard Home будет игнорировать запросы от этих клиентов. Это поле игнорируется, если список разрешённых клиентов содержит записи.", - "disallowedDomainsDescription": "AdGuard Home будет игнорировать DNS-запросы с этими доменами. Такие DNS-запросы не будут отображаться в журнале.", - "addClientFieldDescription": "CIDR, IP-адрес или ClientID", - "clientIdentifier": "Идентификатор клиента", - "allowClient": "Разрешить клиент", - "disallowClient": "Запретить клиента", - "noDisallowedDomains": "Нет запрещенных доменов", - "domainNotAdded": "Не удалось добавить домен", - "statusSelected": "выбран статус", - "updateLists": "Проверить обновления", - "checkHostFiltered": "Проверить хост", - "updatingLists": "Обновление списков...", - "listsUpdated": "списки обновлены", - "listsNotUpdated": "Не удалось обновить списки", - "listsNotLoaded": "Не удалось загрузить списки", - "domainNotValid": "Недействительный домен", - "check": "Проверить", - "checkingHost": "Проверка хоста...", - "errorCheckingHost": "Не удалось проверить хост", - "block": "Запретить", - "unblock": "Разрешить", - "custom": "Своё правило", - "addImportant": "Добавить $important", - "howCreateRules": "Как создать пользовательские правила", - "examples": "Примеры", - "example1": "Заблокировать доступ к домену example.org и всем его поддоменам.", - "example2": "Разблокировать доступ к домену example.org и всем его поддоменам.", - "example3": "Добавлять комментарий.", - "example4": "Блокировать доступ к доменам, соответствующим заданному регулярному выражению.", - "moreInformation": "Больше информации", - "addingRule": "Добавление правила...", - "deletingRule": "Удаление правила...", - "enablingList": "Включение списка...", - "disablingList": "Отключение списка...", - "disableFiltering": "Отключить фильтрацию", - "savingList": "Сохранение списка...", - "enablingFiltering": "Включение фильтрации...", - "disablingFiltering": "Отключение фильтрации...", - "filteringStatusUpdated": "Статус фильтрации успешно обновлен", - "filteringStatusNotUpdated": "Не удалось обновить статус фильтрации", - "updateFrequency": "Частота обновления", - "never": "Никогда", - "hour1": "1 час", - "hours12": "12 часов", - "hours24": "24 часа", - "days3": "3 дня", - "days7": "7 дней", - "changingUpdateFrequency": "Обновление...", - "updateFrequencyChanged": "Частота обновления успешно изменена", - "updateFrequencyNotChanged": "Не удаётся изменить частоту обновления", - "updating": "Обновление значений...", - "blockedServicesUpdated": "Заблокированные сервисы успешно обновлены", - "blockedServicesNotUpdated": "Не удаётся обновить заблокированные сервисы", - "insertDomain": "Проверить фильтрацию имени хоста.", - "dhcpSettings": "Настройки DHCP", - "dhcpSettingsDescription": "Настройка DHCP-сервера", - "dhcpSettingsNotLoaded": "Не удалось загрузить настройки DHCP", - "loadingDhcp": "Загрузка настроек DHCP...", - "enableDhcpServer": "Включить DHCP-сервер", - "selectInterface": "Выбрать интерфейс DHCP", - "hardwareAddress": "MAC-адрес", - "gatewayIp": "IP-адрес шлюза", - "ipv4addresses": "Адрес IPv4", - "ipv6addresses": "Адрес IPv6", - "neededSelectInterface": "Необходимо выбрать интерфейс для настройки DHCP-сервера.", - "ipv4settings": "Настройки IPv4", - "startOfRange": "Начало диапазона", - "endOfRange": "Конец диапазона", - "ipv6settings": "Настройки IPv6", - "subnetMask": "Маска подсети", - "subnetMaskNotValid": "Недопустимая маска подсети", - "gateway": "Шлюз", - "gatewayNotValid": "Недопустимый шлюз", - "leaseTime": "Время аренды", - "seconds": "{time} секунд", - "leaseTimeNotValid": "Недопустимый срок аренды", - "restoreConfiguration": "Сбросить конфигурацию", - "restoreConfigurationMessage": "Вы уверены, что хотите продолжить? Это приведет к сбросу всей конфигурации. Данное действие не может быть отменено.", - "changeInterface": "Изменить интерфейс", - "savingSettings": "Сохранение настроек...", - "settingsSaved": "Настройки успешно сохранены", - "settingsNotSaved": "Не удалось сохранить настройки", - "restoringConfig": "Восстановление конфигурации...", - "configRestored": "Конфигурация успешно сброшена", - "configNotRestored": "Не удалось произвести сброс конфигурации", - "dhcpStatic": "Статические аренды DHCP", - "noDhcpStaticLeases": "Не найдено статических аренд DHCP", - "deleting": "Удаление...", - "staticLeaseDeleted": "Статическая аренда DHCP успешно удалена", - "staticLeaseNotDeleted": "Не удалось удалить статическую аренду DHCP", - "deleteStaticLease": "Удалить статическую аренду", - "deleteStaticLeaseDescription": "Статическая аренда DHCP будет удалена. Данное действие не может быть отменено.", - "addStaticLease": "Добавить статическую аренду", - "macAddress": "MAC-адрес", - "macAddressNotValid": "Недопустимый MAC-адрес", - "hostName": "Имя хоста", - "hostNameError": "Имя хоста не может быть пустым", - "creating": "Создание...", - "staticLeaseCreated": "Статическая аренда DHCP успешно создана", - "staticLeaseNotCreated": "Не удалось создать статическую аренду DHCP", - "staticLeaseExists": "Статическая аренда DHCP уже существует", - "serverNotConfigured": "Сервер не настроен", - "restoreLeases": "Сбросить аренды DHCP", - "restoreLeasesMessage": "Вы уверены, что хотите продолжить? Это приведет к сбросу всех существующих аренд DHCP. Данное действие не может быть отменено.", - "restoringLeases": "Сброс аренд DHCP...", - "leasesRestored": "Аренды DHCP успешно сброшены", - "leasesNotRestored": "Не удалось сбросить аренды DHCP", - "dhcpLeases": "Аренды DHCP", - "noLeases": "Не найдено аренд DHCP", - "dnsRewrites": "Перезапись DNS-запросов", - "dnsRewritesDescription": "Настройка пользовательских правил DNS", - "loadingRewriteRules": "Загрузка правил перезаписи...", - "rewriteRulesNotLoaded": "Не удалось загрузить правила перезаписи DNS.", - "noRewriteRules": "Нет правил перезаписи DNS", - "answer": "Ответ", - "deleteDnsRewrite": "Удалить правило перезаписи DNS-запросов", - "deleteDnsRewriteMessage": "Вы уверены, что хотите удалить это правило перезаписи DNS? Данное действие не может быть отменено.", - "dnsRewriteRuleDeleted": "Правило перезаписи DNS успешно удалено", - "dnsRewriteRuleNotDeleted": "Не удалось удалить правило перезаписи DNS", - "addDnsRewrite": "Добавить правило", - "addingRewrite": "Добавление правила перезаписи DNS-запросов...", - "dnsRewriteRuleAdded": "Правило перезаписи DNS успешно добавлено", - "dnsRewriteRuleNotAdded": "Не удалось добавить правило перезаписи DNS", - "logsSettings": "Настройки журнала", - "enableLog": "Включить журнал", - "clearLogs": "Очистить журнал", - "anonymizeClientIp": "Анонимизировать клиента", - "hours6": "6 часов", - "days30": "30 дней", - "days90": "90 дней", - "retentionTime": "Частота ротации журнала запросов", - "selectOneItem": "Выберите один элемент", - "logSettingsNotLoaded": "Не удалось загрузить настройки журнала.", - "updatingSettings": "Обновление настроек...", - "logsConfigUpdated": "Настройки журнала успешно обновлены", - "logsConfigNotUpdated": "Не удалось обновить настройки журнала", - "deletingLogs": "Очистка журнала...", - "logsCleared": "Журнал успешно очищен", - "logsNotCleared": "Не удалось очистить журнал", - "runningHomeAssistant": "Запускается на Home Assistant", - "serverError": "Ошибка сервера", - "noItems": "Здесь нет предметов для показа", - "dnsSettings": "Настройки DNS", - "dnsSettingsDescription": "Настройка подключения к DNS-серверам", - "upstreamDns": "Upstream DNS-серверы", - "bootstrapDns": "Bootstrap DNS-серверы", - "noUpstreamDns": "Не добавлены upstream DNS-серверы.", - "dnsMode": "Режим DNS", - "noDnsMode": "Не выбран режим DNS", - "loadBalancing": "Распределение нагрузки", - "parallelRequests": "Параллельные запросы", - "fastestIpAddress": "Самый быстрый IP-адрес", - "loadBalancingDescription": "Запрашивать по одному серверу за раз. AdGuard Home использует алгоритм взвешенного случайного выбора сервера, так что самый быстрый сервер используется чаще.", - "parallelRequestsDescription": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.", - "fastestIpAddressDescription": "Опросить все DNS-серверы и вернуть самый быстрый IP-адрес из полученных ответов. Это замедлит DNS-запросы, так как нужно будет дождаться ответов со всех DNS-серверов, но улучшит соединение.", - "noBootstrapDns": "Не добавлены bootstrap DNS-серверы.", - "bootstrapDnsServersInfo": "Bootstrap DNS-сервера используются для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали.", - "privateReverseDnsServers": "Приватные серверы для обратного DNS", - "privateReverseDnsServersDescription": "DNS-серверы, которые AdGuard Home использует для локальных PTR-запросов. Эти серверы используются, чтобы получить доменные имена клиентов с приватными IP-адресами, например «192.168.12.34», с помощью обратного DNS. Если список пуст, AdGuard Home использует DNS-серверы по умолчанию вашей ОС.", - "reverseDnsDefault": "По умолчанию AdGuard Home использует следующие обратные DNS-серверы", - "addItem": "Добавить сервер", - "noServerAddressesAdded": "Адреса серверов не указаны.", - "usePrivateReverseDnsResolvers": "Использовать приватные обратные DNS-резолверы", - "usePrivateReverseDnsResolversDescription": "Посылать обратные DNS-запросы для локально обслуживаемых адресов на указанные серверы. Если отключено, AdGuard Home будет отвечать NXDOMAIN на все подобные PTR-запросы, кроме запросов о клиентах, уже известных по DHCP, /etc/hosts и так далее.", - "enableReverseResolving": "Включить запрашивание доменных имён для IP-адресов клиентов", - "enableReverseResolvingDescription": "Определять доменные имена клиентов через PTR-запросы к соответствующим серверам (приватные DNS-серверы для локальных клиентов, upstream-серверы для клиентов с публичным IP-адресом).", - "dnsServerSettings": "Настройки DNS-сервера", - "limitRequestsSecond": "Лимит запросов в секунду", - "valueNotNumber": "Значение - не число", - "enableEdns": "Включить отправку EDNS Client Subnet", - "enableEdnsDescription": "Добавлять опцию EDNS Client Subnet (ECS) к запросам к upstream-серверам, а также записывать присланные клиентами значения в журнал.", - "enableDnssec": "Включить DNSSEC", - "enableDnssecDescription": "Установите флаг DNSSEC в исходящих DNS-запросах и проверьте результат (требуется резолвер с поддержкой DNSSEC).", - "disableResolvingIpv6": "Отключить обработку IPv6-адресов", - "disableResolvingIpv6Description": "Игнорировать все DNS-запросы адресов IPv6 (тип AAAA) и удалять IPv6-данные из ответов типа HTTPS.", - "blockingMode": "Режим блокировки", - "defaultMode": "Стандартный", - "defaultDescription": "Отвечает с нулевым IP-адресом, (0.0.0.0 для A; :: для AAAA) когда заблокировано правилом в стиле Adblock; отвечает с IP-адресом, указанным в правиле, когда заблокировано правилом в стиле файлов hosts", - "refusedDescription": "Отвечает с кодом REFUSED", - "nxdomainDescription": "Отвечает с кодом NXDOMAIN", - "nullIp": "Нулевой IP", - "nullIpDescription": "Отвечает с нулевым IP-адресом (0.0.0.0 для A; :: для AAAA)", - "customIp": "Пользовательский IP", - "customIpDescription": "Отвечает с вручную настроенным IP-адресом", - "dnsCacheConfig": "Настройка кеша DNS", - "cacheSize": "Размер кеша", - "inBytes": "В байтах", - "overrideMinimumTtl": "Переопределить минимальный TTL", - "overrideMinimumTtlDescription": "Расширить короткие TTL-значения (в секундах), полученные с upstream-сервера при кешировании DNS-ответов.", - "overrideMaximumTtl": "Переопределить максимальный TTL", - "overrideMaximumTtlDescription": "Установить максимальное TTL-значение (в секундах) для записей в DNS-кеше.", - "optimisticCaching": "Оптимистическое кеширование", - "optimisticCachingDescription": "AdGuard Home будет отвечать из кеша, даже если ответы в нём неактуальны, и попытается обновить их.", - "loadingDnsConfig": "Загрузка конфигурации DNS...", - "dnsConfigNotLoaded": "Не удалось загрузить конфигурацию DNS.", - "blockingIpv4": "Блокируется IPv4", - "blockingIpv4Description": "IP-адрес, который будет возвращен для заблокированного запроса А", - "blockingIpv6": "Блокируется IPv6", - "blockingIpv6Description": "IP-адрес, который будет возвращен для заблокированного запроса AAAA", - "invalidIp": "Недопустимый IP-адрес", - "dnsConfigSaved": "Конфигурация DNS-сервера сохранена успешно", - "dnsConfigNotSaved": "Не удалось сохранить конфигурацию DNS-сервера", - "savingConfig": "Сохранение конфигурации...", - "someValueNotValid": "Некоторое значение недопустимо", - "upstreamDnsDescription": "Настройка upstream DNS-серверов и режима DNS", - "bootstrapDnsDescription": "Настройка bootstrap DNS-серверов", - "privateReverseDnsDescription": "Настройка пользовательских DNS-серверов и приватных серверы для обратного DNS", - "dnsServerSettingsDescription": "Настройка ограничения на количество запросов, режима блокировки и многое другое", - "dnsCacheConfigDescription": "Настройка, как сервер должен управлять кэшем DNS", - "comment": "Комментарий", - "address": "Адрес", - "commentsDescription": "Комментариям всегда предшествует #. Вам не обязательно добавлять его, он будет добавлен автоматически.", - "encryptionSettings": "Настройки шифрования", - "encryptionSettingsDescription": "Поддержка шифрования (HTTPS/QUIC/TLS)", - "loadingEncryptionSettings": "Загрузка настроек шифрования...", - "encryptionSettingsNotLoaded": "Не удалось загрузить настройки шифрования.", - "enableEncryption": "Включить шифрование", - "enableEncryptionTypes": "HTTPS, DNS-over-HTTPS и DNS-over-TLS", - "enableEncryptionDescription": "Если порт HTTPS настроен, веб-интерфейс администрирования AdGuard Home будет доступен через HTTPS, а также DNS-over-HTTPS сервер будет доступен по пути '/dns-query'.", - "serverConfiguration": "Конфигурация сервера", - "domainName": "Доменное имя", - "domainNameDescription": "Если задано, AdGuard Home распознаёт ClientID, отвечает на DDR-запросы, и дополнительно проверяет соединения. Если не задано, этот функционал отключён. Должно соответствовать одному из параметров DNS Names в сертификате.", - "redirectHttps": "Автоматически перенаправлять на HTTPS", - "httpsPort": "Порт HTTPS", - "tlsPort": "Порт DNS-over-TLS", - "dnsOverQuicPort": "Порт DNS-over-QUIC", - "certificates": "Сертификаты", - "certificatesDescription": "Для использования шифрования вам необходимо предоставить корректную цепочку SSL-сертификатов для вашего домена. Вы можете получить бесплатный сертификат на letsencrypt.org или вы можете купить его у одного из доверенных Центров Сертификации.", - "certificateFilePath": "Указать путь к файлу сертификатов", - "pasteCertificateContent": "Вставить содержимое сертификатов", - "certificatePath": "Путь к сертификату", - "certificateContent": "Содержимое сертификата", - "privateKey": "Закрытый ключ", - "privateKeyFile": "Указать файл закрытого ключа", - "pastePrivateKey": "Вставить содержимое закрытого ключа", - "usePreviousKey": "Использовать сохранённый ранее ключ", - "privateKeyPath": "Путь к закрытому ключу", - "invalidCertificate": "Цепочка сертификатов не прошла проверку", - "invalidPrivateKey": "Некорректный приватный ключ", - "validatingData": "Проверка данных", - "dataValid": "Данные действительны", - "dataNotValid": "Недопустимые данные", - "encryptionConfigSaved": "Настройки шифрования успешно сохранены", - "encryptionConfigNotSaved": "Не удаётся сохранить настройки шифрования", - "configError": "Ошибка конфигурации", - "enterOnlyCertificate": "Введите только сертификат. Не вводите строки ---BEGIN--- и ---END---.", - "enterOnlyPrivateKey": "Введите только ключ. Не вводите строки ---BEGIN--- и ---END---.", - "noItemsSearch": "Ничего не найдено по данному запросу.", - "clearSearch": "Очистить поиск", - "exitSearch": "Покинуть поиск", - "searchClients": "Поиск клиентов", - "noClientsSearch": "Не найдено клиентов по данному запросу.", - "customization": "Персонализация", - "customizationDescription": "Настройте внешний вид приложения", - "color": "Цветовая тема", - "useDynamicTheme": "Использовать динамическую тему", - "red": "Красный", - "green": "Зелёный", - "blue": "Синий", - "yellow": "Жёлтый", - "orange": "Оранжевый", - "brown": "Коричневый", - "cyan": "Бирюзовый", - "purple": "Пурпурный", - "pink": "Розовый", - "deepOrange": "Темно-оранжевый", - "indigo": "Индиго", - "useThemeColorStatus": "Использовать цвет темы для обозначения статуса", - "useThemeColorStatusDescription": "Заменяет зеленый и красный цвета статуса цветом темы и серым", - "invalidCertificateChain": "Цепочка сертификатов не прошла проверку", - "validCertificateChain": "Действительная цепочка сертификатов", - "subject": "Субъект", - "issuer": "Издатель", - "expires": "Истекает", - "validPrivateKey": "Действительный закрытый ключ", - "expirationDate": "Истекает", - "keysNotMatch": "Недействительный сертификат или ключ: tls: закрытый ключ не соответствует открытому ключу", - "timeLogs": "Время в записях журнала", - "timeLogsDescription": "Показывать время обработки в журнале", - "hostNames": "Имена хостов", - "keyType": "Тип ключа", - "updateAvailable": "Доступно обновление", - "installedVersion": "Установленная версия", - "newVersion": "Новая версия", - "source": "Источник", - "downloadUpdate": "Загрузить обновление", - "download": "Скачать", - "doNotRememberAgainUpdate": "Не запоминать снова для этой версии", - "downloadingUpdate": "Скачивание", - "completed": "завершено", - "permissionNotGranted": "Разрешение не предоставлено", - "inputSearchTerm": "Введите поисковый запрос.", - "answers": "Ответы", - "copyClipboard": "Скопировать в буфер обмена", - "domainCopiedClipboard": "Домен скопирован в буфер обмена", - "clearDnsCache": "Очистить кэш DNS", - "clearDnsCacheMessage": "Вы уверены, что хотите очистить кэш DNS?", - "dnsCacheCleared": "Кэш DNS очищен успешно", - "clearingDnsCache": "Очистка кэша...", - "dnsCacheNotCleared": "Не удалось очистить кэш DNS", - "clientsSelected": "выбранные клиенты", - "invalidDomain": "Недопустимый домен", - "loadingBlockedServicesList": "Загрузка списка заблокированных сервисов...", - "blockedServicesListNotLoaded": "Не удалось загрузить список заблокированных служб", - "error": "Ошибка", - "updates": "Обновления", - "updatesDescription": "Обновить AdGuard Home server", - "updateNow": "Обновить сейчас", - "currentVersion": "Текущая версия", - "requestStartUpdateFailed": "Не удалось выполнить запрос на запуск обновления", - "requestStartUpdateSuccessful": "Запрос на запуск обновления успешен", - "serverUpdated": "Сервер обновлён", - "unknownStatus": "Неизвестный статус", - "checkingUpdates": "Проверка обновлений..", - "checkUpdates": "Проверить обновления", - "requestingUpdate": "Запрос обновления...", - "autoupdateUnavailable": "Автообновление недоступно", - "autoupdateUnavailableDescription": "Служба автоматического обновления недоступна для этого сервера. Это может быть связано с тем, что сервер запущен в контейнере Docker. Вам необходимо обновить свой сервер вручную.", - "minute": "{time} минута", - "minutes": "{time} минут", - "hour": "{time} час", - "hours": "{time} часов", - "remainingTime": "Оставшееся время", - "safeSearchSettings": "Настройки безопасного поиска", - "loadingSafeSearchSettings": "Загрузка настроек безопасного поиска...", - "safeSearchSettingsNotLoaded": "Ошибка при загрузке настроек безопасного поиска.", - "loadingLogsSettings": "Загрузка настроек журнала...", - "selectOptionLeftColumn": "Выберите опцию в левой колонке", - "selectClientLeftColumn": "Выберите клиента в левой колонке", - "disableList": "Отключить список", - "enableList": "Включить список", - "screens": "Экраны", - "copiedClipboard": "Скопировано в буфер обмена", - "seeDetails": "Смотрите подробности", - "listNotAvailable": "Список недоступен", - "copyListUrl": "Скопировать URL", - "listUrlCopied": "URL списка сохранён в буфер обмена", - "unsupportedVersion": "Неподдерживаемая версия", - "unsupprtedVersionMessage": "Поддержка AdGuard Home версии {version} не гарантируется. Приложение может работать нестабильно с данной версией сервера.\n\nПриложение AdGuard Home Manager предназначено для работы со стабильными версиями AdGuard Home. Приложение может работать с альфа и бета версиями сервера, но совместимость и стабильность не гарантируются.", - "iUnderstand": "Продолжить", - "appUpdates": "Обновления приложений", - "usingLatestVersion": "Вы используете последнюю версию", - "ipLogs": "IP-адреса в записях журнала", - "ipLogsDescription": "Всегда показывать IP-адрес в записях журнала вместо имени клиента", - "application": "Приложение", - "combinedChart": "Объединять графики", - "combinedChartDescription": "Комбинирует все графики в один", - "statistics": "Статистика", - "errorLoadFilters": "Ошибка при загрузке фильтров.", - "clientRemovedSuccessfully": "Запись успешно удалена.", - "editRewriteRule": "Редактировать правило", - "dnsRewriteRuleUpdated": "Правило перезаписи DNS успешно обновлено", - "dnsRewriteRuleNotUpdated": "Не удалось обновить правило перезаписи DNS", - "updatingRule": "Обновление правила...", - "serverUpdateNeeded": "Требуется обновление сервера", - "updateYourServer": "Обновите сервер AdGuard Home до версии {version} или выше, чтобы использовать эту функцию.", - "january": "Январь", - "february": "Февраль", - "march": "Март", - "april": "Апрель", - "may": "Май", - "june": "Июнь", - "july": "Июль", - "august": "Август", - "september": "Сентябрь", - "october": "Октябрь", - "november": "Ноябрь", - "december": "Декабрь", - "malwarePhising": "Вредоносные/фишинговые сайты", - "queries": "Запросы", - "adultSites": "«Взрослые» сайты", - "quickFilters": "Быстрые фильтры", - "searchDomainInternet": "Поиск домена в Интернете", - "hideServerAddress": "Скрывать адрес сервера", - "hideServerAddressDescription": "Скрывает адрес сервера на главном экране", - "topItemsOrder": "Расположение блоков на главном экране", - "topItemsOrderDescription": "Упорядочьте расположение блоков на главном экране", - "topItemsReorderInfo": "Чтобы менять порядок элементов, удерживая элемент, перетащите его на новое место.", - "discardChanges": "Отменить изменения", - "discardChangesDescription": "Вы уверены, что хотите отменить изменения?", - "others": "Прочее", - "showChart": "Показать график", - "hideChart": "Скрыть график", - "showTopItemsChart": "Показывать ТОП-графики на главной странице", - "showTopItemsChartDescription": "По умолчанию на главной странице отображаются круговые диаграммы для часто запрашиваемых доменов, частых клиентов и прочего. Влияет только на просмотр с мобильного устройства", - "openMenu": "Открыть меню", - "closeMenu": "Закрыть меню", - "openListUrl": "Открыть URL списка", - "selectionMode": "Режим выбора", - "enableDisableSelected": "Включить или выключить выбранные элементы", - "deleteSelected": "Удалить выбранные элементы", - "deleteSelectedLists": "Удалить выбранные списки", - "allSelectedListsDeletedSuccessfully": "Все выбранные списки успешно удалены.", - "deletionResult": "Результат удаления", - "deletingLists": "Удаление списков...", - "failedElements": "Неудачные элементы", - "processingLists": "Обработка списков...", - "enableDisableResult": "Включить или выключить результат", - "selectedListsEnabledDisabledSuccessfully": "Все выбранные списки были включены или выключены успешно", - "sslWarning": "Если используется HTTPS-соединение с самоподписанным сертификатом, то должна быть активирована опция «Не проверять SSL-сертификат» в разделе «Настройки» > «Дополнительные настройки».", - "unsupportedServerVersion": "Неподдерживаемая версия сервера", - "unsupportedServerVersionMessage": "Данная версия AdGuard Home устарела и не поддерживается AdGuard Home Manager. Чтобы использовать данное приложение, необходимо выполнить обновление AdGuard Home до актуальной версии.", - "yourVersion": "Ваша версия: {version}", - "minimumRequiredVersion": "Минимальная требуемая версия: {version}", - "topUpstreams": "Часто запрашиваемые\nupstream-серверы", - "averageUpstreamResponseTime": "Среднее время отклика\nupstream-сервера", - "dhcpNotAvailable": "DHCP сервер не доступен.", - "osServerInstalledIncompatible": " Операционная система, в которой установлен сервер, несовместима с этой функцией.", - "resetSettings": "Сбросить настройки", - "resetEncryptionSettingsDescription": "Вы уверены, что хотите сбросить настройки шифрования к значениям по умолчанию?", - "resettingConfig": "Сброс конфигурации...", - "configurationResetSuccessfully": "Конфигурация успешно сброшена", - "configurationResetError": "Не удалось сбросить конфигурацию", - "testUpstreamDnsServers": "Тест upstream DNS-серверов", - "errorTestUpstreamDns": "Ошибка при тестировании upstream DNS-серверов.", - "useCustomIpEdns": "Use custom IP for EDNS", - "useCustomIpEdnsDescription": "Использовать собственный IP-адрес для EDNS", - "sortingOptions": "Параметры сортировки", - "fromHighestToLowest": "От большего к меньшему", - "fromLowestToHighest": "От меньшего к большему", - "queryLogsAndStatistics": "Журналы запросов и статистика", - "ignoreClientQueryLog": "Игнорировать этого клиента в журнале запросов", - "ignoreClientStatistics": "Игнорировать этого клиента в статистике", - "savingChanges": "Сохранение изменений...", - "fallbackDnsServers": "Резервные DNS-серверы", - "fallbackDnsServersDescription": "Настроить резервные DNS-серверы", - "fallbackDnsServersInfo": "Список резервных DNS-серверов, используемых в тех случаях, когда вышестоящие DNS-серверы недоступны. Синтаксис такой же, как и в поле Upstream DNS-серверы выше.", - "noFallbackDnsAdded": "Резервные DNS-серверы не добавлены.", - "blockedResponseTtl": "TTL заблокированного ответа", - "blockedResponseTtlDescription": "Указывает, в течение скольких секунд клиенты должны кешировать отфильтрованный ответ", - "invalidValue": "Недопустимое значение", - "noDataChart": "Нет данных для отображения графика.", - "noData": "Нет данных", - "unblockClient": "Разблокировать клиента", - "blockingClient": "Блокировка клиента...", - "unblockingClient": "Снятие блокироваки с клиента...", - "upstreamDnsCacheConfiguration": "Конфигурация кеша upstream DNS-серверов", - "enableDnsCachingClient": "Включить кеширование для пользовательской конфигурации upstream-серверов этого клиента", - "dnsCacheSize": "Размер DNS-кеша", - "nameInvalid": "Требуется имя", - "oneIdentifierRequired": "Требуется по крайней мере один идентификатор", - "dnsCacheNumber": "Размер кэша DNS должен быть числом", - "errors": "Ошибки", - "redirectHttpsWarning": "Если в AdGuard Home активирована опция «Автоматически перенаправлять на HTTPS», то необходимо использовать HTTPS-соединение и HTTPS-порт." -} diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index d0f26c5..06c3b08 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -11,7 +11,7 @@ "port": "Bağlantı noktası", "username": "Kullanıcı adı", "password": "Şifre", - "defaultServer": "Varsayılan sunucu olarak ayarla", + "defaultServer": "Varsayılan sunucu", "general": "Genel", "connection": "Bağlantı", "authentication": "Kimlik doğrulama", @@ -40,18 +40,18 @@ "removeWarning": "Bu AdGuard Home sunucusuyla olan bağlantıyı kaldırmak istediğinizden emin misiniz?", "cancel": "İptal", "defaultConnection": "Varsayılan bağlantı", - "setDefault": "Varsayılan sunucu yap", + "setDefault": "Varsayılan ayarla", "edit": "Düzenle", "delete": "Sil", "save": "Kaydet", "serverStatus": "Sunucu durumu", "connectionNotUpdated": "Bağlantı Güncellenmedi", "ruleFilteringWidget": "Kural filtreleme", - "safeBrowsingWidget": "Güvenli gezinme", + "safeBrowsingWidget": "Güvenli gezinti", "parentalFilteringWidget": "Ebeveyn filtreleme", "safeSearchWidget": "Güvenli arama", "ruleFiltering": "Kural filtreleme", - "safeBrowsing": "Güvenli gezinme", + "safeBrowsing": "Güvenli gezinti", "parentalFiltering": "Ebeveyn filtreleme", "safeSearch": "Güvenli arama", "serverStatusNotRefreshed": "Sunucu durumu yenilenemedi", @@ -63,9 +63,9 @@ "topBlockedDomains": "En çok engellenenler", "appSettings": "Uygulama ayarları", "theme": "Tema", - "light": "Açık", - "dark": "Koyu", - "systemDefined": "Otomatik (Cihazınızın renk düzenine göre)", + "light": "Aydınlık", + "dark": "Karanlık", + "systemDefined": "Sistemle uyumlu hale getir", "close": "Kapat", "connectedTo": "Bağlandı:", "selectedServer": "Seçili sunucu:", @@ -75,13 +75,13 @@ "userNotEmpty": "Kullanıcı adı boş bırakılamaz", "passwordNotEmpty": "Şifre boş bırakılamaz", "examplePath": "Örnek: /adguard", - "helperPath": "Eğer ters proxy kullanıyorsanız", + "helperPath": "Ters proxy kullanıyorsanız", "aboutApp": "Uygulama hakkında", "appVersion": "Uygulama sürümü", "createdBy": "Geliştirici", "clients": "İstemciler", - "allowed": "İzin verilen", - "blocked": "Engellenen", + "allowed": "İzin verildi", + "blocked": "Engellendi", "noClientsList": "Bu listede hiç istemci yok", "activeClients": "Etkin", "removeClient": "İstemciyi kaldır", @@ -102,8 +102,8 @@ "copyLogsClipboard": "Günlükleri panoya kopyala", "logsCopiedClipboard": "Günlükler panoya kopyalandı", "advancedSettings": "Gelişmiş ayarlar", - "dontCheckCertificate": "SSL sertifikasını kontrol etme", - "dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar.", + "dontCheckCertificate": "SSL sertifikasını asla kontrol etme", + "dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar", "advancedSetupDescription": "Gelişmiş seçenekleri yönet", "settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.", "cannotUpdateSettings": "Ayarlar güncellenemiyor.", @@ -114,8 +114,8 @@ "processedRow": "İşlendi (Liste yok)", "blockedBlacklist": "Engellendi\nKara Liste", "blockedBlacklistRow": "Engellendi (Kara liste)", - "blockedSafeBrowsing": "Engellendi\nGüvenli gezinme", - "blockedSafeBrowsingRow": "Engellendi (Güvenli gezinme)", + "blockedSafeBrowsing": "Engellendi\nGüvenli gezinti", + "blockedSafeBrowsingRow": "Engellendi (Güvenli gezinti)", "blockedParental": "Engellendi\nEbeveyn filtreleme", "blockedParentalRow": "Engellendi (Ebeveyn filtreleme)", "blockedInvalid": "Engellendi\nGeçersiz", @@ -131,11 +131,11 @@ "rewrite": "Yeniden Yaz", "status": "Durum", "result": "Sonuç", - "time": "Saat", + "time": "Zaman", "blocklist": "Engelleme Listesi", "request": "İstek", "domain": "Alan adı", - "type": "Tür", + "type": "Tip", "clas": "Sınıf", "response": "Yanıt", "dnsServer": "DNS sunucusu", @@ -181,7 +181,7 @@ "visitGooglePlay": "Google Play sayfasını ziyaret et", "gitHub": "Kaynak kodlarına GitHub'dan ulaşabilirsiniz", "blockClient": "İstemciyi engelle", - "selectTags": "Etiketleri seç", + "selectTags": "Etiketleri seçin", "noTagsSelected": "Seçili etiket yok", "tags": "Etiketler", "identifiers": "Tanımlayıcılar", @@ -190,21 +190,21 @@ "noIdentifiers": "Tanımlayıcı eklenmedi", "useGlobalSettings": "Küresel ayarları kullan", "enableFiltering": "Filtrelemeyi etkinleştir", - "enableSafeBrowsing": "Güvenli gezinmeyi etkinleştir", + "enableSafeBrowsing": "Güvenli gezintiyi etkinleştir", "enableParentalControl": "Ebeveyn kontrolünü etkinleştir", - "enableSafeSearch": "Güvenli aramayı etkinleştir", + "enableSafeSearch": "Güvenli aramayı aktif et", "blockedServices": "Engellenen hizmetler", "selectBlockedServices": "Engellenen hizmetleri seç", "noBlockedServicesSelected": "Engellenen hizmetler seçilmedi", "services": "Hizmetler", "servicesBlocked": "Hizmetler engellendi", - "tagsSelected": "Etiket seçildi", - "upstreamServers": "Üst sunucular", + "tagsSelected": "Seçilen etiketler", + "upstreamServers": "Üst kaynak sunucuları", "serverAddress": "Sunucu adresi", - "noUpstreamServers": "Üst sunucu yok.", - "willBeUsedGeneralServers": "Genel üst sunucular kullanılacak.", + "noUpstreamServers": "Üst kaynak sunucusu yok.", + "willBeUsedGeneralServers": "Genel üst kaynak sunucuları kullanılacak.", "added": "Eklenenler", - "clientUpdatedSuccessfully": "İstemci ayarları başarıyla güncellendi", + "clientUpdatedSuccessfully": "İstemci başarıyla güncellendi", "clientNotUpdated": "İstemci güncellenemedi", "clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı", "clientNotDeleted": "İstemci silinemedi", @@ -215,7 +215,7 @@ "blacklists": "Kara listeler", "rules": "Kurallar", "customRules": "Özel kurallar", - "enabledRules": "Etkin kural", + "enabledRules": "Etkin kurallar", "enabled": "Etkin", "disabled": "Devre dışı", "rule": "Kural", @@ -234,7 +234,7 @@ "addWhitelist": "Beyaz liste ekle", "addBlacklist": "Kara liste ekle", "urlNotValid": "Bağlantı adresi geçerli değil", - "urlAbsolutePath": "Bağlantı adresi veya dosya yolu", + "urlAbsolutePath": "Bağlantı adresi veya kesin dosya yolu", "addingList": "Liste ekleniyor...", "listAdded": "Liste başarıyla eklendi. Eklenen öğeler:", "listAlreadyAdded": "Liste zaten eklenmiş", @@ -267,13 +267,13 @@ "seeDnsAddresses": "DNS adreslerine göz at", "dnsPort": "DNS bağlantı noktası", "httpPort": "HTTP bağlantı noktası", - "protectionEnabled": "Koruma durumu", - "dhcpAvailable": "DHCP durumu", - "serverRunning": "Sunucu durumu", + "protectionEnabled": "Koruma etkin mi?", + "dhcpAvailable": "DHCP mevcut mu?", + "serverRunning": "Sunucu çalışıyor mu?", "serverVersion": "Sunucu sürümü", "serverLanguage": "Sunucu dili", - "yes": "Etkin", - "no": "Mevcut değil", + "yes": "Evet", + "no": "Hayır", "allowedClients": "İzin verilen istemciler", "disallowedClients": "İzin verilmeyen istemciler", "disallowedDomains": "İzin verilmeyen alan adları", @@ -288,7 +288,7 @@ "addClientFieldDescription": "CIDR'ler, IP adresi veya ClientID", "clientIdentifier": "İstemci tanımlayıcısı", "allowClient": "İstemciye izin ver", - "disallowClient": "İstemciyi engelle", + "disallowClient": "İstemciye izin verme", "noDisallowedDomains": "İzin verilmeyen alan adı yok", "domainNotAdded": "Alan adı eklenemedi", "statusSelected": "Durum seçildi.", @@ -305,7 +305,7 @@ "block": "Engelle", "unblock": "Engeli kaldır", "custom": "Özel", - "addImportant": "Başına $important ekle", + "addImportant": "Ekle ($important)", "howCreateRules": "Özel kurallar nasıl oluşturulur?", "examples": "Örnekler", "example1": "example.org ve tüm alt alan adlarına erişimi engeller.", @@ -342,7 +342,7 @@ "dhcpSettingsNotLoaded": "DHCP ayarları yüklenemedi", "loadingDhcp": "DHCP ayarları yükleniyor...", "enableDhcpServer": "DHCP sunucusunu etkinleştir", - "selectInterface": "Arayüz seç", + "selectInterface": "Arayüz seçin", "hardwareAddress": "Donanım adresi", "gatewayIp": "Ağ Geçidi IP'si", "ipv4addresses": "IPv4 adresleri", @@ -368,37 +368,37 @@ "restoringConfig": "Yapılandırma geri yükleniyor...", "configRestored": "Yapılandırma başarıyla sıfırlandı", "configNotRestored": "Yapılandırma sıfırlanamadı", - "dhcpStatic": "DHCP statik kiraları", - "noDhcpStaticLeases": "DHCP statik kirası bulunamadı", + "dhcpStatic": "DHCP statik kiralamaları", + "noDhcpStaticLeases": "DHCP statik kiralamaları bulunamadı", "deleting": "Siliniyor...", - "staticLeaseDeleted": "DHCP statik kirası başarıyla silindi", - "staticLeaseNotDeleted": "DHCP statik kirası silinemedi", - "deleteStaticLease": "Statik kirasını sil", + "staticLeaseDeleted": "DHCP statik kiralama başarıyla silindi", + "staticLeaseNotDeleted": "DHCP statik kiralaması silinemedi", + "deleteStaticLease": "Statik kiralamayı sil", "deleteStaticLeaseDescription": "DHCP statik kirası silinecek. Bu işlem geri alınamaz.", - "addStaticLease": "Statik kira ekleyin", + "addStaticLease": "Statik kiralama ekleyin", "macAddress": "MAC adresi", "macAddressNotValid": "MAC adresi geçersiz", "hostName": "Ana bilgisayar adı", "hostNameError": "Ana bilgisayar adı boş olamaz", "creating": "Oluşturuluyor...", - "staticLeaseCreated": "DHCP statik kirası başarıyla oluşturuldu", - "staticLeaseNotCreated": "DHCP statik kirası oluşturulamadı", - "staticLeaseExists": "DHCP statik kirası zaten mevcut", + "staticLeaseCreated": "DHCP statik kiralaması başarıyla oluşturuldu", + "staticLeaseNotCreated": "DHCP statik kiralaması oluşturulamadı", + "staticLeaseExists": "DHCP statik kiralaması zaten mevcut", "serverNotConfigured": "Sunucu yapılandırılmamış", - "restoreLeases": "Kiraları sıfırla", - "restoreLeasesMessage": "Devam etmek istediğinizden emin misiniz? Bu, mevcut tüm kiraları sıfırlayacaktır. Bu işlem geri alınamaz.", - "restoringLeases": "Kiralar sıfırlanıyor...", - "leasesRestored": "Kiralar başarıyla sıfırlandı", + "restoreLeases": "Kiralamaları sıfırla", + "restoreLeasesMessage": "Devam etmek istediğinizden emin misiniz? Bu, mevcut tüm kiralamaları sıfırlayacaktır. Bu işlem geri alınamaz.", + "restoringLeases": "Kiralamalar sıfırlanıyor...", + "leasesRestored": "Kiralamalar başarıyla sıfırlandı", "leasesNotRestored": "Kiralar sıfırlanamadı", - "dhcpLeases": "DHCP kiraları", - "noLeases": "Kullanılabilir DHCP kiraları yok", + "dhcpLeases": "DHCP kiralamaları", + "noLeases": "Kullanılabilir DHCP kiralaması yok", "dnsRewrites": "DNS yeniden yazımları", "dnsRewritesDescription": "Özel DNS kurallarını yapılandır", "loadingRewriteRules": "Yeniden yazım kuralları yükleniyor...", "rewriteRulesNotLoaded": "DNS yeniden yazım kuralları yüklenemedi.", "noRewriteRules": "DNS yeniden yazım kuralları yok", "answer": "Yanıt", - "deleteDnsRewrite": "DNS yeniden yazımını sil", + "deleteDnsRewrite": "DNS yeniden yazımı sil", "deleteDnsRewriteMessage": "Bu DNS yeniden yazımını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", "dnsRewriteRuleDeleted": "DNS yeniden yazım kuralı başarıyla silindi", "dnsRewriteRuleNotDeleted": "DNS yeniden yazım kuralı silinemedi", @@ -409,7 +409,7 @@ "logsSettings": "Günlük ayarları", "enableLog": "Günlüğü etkinleştir", "clearLogs": "Günlükleri temizle", - "anonymizeClientIp": "İstemci IP'sini gizle", + "anonymizeClientIp": "İstemci IP'sini anonimleştir", "hours6": "6 saat", "days30": "30 gün", "days90": "90 gün", @@ -418,7 +418,7 @@ "logSettingsNotLoaded": "Günlük ayarları yüklenemedi.", "updatingSettings": "Ayarlar güncelleniyor...", "logsConfigUpdated": "Günlük ayarları başarıyla güncellendi", - "logsConfigNotUpdated": "Günlük ayarları güncellenemedi", + "logsConfigNotUpdated": "Günlük ayarları başarıyla güncellendi", "deletingLogs": "Günlükler temizleniyor...", "logsCleared": "Günlükler başarıyla temizlendi", "logsNotCleared": "Günlükler temizlenemedi", @@ -427,17 +427,17 @@ "noItems": "Burada gösterilecek öğe yok", "dnsSettings": "DNS ayarları", "dnsSettingsDescription": "DNS sunucuları ile bağlantıyı yapılandır", - "upstreamDns": "Üst DNS sunucuları", + "upstreamDns": "Üst kaynak DNS sunucuları", "bootstrapDns": "Önyükleme DNS sunucuları", - "noUpstreamDns": "Üst DNS sunucuları eklenmedi.", + "noUpstreamDns": "Üst kaynak DNS sunucuları eklenmedi.", "dnsMode": "DNS modu", "noDnsMode": "DNS modu seçili değil", "loadBalancing": "Yük dengeleme", "parallelRequests": "Paralel istekler", "fastestIpAddress": "En hızlı IP adresi", - "loadBalancingDescription": "Her seferinde bir üst sunucuya sorgu yapar. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.", - "parallelRequestsDescription": "Tüm üst sunucuları aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanılır.", - "fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürür. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.", + "loadBalancingDescription": "Her seferinde bir üst kaynak sunucusuna sorgu yap. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.", + "parallelRequestsDescription": "Tüm üst kaynak sunucularını aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.", + "fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürün. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.", "noBootstrapDns": "Önyükleme DNS sunucuları eklenmedi.", "bootstrapDnsServersInfo": "Önyükleme ​​DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.", "privateReverseDnsServers": "Özel ters DNS sunucuları", @@ -446,36 +446,36 @@ "addItem": "Öğe ekle", "noServerAddressesAdded": "Sunucu adresleri eklenmedi.", "usePrivateReverseDnsResolvers": "Özel ters DNS çözümleyicilerini kullan", - "usePrivateReverseDnsResolversDescription": "Bu üst sunucuları kullanarak yerel olarak sunulan adresler için ters DNS sorguları gerçekleştirin. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts vb. kaynaklardan bilinen istemciler dışında tüm PTR isteklerine NXDOMAIN yanıtı verir.", + "usePrivateReverseDnsResolversDescription": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS sorguları gerçekleştirin. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts vb. kaynaklardan bilinen istemciler dışında tüm PTR isteklerine NXDOMAIN yanıtı verir.", "enableReverseResolving": "İstemcilerin IP adreslerinin ters çözümlemesini etkinleştir", - "enableReverseResolvingDescription": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunuculara) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözer.", + "enableReverseResolvingDescription": "İstemcilerin IP adreslerini karşılık gelen çözücülere PTR sorguları göndererek IP adreslerini tersine çözümleyerek (yerel istemciler için özel DNS sunucuları, genel IP adresine sahip istemciler için üst kaynak sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.", "dnsServerSettings": "AdGuard Home DNS sunucusu ayarları", - "limitRequestsSecond": "Sıklık limiti", + "limitRequestsSecond": "Saniye başına sınırlama isteği", "valueNotNumber": "Değer bir sayı değil", "enableEdns": "EDNS istemci alt ağını etkinleştir", - "enableEdnsDescription": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı Seçeneği (ECS) ekler ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydeder.", + "enableEdnsDescription": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı Seçeneği (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.", "enableDnssec": "DNSSEC'i etkinleştir", - "enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştirir ve sonucu kontrol eder. (DNSSEC etkinleştirilmiş bir çözümleyici gerekli)", + "enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol edin.(DNSSEC etkinleştirilmiş bir çözümleyici gerekli)", "disableResolvingIpv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak", - "disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını bırakır (AAAA yazar) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırır.", + "disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını bırakın (AAAA yazın) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırın.", "blockingMode": "Engelleme modu", "defaultMode": "Varsayılan", - "defaultDescription": "Reklam engelleme tarzı bir kural tarafından engellendiğinde sıfır IP adresi ile yanıt verir. (A için 0.0.0.0; :: AAAA için) /etc/hosts tarzı bir kural tarafından engellendiğinde kuralda belirtilen IP adresi ile yanıt verir.", - "refusedDescription": "REFUSED kodu ile yanıt verir.", - "nxdomainDescription": "NXDOMAIN kodu ile yanıt verir.", + "defaultDescription": "Reklam engelleme tarzı bir kural tarafından engellendiğinde sıfır IP adresi ile yanıt verin. (A için 0.0.0.0; :: AAAA için) /etc/hosts tarzı bir kural tarafından engellendiğinde kuralda belirtilen IP adresi ile yanıt verin.", + "refusedDescription": "REFUSED kodu ile yanıt verin.", + "nxdomainDescription": "NXDOMAIN kodu ile yanıt verin.", "nullIp": "Boş IP", - "nullIpDescription": "Sıfır IP adresi ile yanıt verir. (A için 0.0.0.0; :: AAAA için)", + "nullIpDescription": "Sıfır IP adresi ile yanıt verin. (A için 0.0.0.0; :: AAAA için)", "customIp": "Özel IP", - "customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verir.", + "customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verin.", "dnsCacheConfig": "DNS önbellek yapılandırması", "cacheSize": "Önbellek boyutu", - "inBytes": "Alınacak önbelleğin boyutunu ayarla (Bayt olarak)", + "inBytes": "Bayt olarak", "overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl", - "overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.", + "overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini ayarlayın (saniye olarak)", "overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl", - "overrideMaximumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan maksimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.", + "overrideMaximumTtlDescription": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak)", "optimisticCaching": "İyimser önbelleğe alma", - "optimisticCachingDescription": "Girişlerin süresi dolmuş olsa bile Adguard Home'un önbellekten yanıt vermesini sağlar ve aynı zamanda bunları yenilemeye çalışır.", + "optimisticCachingDescription": "Girişlerin süresi dolmuş olsa bile Adguard Home'un önbellekten yanıt vermesini sağlayın ve aynı zamanda bunları yenilemeye çalışın.", "loadingDnsConfig": "DNS yapılandırması yükleniyor...", "dnsConfigNotLoaded": "DNS yapılandırması yüklenemedi.", "blockingIpv4": "IPv4 engelleniyor", @@ -487,14 +487,14 @@ "dnsConfigNotSaved": "DNS sunucusu yapılandırması kaydedilemedi", "savingConfig": "Yapılandırma kaydediliyor...", "someValueNotValid": "Bazı değerler geçerli değil", - "upstreamDnsDescription": "Üst sunucuları ve DNS modunu yapılandır", + "upstreamDnsDescription": "Üst kaynak sunucularını ve DNS modunu yapılandır", "bootstrapDnsDescription": "Önyükleme DNS sunucularını yapılandır", "privateReverseDnsDescription": "Özel DNS çözümleyicileri yapılandır ve özel ters DNS çözümlemeyi etkinleştir", "dnsServerSettingsDescription": "Hız limiti, engelleme modu ve daha fazlasını yapılandır", "dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır", "comment": "Yorum", "address": "Adres", - "commentsDescription": "Yorumlar her zaman # işareti ile başlar. Eklemenize gerek yok, otomatik olarak eklenecektir.", + "commentsDescription": "Yorumlar her zaman # işareti ile başlar. Onu eklemenize gerek yok, otomatik olarak eklenir.", "encryptionSettings": "Şifreleme ayarları", "encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği", "loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...", @@ -513,13 +513,13 @@ "certificatesDescription": "Şifreleme kullanmak için, alan adınız için geçerli bir SSL sertifikası zinciri sağlamanız gereklidir. letsencrypt.org'dan ücretsiz bir sertifika alabilir veya güvenilir sertifika yetkililerinden satın alabilirsiniz.", "certificateFilePath": "Sertifika dosyası belirle", "pasteCertificateContent": "Sertifika içeriğini yapıştır", - "certificatePath": "Sertifikanın dosya yolu", + "certificatePath": "Sertifika dosya yolu", "certificateContent": "Sertifika içeriği", "privateKey": "Özel anahtarlar", "privateKeyFile": "Özel anahtar dosyası belirle", "pastePrivateKey": "Özel anahtar içeriğini yapıştır", - "usePreviousKey": "Önceden kaydedilmiş olan anahtarı kullan", - "privateKeyPath": "Özel anahtarın dosya yolu", + "usePreviousKey": "Önceden kaydedilmiş anahtarı kullan", + "privateKeyPath": "Özel anahtar dosya yolu", "invalidCertificate": "Geçersiz sertifika", "invalidPrivateKey": "Geçersiz özel anahtar", "validatingData": "Veri doğrulama", @@ -560,8 +560,8 @@ "validPrivateKey": "Geçerli özel anahtar", "expirationDate": "Son kullanma tarihi", "keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.", - "timeLogs": "Günlüklerde işlem süresini göster", - "timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini gösterir.", + "timeLogs": "Günlüklerdeki işlem süresi", + "timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini göster", "hostNames": "Ana bilgisayar adları", "keyType": "Anahtar türü", "updateAvailable": "Güncelleme mevcut", @@ -591,7 +591,7 @@ "updates": "Güncellemeler", "updatesDescription": "AdGuard Home sunucusunu güncelle", "updateNow": "Şimdi güncelle", - "currentVersion": "Yüklü sürüm", + "currentVersion": "Mevcut sürüm", "requestStartUpdateFailed": "Güncellemeyi başlatma isteği başarısız oldu", "requestStartUpdateSuccessful": "Güncellemeyi başlatma isteği başarılı", "serverUpdated": "Sunucu güncellendi", @@ -618,15 +618,15 @@ "copiedClipboard": "Panoya kopyalandı", "seeDetails": "Detayları gör", "listNotAvailable": "Liste mevcut değil", - "copyListUrl": "Bağlantıyı kopyala", - "listUrlCopied": "Liste bağlantısı panoya kopyalandı", + "copyListUrl": "Liste bağlantısını kopyala", + "listUrlCopied": "Panoya kopyalanan bağlantı adresini listeleyin", "unsupportedVersion": "Desteklenmeyen sürüm", "unsupprtedVersionMessage": "Sunucu sürümünüz {version} için destek garantisi verilmiyor. Bu uygulamanın bu sunucu sürümüyle çalışmasında bazı sorunlar olabilir. AdGuard Home Yöneticisi, AdGuard Home sunucunun kararlı sürümleriyle çalışacak şekilde tasarlanmıştır. Alfa ve beta sürümleriyle çalışabilir, ancak uyumluluk garanti edilmez ve uygulama bu sürümlerle çalışırken bazı sorunlar yaşayabilir.", "iUnderstand": "Anladım", "appUpdates": "Uygulama güncellemeleri", - "usingLatestVersion": "En son sürümü kullanıyorsunuz", - "ipLogs": "Günlüklerde IP adresini göster", - "ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini gösterir.", + "usingLatestVersion": "En son sürümü kullanıyorsunuz :)", + "ipLogs": "Günlüklerdeki IP", + "ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster", "application": "Uygulama", "combinedChart": "Birleştirilmiş grafik", "combinedChartDescription": "Tüm grafikleri bir araya getirir.", @@ -655,11 +655,11 @@ "queries": "Sorgular", "adultSites": "Yetişkin içerikler", "quickFilters": "Hızlı filtreler", - "searchDomainInternet": "Alan adını arat", + "searchDomainInternet": "İnternette alan adı ara", "hideServerAddress": "Sunucu adresini gizle", "hideServerAddressDescription": "Ana ekranda sunucu adresini gizler.", "topItemsOrder": "Öne çıkan öğeler sıralaması", - "topItemsOrderDescription": "Ana ekrandaki öne çıkan öğe listelerini sıralayın.", + "topItemsOrderDescription": "Ana ekrandaki öne çıkan öğe listelerini sırala", "topItemsReorderInfo": "Yeniden sıralamak için bir öğeyi basılı tutun ve kaydırın.", "discardChanges": "Değişiklikleri iptal et", "discardChangesDescription": "Değişiklikleri iptal etmek istediğinizden emin misiniz?", @@ -687,124 +687,6 @@ "unsupportedServerVersionMessage": "AdGuard Home sunucu sürümünüz çok eski ve AdGuard Home Manager tarafından desteklenmiyor. Bu uygulamayı kullanmak için AdGuard Home sunucunuzu daha yeni bir sürüme yükseltmeniz gerekecektir.", "yourVersion": "Yüklü sürüm: {version}", "minimumRequiredVersion": "Gerekli minimum sürüm: {version}", - "topUpstreams": "Öne çıkan DNS sunucuları", - "averageUpstreamResponseTime": "DNS sunucuları işlem süresi" , - "dhcpNotAvailable": "DHCP sunucusu kullanılamıyor.", - "osServerInstalledIncompatible": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor.", - "resetSettings": "Ayarları sıfırla", - "resetEncryptionSettingsDescription": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?", - "resettingConfig": "Yapılandırma sıfırlanıyor...", - "configurationResetSuccessfully": "Yapılandırma başarıyla sıfırlandı", - "configurationResetError": "Yapılandırma sıfırlanamadı", - "testUpstreamDnsServers": "DNS sunucusunu test et", - "errorTestUpstreamDns": "DNS sunucularını test ederken hata oluştu.", - "useCustomIpEdns": "EDNS için özel IP kullan", - "useCustomIpEdnsDescription": "EDNS için özel IP kullanımına izin ver", - "sortingOptions": "Sıralama seçenekleri", - "fromHighestToLowest": "Yüksekten düşüğe", - "fromLowestToHighest": "Düşükten yükseğe", - "queryLogsAndStatistics": "Sorgu günlüğü ve istatistikler", - "ignoreClientQueryLog": "Sorgu günlüğünde bu istemciyi yoksay", - "ignoreClientStatistics": "İstatistiklerde bu istemciyi yoksay", - "savingChanges": "Değişiklikler kaydediliyor...", - "fallbackDnsServers": "Yedek DNS sunucuları", - "fallbackDnsServersDescription": "Yedek DNS sunucularını yapılandır", - "fallbackDnsServersInfo": "Üst DNS sunucuları yanıt vermediğinde kullanılan yedek DNS sunucularının listesi. Sözdizimi, yukarıdaki ana üst kaynak alanıyla aynıdır.", - "noFallbackDnsAdded": "Yedek DNS sunucusu eklenmedi.", - "blockedResponseTtl": "Engellenen yanıtın kullanım süresi", - "blockedResponseTtlDescription": "İstemcilerin filtrelenmiş bir yanıtı kaç saniye süreyle önbelleğe alması gerektiğini belirtir", - "invalidValue": "Geçersiz değer", - "noDataChart": "Bu grafiği görüntüleyecek veri yok.", - "noData": "Veri yok", - "unblockClient": "İstemci engelini kaldır", - "blockingClient": "İstemci engelleniyor...", - "unblockingClient": "İstemci engeli kaldırılıyor...", - "upstreamDnsCacheConfiguration": "DNS önbellek yapılandırması", - "enableDnsCachingClient": "Bu istemci için DNS önbelleğe almayı etkinleştir", - "dnsCacheSize": "DNS önbellek boyutu (Bayt cinsinden)", - "nameInvalid": "Ad gereklidir", - "oneIdentifierRequired": "En az bir tanımlayıcı gereklidir", - "dnsCacheNumber": "DNS önbellek boyutu bir rakam içermelidir", - "errors": "Hatalar", - "redirectHttpsWarning": "AdGuard Home sunucunuzda \"Otomatik olarak HTTPS'e yönlendir\" seçeneğini etkinleştirdiyseniz, bir HTTPS bağlantısı seçmeli ve sunucunuzun HTTPS bağlantı noktasını kullanmalısınız.", - "logsSettingsDescription": "Sorgu günlüklerini yapılandır", - "ignoredDomains": "Yok sayılan alan adları", - "noIgnoredDomainsAdded": "Yok sayılacak alan adı eklenmedi", - "pauseServiceBlocking": "Hizmet engellemeyi duraklat", - "newSchedule": "Yeni program", - "editSchedule": "Programı düzenle", - "timezone": "Zaman dilimi", - "monday": "Pazartesi", - "tuesday": "Salı", - "wednesday": "Çarşamba", - "thursday": "Perşembe", - "friday": "Cuma", - "saturday": "Cumartesi", - "sunday": "Pazar", - "from": "Başlangıç", - "to": "Bitiş", - "selectStartTime": "Başlangıç zamanını seç", - "selectEndTime": "Bitiş zamanını seç", - "startTimeBeforeEndTime": "Başlangıç zamanı bitiş zamanından önce olmalıdır.", - "noBlockingScheduleThisDevice": "Bu cihaz için herhangi bir engelleme programı bulunmamaktadır.", - "selectTimezone": "Bir zaman dilimi seç", - "selectClientsFiltersInfo": "Görüntülemek istediğiniz istemcileri seçin. Hiçbir istemci seçilmemişse, hepsi görüntülenecektir.", - "noDataThisSection": "Bu bölüm için veri yok.", - "statisticsSettings": "İstatistik ayarları", - "statisticsSettingsDescription": "İstatistikler için veri toplamayı yapılandır", - "loadingStatisticsSettings": "İstatistik ayarları yükleniyor...", - "statisticsSettingsLoadError": "İstatistik ayarları yüklenirken bir hata oluştu.", - "statisticsConfigUpdated": "İstatistik ayarları başarıyla güncellendi", - "statisticsConfigNotUpdated": "İstatistik ayarları güncellenemedi", - "customTimeInHours": "Özel zaman (Saat olarak)", - "invalidTime": "Geçersiz zaman", - "removeDomain": "Alan adını kaldır", - "addDomain": "Alan adı ekle", - "notLess1Hour": "Zaman 1 saatten az olamaz", - "rateLimit": "Hız sınırı", - "subnetPrefixLengthIpv4": "IPv4 için alt ağ önek uzunluğu", - "subnetPrefixLengthIpv6": "IPv6 için alt ağ önek uzunluğu", - "rateLimitAllowlist": "Hız sınırlama izin listesi", - "rateLimitAllowlistDescription": "Hız sınırlamasından hariç tutulan IP adresleri", - "dnsOptions": "DNS ayarları", - "editor": "Editör", - "editCustomRules": "Özel kuralları düzenle", - "savingCustomRules": "Özel kurallar kaydediliyor...", - "customRulesUpdatedSuccessfully": "Özel kurallar başarıyla güncellendi", - "customRulesNotUpdated": "Özel kurallar güncellenemedi", - "reorder": "Sırala", - "showHide": "Göster/gizle", - "noElementsReorderMessage": "Burada yeniden sıralamak için göster/gizle sekmesindeki bazı öğeleri etkinleştirin.", - "enablePlainDns": "Düz DNS'i etkinleştir", - "enablePlainDnsDescription": "Düz DNS varsayılan olarak etkindir. Tüm aygıtları şifrelenmiş DNS kullanmaya zorlamak için bunu devre dışı bırakabilirsiniz. Bunu yapmak için en az bir şifrelenmiş DNS protokolünü etkinleştirmeniz gerekir.", - "date": "Tarih", - "loadingChangelog": "Değişiklikler yükleniyor...", - "invalidIpOrUrl": "Geçersiz IP adresi veya URL", - "addPersistentClient": "Kalıcı istemci olarak ekle", - "blockThisClientOnly": "Yalnızca bu istemci için engelle", - "unblockThisClientOnly": "Yalnızca bu istemci için engeli kaldır", - "domainBlockedThisClient": "Bu istemci için {domain} engellendi", - "domainUnblockedThisClient": "Bu istemci için {domain} engeli kaldırıldı", - "disallowThisClient": "Bu istemciye izin verme", - "allowThisClient": "Bu istemciye izin ver", - "clientAllowedSuccessfully": "İstemciye başarıyla izin verildi", - "clientDisallowedSuccessfully": "İstemci başarıyla reddedildi", - "changesNotSaved": "Değişiklikler kaydedilemedi", - "allowingClient": "İstemciye izin veriliyor...", - "disallowingClient": "İstemci reddediliyor...", - "clientIpCopied": "İstemci IP'si panoya kopyalandı", - "clientNameCopied": "İstemci adı panoya kopyalandı", - "dnsServerAddressCopied": "DNS sunucu adresi panoya kopyalandı", - "select": "Seç", - "liveLogs": "Canlı günlükler", - "hereWillAppearRealtimeLogs": "Burada gerçek zamanlı günlükler görünecek.", - "applicationDetails": "Uygulama detayları", - "applicationDetailsDescription": "Uygulama deposu, mevcut olduğu mağazalar ve daha fazlası", - "myOtherApps": "Diğer uygulamalarım", - "myOtherAppsDescription": "Diğer uygulamalarımı kontrol et, bağış yap, destekle iletişime geç ve daha fazlası", - "topToBottom": "Yukarıdan aşağıya", - "bottomToTop": "Aşağıdan yukarıya", - "upstreamTimeout": "Üst sunucu zaman aşımı", - "upstreamTimeoutHelper": "Üst DNS sunucusundan yanıt bekleme süresini saniye cinsinden belirtir", - "fieldCannotBeEmpty": "Bu alan boş olamaz" + "topUpstreams": "Öne çıkan üst kaynaklar", + "averageUpstreamResponseTime": "Üst kaynak ortalama yanıt süresi" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index a789942..8d246eb 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -23,7 +23,7 @@ "invalidUsernamePassword": "用户名或密码错误", "tooManyAttempts": "尝试次数过多,请稍后再试", "cantReachServer": "无法连接服务器,请检查连接信息是否正确", - "sslError": "SSL 错误 转到 设置 > 高级设置 并启用 不检查 SSL 证书", + "sslError": "SSL 错误。转到 设置 > 高级设置 并启用 不检查 SSL 证书", "unknownError": "未知错误", "connectionNotCreated": "连接无法创建", "connecting": "正在连接...", @@ -158,7 +158,7 @@ "notSelected": "未选择", "resetFilters": "重置过滤器", "noLogsDisplay": "无日志可显示", - "noLogsThatOld": "选择的时间段可能没有日志 请尝试选择近期时间 ", + "noLogsThatOld": "选择的时间段可能没有日志。尝试选择近期时间。", "apply": "应用", "selectAll": "全选", "unselectAll": "取消全选", @@ -235,7 +235,7 @@ "urlNotValid": "URL 无效", "urlAbsolutePath": "URL 或绝对路径", "addingList": "正在添加订阅规则...", - "listAdded": "订阅规则添加成功 已添加项目:", + "listAdded": "订阅规则添加成功 。已添加项目:", "listAlreadyAdded": "订阅规则已被添加", "listUrlInvalid": "订阅规则 URL 无效", "listNotAdded": "无法添加订阅规则", @@ -282,7 +282,7 @@ "clientsNotLoaded": "无法加载客户端", "noAllowedClients": "没有已允许的客户端", "allowedClientsDescription": "如果此列表中有条目,AdGuard Home 将仅接受来自这些客户端的请求", - "blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求 如果已允许客户端中有条目,则会忽略此字段", + "blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求。如果已允许客户端中有条目,则会忽略此字段", "disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中", "addClientFieldDescription": "CIDR、IP 地址或客户端 ID", "clientIdentifier": "客户端标识符", @@ -358,7 +358,7 @@ "seconds": "{time} 秒", "leaseTimeNotValid": "租期无效", "restoreConfiguration": "重置配置", - "restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置 此操作无法撤消", + "restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置。此操作无法撤消", "changeInterface": "更改接口", "savingSettings": "正在保存设置...", "settingsSaved": "设置保存成功", @@ -372,7 +372,7 @@ "staticLeaseDeleted": "DHCP 静态租用删除成功", "staticLeaseNotDeleted": "无法删除 DHCP 静态租用", "deleteStaticLease": "删除静态租用", - "deleteStaticLeaseDescription": "DHCP 静态租用将被删除 此操作无法撤消", + "deleteStaticLeaseDescription": "DHCP 静态租用将被删除。此操作无法撤消", "addStaticLease": "添加静态租用", "macAddress": "MAC 地址", "macAddressNotValid": "MAC 地址无效", @@ -384,7 +384,7 @@ "staticLeaseExists": "DHCP 静态租用已存在", "serverNotConfigured": "未配置服务器", "restoreLeases": "重置租用", - "restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用 此操作无法撤消", + "restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用。此操作无法撤消", "restoringLeases": "正在重置租用...", "leasesRestored": "租用重置成功", "leasesNotRestored": "无法重置租用", @@ -427,24 +427,24 @@ "dnsSettingsDescription": "配置与 DNS 服务器的连接", "upstreamDns": "上游 DNS 服务器", "bootstrapDns": "引导 DNS 服务器", - "noUpstreamDns": "未添加上游 DNS 服务器 ", + "noUpstreamDns": "未添加上游 DNS 服务器。", "dnsMode": "DNS 模式", "noDnsMode": "未选择 DNS 模式", "loadBalancing": "负载均衡", "parallelRequests": "并行请求", "fastestIpAddress": "最快的 IP 地址", - "loadBalancingDescription": "每次查询一个上游服务器 AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器", + "loadBalancingDescription": "每次查询一个上游服务器。AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器", "parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器", - "fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址 这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性", - "noBootstrapDns": "未添加引导 DNS 服务器 ", - "bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址 ", + "fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址。这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性", + "noBootstrapDns": "未添加引导 DNS 服务器。", + "bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址。", "privateReverseDnsServers": "私有反向 DNS 服务器", - "privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器 这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\" 如果未设置,AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址 ", + "privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器。这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\"。如果未设置,AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址。", "reverseDnsDefault": "默认情况下,AdGuard Home 使用以下默认反向 DNS 解析器", "addItem": "添加项目", "noServerAddressesAdded": "未添加服务器地址", "usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器", - "usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询 如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN", + "usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询。如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN", "enableReverseResolving": "启用客户端 IP 地址的反向解析", "enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)", "dnsServerSettings": "AdGuard Home DNS 服务器设置", @@ -492,7 +492,7 @@ "dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存", "comment": "注释", "address": "地址", - "commentsDescription": "注释始终以 # 开头 您无需添加它,系统将自动添加", + "commentsDescription": "注释始终以 # 开头。您无需添加它,系统将自动添加", "encryptionSettings": "加密设置", "encryptionSettingsDescription": "加密(HTTPS/QUIC/TLS)支持", "loadingEncryptionSettings": "正在加载加密设置...", @@ -502,13 +502,13 @@ "enableEncryptionDescription": "如果启用加密,AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求", "serverConfiguration": "服务器配置", "domainName": "域名", - "domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证 如果未设置,这些功能将被禁用 必须与证书中的 DNS 名称之一匹配", + "domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证。如果未设置,这些功能将被禁用。必须与证书中的 DNS 名称之一匹配", "redirectHttps": "自动重定向到 HTTPS", "httpsPort": "HTTPS 端口", "tlsPort": "DNS-over-TLS 端口", "dnsOverQuicPort": "DNS-over-QUIC 端口", "certificates": "证书", - "certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链 您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买", + "certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链。您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买", "certificateFilePath": "设置证书文件路径", "pasteCertificateContent": "粘贴证书内容", "certificatePath": "证书路径", @@ -526,13 +526,13 @@ "encryptionConfigSaved": "加密配置已成功保存", "encryptionConfigNotSaved": "无法保存加密配置", "configError": "配置错误", - "enterOnlyCertificate": "只输入证书 不要输入 ---BEGIN--- 和 ---END--- 行 ", - "enterOnlyPrivateKey": "只输入密钥 不要输入 ---BEGIN--- 和 ---END--- 行 ", + "enterOnlyCertificate": "只输入证书。不要输入 ---BEGIN--- 和 ---END--- 行。", + "enterOnlyPrivateKey": "只输入密钥。不要输入 ---BEGIN--- 和 ---END--- 行。", "noItemsSearch": "没有匹配的项目", "clearSearch": "清除搜索", "exitSearch": "退出搜索", "searchClients": "搜索客户端", - "noClientsSearch": "没有匹配的客户端 ", + "noClientsSearch": "没有匹配的客户端。", "customization": "定制", "customizationDescription": "自定义此应用程序", "color": "颜色", @@ -598,7 +598,7 @@ "checkUpdates": "检查更新", "requestingUpdate": "正在请求更新...", "autoupdateUnavailable": "自动更新不可用", - "autoupdateUnavailableDescription": "此服务器不支持自动更新服务 这可能是因为服务器正在 Docker 容器中运行 您需要手动更新服务器", + "autoupdateUnavailableDescription": "此服务器不支持自动更新服务。这可能是因为服务器正在 Docker 容器中运行。您需要手动更新服务器", "minute": "{time} 分钟", "minutes": "{time} 分钟", "hour": "{time} 小时", @@ -606,7 +606,7 @@ "remainingTime": "剩余时间", "safeSearchSettings": "安全搜索设置", "loadingSafeSearchSettings": "正在加载安全搜索设置...", - "safeSearchSettingsNotLoaded": "加载安全搜索设置时出错 ", + "safeSearchSettingsNotLoaded": "加载安全搜索设置时出错。", "loadingLogsSettings": "正在加载日志设置...", "selectOptionLeftColumn": "在左侧栏中选择一个选项", "selectClientLeftColumn": "在左侧栏中选择一个客户端", @@ -619,7 +619,7 @@ "copyListUrl": "复制订阅规则 URL", "listUrlCopied": "订阅规则 URL 已复制到剪贴板", "unsupportedVersion": "不支持的版本", - "unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本 alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题", + "unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本。alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题", "iUnderstand": "我了解", "appUpdates": "应用程序更新", "usingLatestVersion": "您正在使用最新版本", @@ -658,136 +658,7 @@ "hideServerAddressDescription": "在主页上隐藏服务器地址", "topItemsOrder": "顶部项目顺序", "topItemsOrderDescription": "排列主页顶部项目列表", - "topItemsReorderInfo": "按住并滑动一个项目以重新排序 ", + "topItemsReorderInfo": "按住并滑动一个项目以重新排序。", "discardChanges": "放弃更改", - "discardChangesDescription": "您确定要放弃更改吗?", - "others": "其他", - "showChart": "显示图表", - "hideChart": "隐藏图表", - "showTopItemsChart": "显示顶部项目图表", - "showTopItemsChartDescription": "默认情况下在移动视图中显示顶部项目部分的环形图 ", - "openMenu": "打开菜单", - "closeMenu": "关闭菜单", - "openListUrl": "打开列表URL", - "selectionMode": "选择模式", - "enableDisableSelected": "启用或禁用选定项目", - "deleteSelected": "删除选定项目", - "deleteSelectedLists": "删除选定列表", - "allSelectedListsDeletedSuccessfully": "所有选定列表已成功删除 ", - "deletionResult": "删除结果", - "deletingLists": "正在删除列表...", - "failedElements": "失败元素", - "processingLists": "正在处理列表...", - "enableDisableResult": "启用或禁用结果", - "selectedListsEnabledDisabledSuccessfully": "所有选定列表已成功启用或禁用", - "sslWarning": "如果您正在使用带有自签名证书的 HTTPS 连接,请确保在设置 > 高级设置中启用 '不检查 SSL 证书' ", - "unsupportedServerVersion": "不支持的服务器版本", - "unsupportedServerVersionMessage": "您的 AdGuard Home 服务器版本过旧,不受 AdGuard Home Manager 支持 您需要将 AdGuard Home 服务器升级到更新的版本才能使用此应用程序 ", - "yourVersion": "您的版本:{version}", - "minimumRequiredVersion": "最低要求版本:{version}", - "topUpstreams": "主要上游", - "averageUpstreamResponseTime": "平均上游响应时间", - "dhcpNotAvailable": "DHCP 服务器不可用 ", - "osServerInstalledIncompatible": "服务器安装的操作系统与此功能不兼容 ", - "resetSettings": "重置设置", - "resetEncryptionSettingsDescription": "您确定要将加密设置重置为默认值吗?", - "resettingConfig": "正在重置配置...", - "configurationResetSuccessfully": "配置已成功重置", - "configurationResetError": "配置无法重置", - "testUpstreamDnsServers": "测试上游 DNS 服务器", - "errorTestUpstreamDns": "测试上游 DNS 服务器时出错 ", - "useCustomIpEdns": "使用 EDNS 的自定义 IP", - "useCustomIpEdnsDescription": "允许使用 EDNS 的自定义 IP", - "sortingOptions": "排序选项", - "fromHighestToLowest": "从高到低", - "fromLowestToHighest": "从低到高", - "queryLogsAndStatistics": "查询日志和统计", - "ignoreClientQueryLog": "在查询日志中忽略此客户端", - "ignoreClientStatistics": "在统计中忽略此客户端", - "savingChanges": "正在保存更改...", - "fallbackDnsServers": "备用 DNS 服务器", - "fallbackDnsServersDescription": "配置备用 DNS 服务器", - "fallbackDnsServersInfo": "当上游 DNS 服务器无响应时使用的备用 DNS 服务器列表 语法与上面的主上游字段相同 ", - "noFallbackDnsAdded": "未添加备用 DNS 服务器 ", - "blockedResponseTtl": "被阻止的响应 TTL", - "blockedResponseTtlDescription": "指定客户端应缓存过滤响应的秒数", - "invalidValue": "无效值", - "noDataChart": "没有数据显示此图表 ", - "noData": "无数据", - "unblockClient": "解除客户端封锁", - "blockingClient": "正在封锁客户端...", - "unblockingClient": "正在解除客户端封锁...", - "upstreamDnsCacheConfiguration": "DNS 上游缓存配置", - "enableDnsCachingClient": "为此客户端启用 DNS 缓存", - "dnsCacheSize": "DNS 缓存大小", - "nameInvalid": "名称是必需的", - "oneIdentifierRequired": "至少需要一个标识符", - "dnsCacheNumber": "DNS 缓存大小必须是一个数字", - "errors": "错误", - "redirectHttpsWarning": "如果您在 AdGuard Home 服务器上启用了 '自动重定向到 HTTPS',则必须选择 HTTPS 连接并使用服务器的 HTTPS 端口 ", - "logsSettingsDescription": "配置查询日志", - "ignoredDomains": "忽略的域名", - "noIgnoredDomainsAdded": "未添加忽略的域名", - "pauseServiceBlocking": "暂停服务阻止", - "newSchedule": "新计划", - "editSchedule": "编辑计划", - "timezone": "时区", - "monday": "星期一", - "tuesday": "星期二", - "wednesday": "星期三", - "thursday": "星期四", - "friday": "星期五", - "saturday": "星期六", - "sunday": "星期日", - "from": "从", - "to": "到", - "selectStartTime": "选择开始时间", - "selectEndTime": "选择结束时间", - "startTimeBeforeEndTime": "开始时间必须在结束时间之前 ", - "noBlockingScheduleThisDevice": "此设备没有阻止计划 ", - "selectTimezone": "选择时区", - "selectClientsFiltersInfo": "选择您想要显示的客户端 如果没有选择任何客户端,将显示所有客户端 ", - "noDataThisSection": "本节没有数据 ", - "statisticsSettings": "统计设置", - "statisticsSettingsDescription": "配置统计数据收集", - "loadingStatisticsSettings": "正在加载统计设置...", - "statisticsSettingsLoadError": "加载统计设置时发生错误 ", - "statisticsConfigUpdated": "统计设置成功更新", - "statisticsConfigNotUpdated": "统计设置无法更新", - "customTimeInHours": "自定义时间(以小时为单位)", - "invalidTime": "无效时间", - "removeDomain": "移除域名", - "addDomain": "添加域名", - "notLess1Hour": "时间不能少于 1 小时", - "rateLimit": "速率限制", - "subnetPrefixLengthIpv4": "IPv4 的子网前缀长度", - "subnetPrefixLengthIpv6": "IPv6 的子网前缀长度", - "rateLimitAllowlist": "速率限制白名单", - "rateLimitAllowlistDescription": "从速率限制中排除的 IP 地址", - "dnsOptions": "DNS 选项", - "editor": "编辑器", - "editCustomRules": "编辑自定义规则", - "savingCustomRules": "正在保存自定义规则...", - "customRulesUpdatedSuccessfully": "自定义规则成功更新", - "customRulesNotUpdated": "自定义规则无法更新", - "reorder": "重新排序", - "showHide": "显示/隐藏", - "noElementsReorderMessage": "在显示/隐藏标签页上启用一些元素,然后在这里重新排序 ", - "enablePlainDns": "启用普通 DNS", - "enablePlainDnsDescription": "默认启用普通 DNS 您可以禁用它,强制所有设备使用加密 DNS 要做到这一点,您必须至少启用一个加密 DNS 协议 ", - "date": "日期", - "loadingChangelog": "正在加载更新日志...", - "invalidIpOrUrl": "无效的 IP 地址或 URL", - "addPersistentClient": "添加为持久客户端", - "blockThisClientOnly": "仅为此客户端封锁", - "unblockThisClientOnly": "仅为此客户端解封", - "domainBlockedThisClient": "{domain} 已为此客户端封锁", - "domainUnblockedThisClient": "{domain} 已为此客户端解封", - "disallowThisClient": "禁止此客户端", - "allowThisClient": "允许此客户端", - "clientAllowedSuccessfully": "客户端成功允许", - "clientDisallowedSuccessfully": "客户端成功禁止", - "changesNotSaved": "更改无法保存", - "allowingClient": "正在允许客户端...", - "disallowingClient": "正在禁止客户端..." + "discardChangesDescription": "您确定要放弃更改吗?" } diff --git a/lib/l10n/app_zh_CN.arb b/lib/l10n/app_zh_CN.arb index a789942..8d246eb 100644 --- a/lib/l10n/app_zh_CN.arb +++ b/lib/l10n/app_zh_CN.arb @@ -23,7 +23,7 @@ "invalidUsernamePassword": "用户名或密码错误", "tooManyAttempts": "尝试次数过多,请稍后再试", "cantReachServer": "无法连接服务器,请检查连接信息是否正确", - "sslError": "SSL 错误 转到 设置 > 高级设置 并启用 不检查 SSL 证书", + "sslError": "SSL 错误。转到 设置 > 高级设置 并启用 不检查 SSL 证书", "unknownError": "未知错误", "connectionNotCreated": "连接无法创建", "connecting": "正在连接...", @@ -158,7 +158,7 @@ "notSelected": "未选择", "resetFilters": "重置过滤器", "noLogsDisplay": "无日志可显示", - "noLogsThatOld": "选择的时间段可能没有日志 请尝试选择近期时间 ", + "noLogsThatOld": "选择的时间段可能没有日志。尝试选择近期时间。", "apply": "应用", "selectAll": "全选", "unselectAll": "取消全选", @@ -235,7 +235,7 @@ "urlNotValid": "URL 无效", "urlAbsolutePath": "URL 或绝对路径", "addingList": "正在添加订阅规则...", - "listAdded": "订阅规则添加成功 已添加项目:", + "listAdded": "订阅规则添加成功 。已添加项目:", "listAlreadyAdded": "订阅规则已被添加", "listUrlInvalid": "订阅规则 URL 无效", "listNotAdded": "无法添加订阅规则", @@ -282,7 +282,7 @@ "clientsNotLoaded": "无法加载客户端", "noAllowedClients": "没有已允许的客户端", "allowedClientsDescription": "如果此列表中有条目,AdGuard Home 将仅接受来自这些客户端的请求", - "blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求 如果已允许客户端中有条目,则会忽略此字段", + "blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求。如果已允许客户端中有条目,则会忽略此字段", "disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中", "addClientFieldDescription": "CIDR、IP 地址或客户端 ID", "clientIdentifier": "客户端标识符", @@ -358,7 +358,7 @@ "seconds": "{time} 秒", "leaseTimeNotValid": "租期无效", "restoreConfiguration": "重置配置", - "restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置 此操作无法撤消", + "restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置。此操作无法撤消", "changeInterface": "更改接口", "savingSettings": "正在保存设置...", "settingsSaved": "设置保存成功", @@ -372,7 +372,7 @@ "staticLeaseDeleted": "DHCP 静态租用删除成功", "staticLeaseNotDeleted": "无法删除 DHCP 静态租用", "deleteStaticLease": "删除静态租用", - "deleteStaticLeaseDescription": "DHCP 静态租用将被删除 此操作无法撤消", + "deleteStaticLeaseDescription": "DHCP 静态租用将被删除。此操作无法撤消", "addStaticLease": "添加静态租用", "macAddress": "MAC 地址", "macAddressNotValid": "MAC 地址无效", @@ -384,7 +384,7 @@ "staticLeaseExists": "DHCP 静态租用已存在", "serverNotConfigured": "未配置服务器", "restoreLeases": "重置租用", - "restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用 此操作无法撤消", + "restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用。此操作无法撤消", "restoringLeases": "正在重置租用...", "leasesRestored": "租用重置成功", "leasesNotRestored": "无法重置租用", @@ -427,24 +427,24 @@ "dnsSettingsDescription": "配置与 DNS 服务器的连接", "upstreamDns": "上游 DNS 服务器", "bootstrapDns": "引导 DNS 服务器", - "noUpstreamDns": "未添加上游 DNS 服务器 ", + "noUpstreamDns": "未添加上游 DNS 服务器。", "dnsMode": "DNS 模式", "noDnsMode": "未选择 DNS 模式", "loadBalancing": "负载均衡", "parallelRequests": "并行请求", "fastestIpAddress": "最快的 IP 地址", - "loadBalancingDescription": "每次查询一个上游服务器 AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器", + "loadBalancingDescription": "每次查询一个上游服务器。AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器", "parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器", - "fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址 这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性", - "noBootstrapDns": "未添加引导 DNS 服务器 ", - "bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址 ", + "fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址。这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性", + "noBootstrapDns": "未添加引导 DNS 服务器。", + "bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址。", "privateReverseDnsServers": "私有反向 DNS 服务器", - "privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器 这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\" 如果未设置,AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址 ", + "privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器。这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\"。如果未设置,AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址。", "reverseDnsDefault": "默认情况下,AdGuard Home 使用以下默认反向 DNS 解析器", "addItem": "添加项目", "noServerAddressesAdded": "未添加服务器地址", "usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器", - "usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询 如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN", + "usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询。如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN", "enableReverseResolving": "启用客户端 IP 地址的反向解析", "enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)", "dnsServerSettings": "AdGuard Home DNS 服务器设置", @@ -492,7 +492,7 @@ "dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存", "comment": "注释", "address": "地址", - "commentsDescription": "注释始终以 # 开头 您无需添加它,系统将自动添加", + "commentsDescription": "注释始终以 # 开头。您无需添加它,系统将自动添加", "encryptionSettings": "加密设置", "encryptionSettingsDescription": "加密(HTTPS/QUIC/TLS)支持", "loadingEncryptionSettings": "正在加载加密设置...", @@ -502,13 +502,13 @@ "enableEncryptionDescription": "如果启用加密,AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求", "serverConfiguration": "服务器配置", "domainName": "域名", - "domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证 如果未设置,这些功能将被禁用 必须与证书中的 DNS 名称之一匹配", + "domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证。如果未设置,这些功能将被禁用。必须与证书中的 DNS 名称之一匹配", "redirectHttps": "自动重定向到 HTTPS", "httpsPort": "HTTPS 端口", "tlsPort": "DNS-over-TLS 端口", "dnsOverQuicPort": "DNS-over-QUIC 端口", "certificates": "证书", - "certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链 您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买", + "certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链。您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买", "certificateFilePath": "设置证书文件路径", "pasteCertificateContent": "粘贴证书内容", "certificatePath": "证书路径", @@ -526,13 +526,13 @@ "encryptionConfigSaved": "加密配置已成功保存", "encryptionConfigNotSaved": "无法保存加密配置", "configError": "配置错误", - "enterOnlyCertificate": "只输入证书 不要输入 ---BEGIN--- 和 ---END--- 行 ", - "enterOnlyPrivateKey": "只输入密钥 不要输入 ---BEGIN--- 和 ---END--- 行 ", + "enterOnlyCertificate": "只输入证书。不要输入 ---BEGIN--- 和 ---END--- 行。", + "enterOnlyPrivateKey": "只输入密钥。不要输入 ---BEGIN--- 和 ---END--- 行。", "noItemsSearch": "没有匹配的项目", "clearSearch": "清除搜索", "exitSearch": "退出搜索", "searchClients": "搜索客户端", - "noClientsSearch": "没有匹配的客户端 ", + "noClientsSearch": "没有匹配的客户端。", "customization": "定制", "customizationDescription": "自定义此应用程序", "color": "颜色", @@ -598,7 +598,7 @@ "checkUpdates": "检查更新", "requestingUpdate": "正在请求更新...", "autoupdateUnavailable": "自动更新不可用", - "autoupdateUnavailableDescription": "此服务器不支持自动更新服务 这可能是因为服务器正在 Docker 容器中运行 您需要手动更新服务器", + "autoupdateUnavailableDescription": "此服务器不支持自动更新服务。这可能是因为服务器正在 Docker 容器中运行。您需要手动更新服务器", "minute": "{time} 分钟", "minutes": "{time} 分钟", "hour": "{time} 小时", @@ -606,7 +606,7 @@ "remainingTime": "剩余时间", "safeSearchSettings": "安全搜索设置", "loadingSafeSearchSettings": "正在加载安全搜索设置...", - "safeSearchSettingsNotLoaded": "加载安全搜索设置时出错 ", + "safeSearchSettingsNotLoaded": "加载安全搜索设置时出错。", "loadingLogsSettings": "正在加载日志设置...", "selectOptionLeftColumn": "在左侧栏中选择一个选项", "selectClientLeftColumn": "在左侧栏中选择一个客户端", @@ -619,7 +619,7 @@ "copyListUrl": "复制订阅规则 URL", "listUrlCopied": "订阅规则 URL 已复制到剪贴板", "unsupportedVersion": "不支持的版本", - "unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本 alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题", + "unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本。alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题", "iUnderstand": "我了解", "appUpdates": "应用程序更新", "usingLatestVersion": "您正在使用最新版本", @@ -658,136 +658,7 @@ "hideServerAddressDescription": "在主页上隐藏服务器地址", "topItemsOrder": "顶部项目顺序", "topItemsOrderDescription": "排列主页顶部项目列表", - "topItemsReorderInfo": "按住并滑动一个项目以重新排序 ", + "topItemsReorderInfo": "按住并滑动一个项目以重新排序。", "discardChanges": "放弃更改", - "discardChangesDescription": "您确定要放弃更改吗?", - "others": "其他", - "showChart": "显示图表", - "hideChart": "隐藏图表", - "showTopItemsChart": "显示顶部项目图表", - "showTopItemsChartDescription": "默认情况下在移动视图中显示顶部项目部分的环形图 ", - "openMenu": "打开菜单", - "closeMenu": "关闭菜单", - "openListUrl": "打开列表URL", - "selectionMode": "选择模式", - "enableDisableSelected": "启用或禁用选定项目", - "deleteSelected": "删除选定项目", - "deleteSelectedLists": "删除选定列表", - "allSelectedListsDeletedSuccessfully": "所有选定列表已成功删除 ", - "deletionResult": "删除结果", - "deletingLists": "正在删除列表...", - "failedElements": "失败元素", - "processingLists": "正在处理列表...", - "enableDisableResult": "启用或禁用结果", - "selectedListsEnabledDisabledSuccessfully": "所有选定列表已成功启用或禁用", - "sslWarning": "如果您正在使用带有自签名证书的 HTTPS 连接,请确保在设置 > 高级设置中启用 '不检查 SSL 证书' ", - "unsupportedServerVersion": "不支持的服务器版本", - "unsupportedServerVersionMessage": "您的 AdGuard Home 服务器版本过旧,不受 AdGuard Home Manager 支持 您需要将 AdGuard Home 服务器升级到更新的版本才能使用此应用程序 ", - "yourVersion": "您的版本:{version}", - "minimumRequiredVersion": "最低要求版本:{version}", - "topUpstreams": "主要上游", - "averageUpstreamResponseTime": "平均上游响应时间", - "dhcpNotAvailable": "DHCP 服务器不可用 ", - "osServerInstalledIncompatible": "服务器安装的操作系统与此功能不兼容 ", - "resetSettings": "重置设置", - "resetEncryptionSettingsDescription": "您确定要将加密设置重置为默认值吗?", - "resettingConfig": "正在重置配置...", - "configurationResetSuccessfully": "配置已成功重置", - "configurationResetError": "配置无法重置", - "testUpstreamDnsServers": "测试上游 DNS 服务器", - "errorTestUpstreamDns": "测试上游 DNS 服务器时出错 ", - "useCustomIpEdns": "使用 EDNS 的自定义 IP", - "useCustomIpEdnsDescription": "允许使用 EDNS 的自定义 IP", - "sortingOptions": "排序选项", - "fromHighestToLowest": "从高到低", - "fromLowestToHighest": "从低到高", - "queryLogsAndStatistics": "查询日志和统计", - "ignoreClientQueryLog": "在查询日志中忽略此客户端", - "ignoreClientStatistics": "在统计中忽略此客户端", - "savingChanges": "正在保存更改...", - "fallbackDnsServers": "备用 DNS 服务器", - "fallbackDnsServersDescription": "配置备用 DNS 服务器", - "fallbackDnsServersInfo": "当上游 DNS 服务器无响应时使用的备用 DNS 服务器列表 语法与上面的主上游字段相同 ", - "noFallbackDnsAdded": "未添加备用 DNS 服务器 ", - "blockedResponseTtl": "被阻止的响应 TTL", - "blockedResponseTtlDescription": "指定客户端应缓存过滤响应的秒数", - "invalidValue": "无效值", - "noDataChart": "没有数据显示此图表 ", - "noData": "无数据", - "unblockClient": "解除客户端封锁", - "blockingClient": "正在封锁客户端...", - "unblockingClient": "正在解除客户端封锁...", - "upstreamDnsCacheConfiguration": "DNS 上游缓存配置", - "enableDnsCachingClient": "为此客户端启用 DNS 缓存", - "dnsCacheSize": "DNS 缓存大小", - "nameInvalid": "名称是必需的", - "oneIdentifierRequired": "至少需要一个标识符", - "dnsCacheNumber": "DNS 缓存大小必须是一个数字", - "errors": "错误", - "redirectHttpsWarning": "如果您在 AdGuard Home 服务器上启用了 '自动重定向到 HTTPS',则必须选择 HTTPS 连接并使用服务器的 HTTPS 端口 ", - "logsSettingsDescription": "配置查询日志", - "ignoredDomains": "忽略的域名", - "noIgnoredDomainsAdded": "未添加忽略的域名", - "pauseServiceBlocking": "暂停服务阻止", - "newSchedule": "新计划", - "editSchedule": "编辑计划", - "timezone": "时区", - "monday": "星期一", - "tuesday": "星期二", - "wednesday": "星期三", - "thursday": "星期四", - "friday": "星期五", - "saturday": "星期六", - "sunday": "星期日", - "from": "从", - "to": "到", - "selectStartTime": "选择开始时间", - "selectEndTime": "选择结束时间", - "startTimeBeforeEndTime": "开始时间必须在结束时间之前 ", - "noBlockingScheduleThisDevice": "此设备没有阻止计划 ", - "selectTimezone": "选择时区", - "selectClientsFiltersInfo": "选择您想要显示的客户端 如果没有选择任何客户端,将显示所有客户端 ", - "noDataThisSection": "本节没有数据 ", - "statisticsSettings": "统计设置", - "statisticsSettingsDescription": "配置统计数据收集", - "loadingStatisticsSettings": "正在加载统计设置...", - "statisticsSettingsLoadError": "加载统计设置时发生错误 ", - "statisticsConfigUpdated": "统计设置成功更新", - "statisticsConfigNotUpdated": "统计设置无法更新", - "customTimeInHours": "自定义时间(以小时为单位)", - "invalidTime": "无效时间", - "removeDomain": "移除域名", - "addDomain": "添加域名", - "notLess1Hour": "时间不能少于 1 小时", - "rateLimit": "速率限制", - "subnetPrefixLengthIpv4": "IPv4 的子网前缀长度", - "subnetPrefixLengthIpv6": "IPv6 的子网前缀长度", - "rateLimitAllowlist": "速率限制白名单", - "rateLimitAllowlistDescription": "从速率限制中排除的 IP 地址", - "dnsOptions": "DNS 选项", - "editor": "编辑器", - "editCustomRules": "编辑自定义规则", - "savingCustomRules": "正在保存自定义规则...", - "customRulesUpdatedSuccessfully": "自定义规则成功更新", - "customRulesNotUpdated": "自定义规则无法更新", - "reorder": "重新排序", - "showHide": "显示/隐藏", - "noElementsReorderMessage": "在显示/隐藏标签页上启用一些元素,然后在这里重新排序 ", - "enablePlainDns": "启用普通 DNS", - "enablePlainDnsDescription": "默认启用普通 DNS 您可以禁用它,强制所有设备使用加密 DNS 要做到这一点,您必须至少启用一个加密 DNS 协议 ", - "date": "日期", - "loadingChangelog": "正在加载更新日志...", - "invalidIpOrUrl": "无效的 IP 地址或 URL", - "addPersistentClient": "添加为持久客户端", - "blockThisClientOnly": "仅为此客户端封锁", - "unblockThisClientOnly": "仅为此客户端解封", - "domainBlockedThisClient": "{domain} 已为此客户端封锁", - "domainUnblockedThisClient": "{domain} 已为此客户端解封", - "disallowThisClient": "禁止此客户端", - "allowThisClient": "允许此客户端", - "clientAllowedSuccessfully": "客户端成功允许", - "clientDisallowedSuccessfully": "客户端成功禁止", - "changesNotSaved": "更改无法保存", - "allowingClient": "正在允许客户端...", - "disallowingClient": "正在禁止客户端..." + "discardChangesDescription": "您确定要放弃更改吗?" } diff --git a/lib/main.dart b/lib/main.dart index 6402b70..9734263 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,16 +2,16 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -import 'package:window_manager/window_manager.dart'; +import 'package:store_checker/store_checker.dart'; +import 'package:window_size/window_size.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -30,16 +30,14 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/constants/colors.dart'; import 'package:adguard_home_manager/config/globals.dart'; import 'package:adguard_home_manager/config/theme.dart'; -import 'package:adguard_home_manager/services/db/database.dart'; import 'package:adguard_home_manager/classes/http_override.dart'; +import 'package:adguard_home_manager/services/db/database.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { - await windowManager.ensureInitialized(); - WindowManager.instance.setMinimumSize(const Size(500, 700)); + setWindowMinSize(const Size(500, 500)); } if (Platform.isWindows || Platform.isLinux) { @@ -48,12 +46,8 @@ void main() async { } await dotenv.load(fileName: '.env'); - - final sharedPreferences = await SharedPreferences.getInstance(); - final AppConfigProvider appConfigProvider = AppConfigProvider( - sharedPreferencesInstance: sharedPreferences - ); + final AppConfigProvider appConfigProvider = AppConfigProvider(); final ServersProvider serversProvider = ServersProvider(); final StatusProvider statusProvider = StatusProvider(); final ClientsProvider clientsProvider = ClientsProvider(); @@ -73,15 +67,20 @@ void main() async { appConfigProvider.setIosInfo(iosInfo); } - if (sharedPreferences.getBool('overrideSslCheck') == true) { + final dbData = await loadDb(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31); + + if (dbData['appConfig']['overrideSslCheck'] == 1) { HttpOverrides.global = MyHttpOverrides(); } - final dbData = await loadDb(); - serversProvider.setDbInstance(dbData['dbInstance']); - serversProvider.saveFromDb(dbData['servers']); + if (Platform.isAndroid || Platform.isIOS) { + Source installationSource = await StoreChecker.getSource; + appConfigProvider.setInstallationSource(installationSource); + } - appConfigProvider.saveFromSharedPreferences(); + serversProvider.setDbInstance(dbData['dbInstance']); + appConfigProvider.saveFromDb(dbData['dbInstance'], dbData['appConfig']); + serversProvider.saveFromDb(dbData['servers']); PackageInfo appInfo = await PackageInfo.fromPlatform(); appConfigProvider.setAppInfo(appInfo); @@ -162,31 +161,6 @@ void main() async { (options) { options.dsn = dotenv.env['SENTRY_DSN']; options.sendDefaultPii = false; - options.beforeSend = (event, hint) { - if (event.throwable is HttpException) { - return null; - } - - if (event.message?.formatted.contains("HttpException") == true) { - return null; - } - - if ( - event.message?.formatted.contains("Unexpected character") ?? false || - (event.throwable != null && event.throwable!.toString().contains("Unexpected character")) - ) { - return null; - } - - if ( - event.message?.formatted.contains("Unexpected end of input") ?? false || - (event.throwable != null && event.throwable!.toString().contains("Unexpected end of input")) - ) { - return null; - } - - return event; - }; }, appRunner: () => startApp() ); @@ -196,53 +170,77 @@ void main() async { } } -class Main extends StatelessWidget { +class Main extends StatefulWidget { const Main({super.key}); + @override + State
    createState() => _MainState(); +} + +class _MainState extends State
    { + List modes = []; + DisplayMode? active; + DisplayMode? preferred; + + Future displayMode() async { + try { + modes = await FlutterDisplayMode.supported; + preferred = await FlutterDisplayMode.preferred; + active = await FlutterDisplayMode.active; + await FlutterDisplayMode.setHighRefreshRate(); + setState(() {}); + } catch (_) { + // ---- // + } + } + + @override + void initState() { + displayMode(); + + super.initState(); + } + @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - + return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) { - appConfigProvider.setSupportsDynamicTheme(lightDynamic != null && darkDynamic != null); - return MaterialApp( - title: 'AdGuard Home Manager', - theme: lightDynamic != null - ? appConfigProvider.useDynamicColor == true - ? lightTheme(lightDynamic) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]), - darkTheme: darkDynamic != null - ? appConfigProvider.useDynamicColor == true - ? darkTheme(darkDynamic) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]), - themeMode: appConfigProvider.selectedTheme, - debugShowCheckedModeBanner: false, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - AppLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', ''), - Locale('es', ''), - Locale('zh', ''), - Locale('zh', 'CN'), - Locale('pl', ''), - Locale('tr', ''), - Locale('ru', '') - ], - scaffoldMessengerKey: scaffoldMessengerKey, - navigatorKey: globalNavigatorKey, - builder: (context, child) => CustomMenuBar( - child: child!, - ), - home: const Layout(), - ); - } + builder: (lightDynamic, darkDynamic) => MaterialApp( + title: 'AdGuard Home Manager', + theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? lightTheme(lightDynamic) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]), + darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? darkTheme(darkDynamic) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]), + themeMode: appConfigProvider.selectedTheme, + debugShowCheckedModeBanner: false, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + AppLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', ''), + Locale('es', ''), + Locale('zh', ''), + Locale('zh', 'CN'), + Locale('pl', ''), + Locale('tr', '') + ], + scaffoldMessengerKey: scaffoldMessengerKey, + navigatorKey: globalNavigatorKey, + builder: (context, child) => CustomMenuBar( + child: child!, + ), + home: const Layout(), + ), ); } } \ No newline at end of file diff --git a/lib/models/applied_filters.dart b/lib/models/applied_filters.dart index f4ef1ee..2b60e71 100644 --- a/lib/models/applied_filters.dart +++ b/lib/models/applied_filters.dart @@ -1,7 +1,7 @@ class AppliedFiters { String selectedResultStatus = 'all'; String? searchText; - List clients; + List? clients; AppliedFiters({ required this.selectedResultStatus, diff --git a/lib/models/clients.dart b/lib/models/clients.dart index 9527804..1472a0f 100644 --- a/lib/models/clients.dart +++ b/lib/models/clients.dart @@ -87,11 +87,6 @@ class Client { final bool useGlobalBlockedServices; final bool useGlobalSettings; final SafeSearch? safeSearch; - final bool? ignoreQuerylog; - final bool? ignoreStatistics; - final bool? upstreamsCacheEnabled; - final int? upstreamsCacheSize; - final BlockedServicesSchedule? blockedServicesSchedule; Client({ required this.name, @@ -105,11 +100,6 @@ class Client { required this.useGlobalBlockedServices, required this.useGlobalSettings, required this.safeSearch, - required this.ignoreQuerylog, - required this.ignoreStatistics, - required this.upstreamsCacheEnabled, - required this.upstreamsCacheSize, - required this.blockedServicesSchedule, }); factory Client.fromJson(Map json) => Client( @@ -125,13 +115,6 @@ class Client { useGlobalSettings: json["use_global_settings"], safeSearch: json["safe_search"] != null ? SafeSearch.fromJson(json["safe_search"]) - : null, - ignoreQuerylog: json["ignore_querylog"], - ignoreStatistics: json["ignore_statistics"], - upstreamsCacheEnabled: json["upstreams_cache_enabled"], - upstreamsCacheSize: json["upstreams_cache_size"], - blockedServicesSchedule: json["blocked_services_schedule"] != null - ? BlockedServicesSchedule.fromJson(json["blocked_services_schedule"]) : null ); @@ -147,74 +130,5 @@ class Client { "safe_search": safeSearch, "use_global_blocked_services": useGlobalBlockedServices, "use_global_settings": useGlobalSettings, - "ignore_querylog": ignoreQuerylog, - "ignore_statistics": ignoreStatistics, - "upstreams_cache_enabled": upstreamsCacheEnabled, - "upstreams_cache_size": upstreamsCacheSize, - "blocked_services_schedule":blockedServicesSchedule?.toJson() - }; -} - -class BlockedServicesSchedule { - final String? timeZone; - final BlockedServicesScheduleDay? mon; - final BlockedServicesScheduleDay? tue; - final BlockedServicesScheduleDay? wed; - final BlockedServicesScheduleDay? thu; - final BlockedServicesScheduleDay? fri; - final BlockedServicesScheduleDay? sat; - final BlockedServicesScheduleDay? sun; - - BlockedServicesSchedule({ - this.timeZone, - this.mon, - this.tue, - this.wed, - this.thu, - this.fri, - this.sat, - this.sun - }); - - factory BlockedServicesSchedule.fromJson(Map json) => BlockedServicesSchedule( - timeZone: json["time_zone"], - mon: json["mon"] == null ? null : BlockedServicesScheduleDay.fromJson(json["mon"]), - tue: json["tue"] == null ? null : BlockedServicesScheduleDay.fromJson(json["tue"]), - wed: json["wed"] == null ? null : BlockedServicesScheduleDay.fromJson(json["wed"]), - thu: json["thu"] == null ? null : BlockedServicesScheduleDay.fromJson(json["thu"]), - fri: json["fri"] == null ? null : BlockedServicesScheduleDay.fromJson(json["fri"]), - sat: json["sat"] == null ? null : BlockedServicesScheduleDay.fromJson(json["sat"]), - sun: json["sun"] == null ? null : BlockedServicesScheduleDay.fromJson(json["sun"]), - ); - - Map toJson() => { - "time_zone": timeZone, - "mon": mon?.toJson(), - "tue": tue?.toJson(), - "wed": wed?.toJson(), - "thu": thu?.toJson(), - "fri": fri?.toJson(), - "sat": sat?.toJson(), - "sun": sun?.toJson(), - }; -} - -class BlockedServicesScheduleDay { - final int? start; - final int? end; - - BlockedServicesScheduleDay({ - this.start, - this.end, - }); - - factory BlockedServicesScheduleDay.fromJson(Map json) => BlockedServicesScheduleDay( - start: json["start"], - end: json["end"], - ); - - Map toJson() => { - "start": start, - "end": end, }; } \ No newline at end of file diff --git a/lib/models/dhcp.dart b/lib/models/dhcp.dart index 7953f47..21b460b 100644 --- a/lib/models/dhcp.dart +++ b/lib/models/dhcp.dart @@ -1,7 +1,7 @@ import 'dart:convert'; class DhcpModel { List networkInterfaces; - DhcpStatus? dhcpStatus; + DhcpStatus dhcpStatus; DhcpModel({ required this.networkInterfaces, @@ -81,8 +81,8 @@ class DhcpStatus { Map toJson() => { "interface_name": interfaceName, - "v4": v4?.toJson(), - "v6": v6?.toJson(), + "v4": v4 != null ? v4!.toJson() : null, + "v6": v6 != null ? v6!.toJson() : null, "leases": List.from(leases.map((x) => x)), "static_leases": List.from(staticLeases.map((x) => x)), "enabled": enabled, diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 7007644..4e85400 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -2,13 +2,10 @@ class DnsInfo { List upstreamDns; String? upstreamDnsFile; List bootstrapDns; - List? fallbackDns; bool protectionEnabled; int ratelimit; String blockingMode; bool ednsCsEnabled; - bool? ednsCsUseCustom; - String? ednsCsCustomIp; bool dnssecEnabled; bool disableIpv6; String? upstreamMode; @@ -22,23 +19,15 @@ class DnsInfo { String blockingIpv4; String blockingIpv6; List defaultLocalPtrUpstreams; - int? blockedResponseTtl; - int? ratelimitSubnetLenIpv4; - int? ratelimitSubnetLenIpv6; - List? ratelimitWhitelist; - int? upstreamTimeout; DnsInfo({ required this.upstreamDns, required this.upstreamDnsFile, required this.bootstrapDns, - required this.fallbackDns, required this.protectionEnabled, required this.ratelimit, required this.blockingMode, required this.ednsCsEnabled, - required this.ednsCsUseCustom, - required this.ednsCsCustomIp, required this.dnssecEnabled, required this.disableIpv6, required this.upstreamMode, @@ -52,24 +41,16 @@ class DnsInfo { required this.blockingIpv4, required this.blockingIpv6, required this.defaultLocalPtrUpstreams, - required this.blockedResponseTtl, - required this.ratelimitSubnetLenIpv4, - required this.ratelimitSubnetLenIpv6, - required this.ratelimitWhitelist, - required this.upstreamTimeout, }); factory DnsInfo.fromJson(Map json) => DnsInfo( upstreamDns: json["upstream_dns"] != null ? List.from(json["upstream_dns"].map((x) => x)) : [], upstreamDnsFile: json["upstream_dns_file"], bootstrapDns: json["bootstrap_dns"] != null ? List.from(json["bootstrap_dns"].map((x) => x)) : [], - fallbackDns: json["fallback_dns"] != null ? List.from(json["fallback_dns"].map((x) => x)) : [], protectionEnabled: json["protection_enabled"], ratelimit: json["ratelimit"], blockingMode: json["blocking_mode"], ednsCsEnabled: json["edns_cs_enabled"], - ednsCsUseCustom: json["edns_cs_use_custom"], - ednsCsCustomIp: json["edns_cs_custom_ip"], dnssecEnabled: json["dnssec_enabled"], disableIpv6: json["disable_ipv6"], upstreamMode: json["upstream_mode"], @@ -83,24 +64,16 @@ class DnsInfo { blockingIpv4: json["blocking_ipv4"], blockingIpv6: json["blocking_ipv6"], defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List.from(json["default_local_ptr_upstreams"].map((x) => x)) : [], - blockedResponseTtl: json["blocked_response_ttl"], - ratelimitSubnetLenIpv4: json["ratelimit_subnet_len_ipv4"], - ratelimitSubnetLenIpv6: json["ratelimit_subnet_len_ipv6"], - ratelimitWhitelist: json["ratelimit_whitelist"] != null ? List.from(json["ratelimit_whitelist"].map((x) => x)) : [], - upstreamTimeout: json["upstream_timeout"], ); Map toJson() => { "upstream_dns": List.from(upstreamDns.map((x) => x)), "upstream_dns_file": upstreamDnsFile, "bootstrap_dns": List.from(bootstrapDns.map((x) => x)), - "fallback_dns": List.from(bootstrapDns.map((x) => x)), "protection_enabled": protectionEnabled, "ratelimit": ratelimit, "blocking_mode": blockingMode, "edns_cs_enabled": ednsCsEnabled, - "edns_cs_use_custom": ednsCsUseCustom, - "edns_cs_custom_ip": ednsCsCustomIp, "dnssec_enabled": dnssecEnabled, "disable_ipv6": disableIpv6, "upstream_mode": upstreamMode, @@ -114,10 +87,5 @@ class DnsInfo { "blocking_ipv4": blockingIpv4, "blocking_ipv6": blockingIpv6, "default_local_ptr_upstreams": List.from(defaultLocalPtrUpstreams.map((x) => x)), - "blocked_response_ttl": blockedResponseTtl, - "ratelimit_subnet_len_ipv4": ratelimitSubnetLenIpv4, - "ratelimit_subnet_len_ipv6": ratelimitSubnetLenIpv6, - "ratelimit_whitelist": ratelimitWhitelist != null ? List.from(ratelimitWhitelist!.map((x) => x)) : null, - "upstream_timeout": upstreamTimeout, }; } diff --git a/lib/models/dns_statistics.dart b/lib/models/dns_statistics.dart index ed787ce..f279886 100644 --- a/lib/models/dns_statistics.dart +++ b/lib/models/dns_statistics.dart @@ -5,7 +5,7 @@ DnsStatistics dnsStatisticsFromJson(String str) => DnsStatistics.fromJson(json.d String dnsStatisticsToJson(DnsStatistics data) => json.encode(data.toJson()); class DnsStatistics { - final String? timeUnits; + final String timeUnits; final List> topQueriedDomains; final List> topClients; final List> topBlockedDomains; @@ -43,9 +43,9 @@ class DnsStatistics { factory DnsStatistics.fromJson(Map json) => DnsStatistics( timeUnits: json["time_units"], - topQueriedDomains: json["top_queried_domains"] != null ? List>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : [], - topClients: json["top_clients"] != null ? List>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : [], - topBlockedDomains: json["top_blocked_domains"] != null ? List>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))): [], + topQueriedDomains: List>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), + topClients: List>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), + topBlockedDomains: List>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), topUpstreamResponses: json["top_upstreams_responses"] != null ? List>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : null, topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : null, dnsQueries: List.from(json["dns_queries"].map((x) => x)), diff --git a/lib/models/encryption.dart b/lib/models/encryption.dart index 5fff3e0..1315868 100644 --- a/lib/models/encryption.dart +++ b/lib/models/encryption.dart @@ -39,7 +39,6 @@ class EncryptionData { final String certificatePath; final String privateKeyPath; final bool privateKeySaved; - final bool? servePlainDns; EncryptionData({ required this.validCert, @@ -66,7 +65,6 @@ class EncryptionData { required this.certificatePath, required this.privateKeyPath, required this.privateKeySaved, - required this.servePlainDns, }); @@ -95,7 +93,6 @@ class EncryptionData { certificatePath: json["certificate_path"], privateKeyPath: json["private_key_path"], privateKeySaved: json["private_key_saved"], - servePlainDns: json["serve_plain_dns"], ); Map toJson() => { @@ -123,7 +120,6 @@ class EncryptionData { "certificate_path": certificatePath, "private_key_path": privateKeyPath, "private_key_saved": privateKeySaved, - "serve_plain_dns": servePlainDns, }; } diff --git a/lib/models/filtering.dart b/lib/models/filtering.dart index 5e43bf4..7b521d2 100644 --- a/lib/models/filtering.dart +++ b/lib/models/filtering.dart @@ -63,7 +63,7 @@ class Filter { Map toJson() => { "url": url, "name": name, - "last_updated": lastUpdated?.toIso8601String(), + "last_updated": lastUpdated != null ? lastUpdated!.toIso8601String() : null, "id": id, "rules_count": rulesCount, "enabled": enabled, diff --git a/lib/models/filtering_status.dart b/lib/models/filtering_status.dart index 83908c3..bfd219d 100644 --- a/lib/models/filtering_status.dart +++ b/lib/models/filtering_status.dart @@ -65,7 +65,7 @@ class Filter { Map toJson() => { "url": url, "name": name, - "last_updated": lastUpdated?.toIso8601String(), + "last_updated": lastUpdated != null ? lastUpdated!.toIso8601String() : null, "id": id, "rules_count": rulesCount, "enabled": enabled, diff --git a/lib/models/logs.dart b/lib/models/logs.dart index b4991be..9ba3738 100644 --- a/lib/models/logs.dart +++ b/lib/models/logs.dart @@ -30,7 +30,7 @@ class LogsData { Map toJson() => { "data": List.from(data.map((x) => x.toJson())), - "oldest": oldest?.toIso8601String(), + "oldest": oldest != null ? oldest!.toIso8601String() : null, }; } diff --git a/lib/models/menu_option.dart b/lib/models/menu_option.dart index e898514..b89f8d9 100644 --- a/lib/models/menu_option.dart +++ b/lib/models/menu_option.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class MenuOption { final IconData? icon; final String title; - final void Function() action; + final void Function(dynamic) action; final bool? disabled; const MenuOption({ diff --git a/lib/models/querylog_config.dart b/lib/models/querylog_config.dart deleted file mode 100644 index dfb80b4..0000000 --- a/lib/models/querylog_config.dart +++ /dev/null @@ -1,27 +0,0 @@ -class QueryLogConfig { - final List? ignored; - final int? interval; - final bool? enabled; - final bool? anonymizeClientIp; - - QueryLogConfig({ - this.ignored, - this.interval, - this.enabled, - this.anonymizeClientIp, - }); - - factory QueryLogConfig.fromJson(Map json) => QueryLogConfig( - ignored: json["ignored"] == null ? [] : List.from(json["ignored"]!.map((x) => x)), - interval: json["interval"], - enabled: json["enabled"], - anonymizeClientIp: json["anonymize_client_ip"], - ); - - Map toJson() => { - "ignored": ignored == null ? [] : List.from(ignored!.map((x) => x)), - "interval": interval, - "enabled": enabled, - "anonymize_client_ip": anonymizeClientIp, - }; -} diff --git a/lib/models/server_status.dart b/lib/models/server_status.dart index 16e20b4..b43dbd1 100644 --- a/lib/models/server_status.dart +++ b/lib/models/server_status.dart @@ -21,7 +21,6 @@ class ServerStatus { bool? safeSearchPixabay; bool? safeSearchYandex; bool? safeSearchYoutube; - bool dhcpAvailable; ServerStatus({ required this.stats, @@ -40,8 +39,7 @@ class ServerStatus { required this.safeSearchDuckduckgo, required this.safeSearchPixabay, required this.safeSearchYandex, - required this.safeSearchYoutube, - required this.dhcpAvailable, + required this.safeSearchYoutube }); factory ServerStatus.fromJson(Map json) => ServerStatus( @@ -66,6 +64,5 @@ class ServerStatus { safeSearchPixabay: json['safeSearch']['pixabay'], safeSearchYandex: json['safeSearch']['yandex'], safeSearchYoutube: json['safeSearch']['youtube'], - dhcpAvailable: json['status']['dhcp_available'] ?? false ); } \ No newline at end of file diff --git a/lib/models/statistics_config.dart b/lib/models/statistics_config.dart deleted file mode 100644 index f504708..0000000 --- a/lib/models/statistics_config.dart +++ /dev/null @@ -1,23 +0,0 @@ -class StatisticsConfig { - final List? ignored; - final int? interval; - final bool? enabled; - - StatisticsConfig({ - this.ignored, - this.interval, - this.enabled, - }); - - factory StatisticsConfig.fromJson(Map json) => StatisticsConfig( - ignored: json["ignored"] == null ? [] : List.from(json["ignored"]!.map((x) => x)), - interval: json["interval"], - enabled: json["enabled"], - ); - - Map toJson() => { - "ignored": ignored == null ? [] : List.from(ignored!.map((x) => x)), - "interval": interval, - "enabled": enabled, - }; -} diff --git a/lib/providers/app_config_provider.dart b/lib/providers/app_config_provider.dart index bb6887c..d44b5af 100644 --- a/lib/providers/app_config_provider.dart +++ b/lib/providers/app_config_provider.dart @@ -1,22 +1,22 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/scheduler.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:store_checker/store_checker.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:sqflite/sqlite_api.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/config/home_top_items_default_order.dart'; import 'package:adguard_home_manager/models/github_release.dart'; +import 'package:adguard_home_manager/services/db/queries.dart'; +import 'package:adguard_home_manager/functions/conversions.dart'; import 'package:adguard_home_manager/models/app_log.dart'; class AppConfigProvider with ChangeNotifier { - final SharedPreferences sharedPreferencesInstance; - - AppConfigProvider({ - required this.sharedPreferencesInstance - }); + Database? _dbInstance; PackageInfo? _appInfo; AndroidDeviceInfo? _androidDeviceInfo; @@ -28,35 +28,38 @@ class AppConfigProvider with ChangeNotifier { bool _showingSnackbar = false; - bool _supportsDynamicTheme = true; int _selectedTheme = 0; bool _useDynamicColor = true; int _staticColor = 0; - final bool _useThemeColorForStatus = false; + bool _useThemeColorForStatus = false; int _selectedClientsTab = 0; int _selectedFiltersTab = 0; List _homeTopItemsOrder = homeTopItemsDefaultOrder; - bool _hideServerAddress = false; + int _hideServerAddress = 0; final List _logs = []; - bool _overrideSslCheck = false; + int _overrideSslCheck = 0; - bool _hideZeroValues = false; + int _hideZeroValues = 0; - bool _showTimeLogs = false; + int _showTimeLogs = 0; - bool _showIpLogs = false; + int _showIpLogs = 0; - bool _combinedChartHome = false; + int _combinedChartHome = 0; + + int _showTopItemsChart = 0; String? _doNotRememberVersion; GitHubRelease? _appUpdatesAvailable; + Source _installationSource = Source.UNKNOWN; + PackageInfo? get getAppInfo { return _appInfo; } @@ -87,10 +90,6 @@ class AppConfigProvider with ChangeNotifier { } } - bool get supportsDynamicTheme { - return _supportsDynamicTheme; - } - int get selectedThemeNumber { return _selectedTheme; } @@ -108,11 +107,11 @@ class AppConfigProvider with ChangeNotifier { } bool get overrideSslCheck { - return _overrideSslCheck; + return _overrideSslCheck == 1 ? true : false; } bool get hideZeroValues { - return _hideZeroValues; + return _hideZeroValues == 1 ? true : false; } int get selectedScreen { @@ -136,15 +135,15 @@ class AppConfigProvider with ChangeNotifier { } bool get showTimeLogs { - return _showTimeLogs; + return _showTimeLogs == 1 ? true : false; } bool get showIpLogs { - return _showIpLogs; + return _showIpLogs == 1 ? true : false; } bool get combinedChartHome { - return _combinedChartHome; + return _combinedChartHome == 1 ? true : false; } String? get doNotRememberVersion { @@ -159,16 +158,24 @@ class AppConfigProvider with ChangeNotifier { return _appUpdatesAvailable; } + Source get installationSource { + return _installationSource; + } + List get homeTopItemsOrder { return _homeTopItemsOrder; } bool get hideServerAddress { - return _hideServerAddress; + return _hideServerAddress == 1 ? true : false; } - void setSupportsDynamicTheme(bool value) { - _supportsDynamicTheme = value; + bool get showTopItemsChart { + return _showTopItemsChart == 1 ? true : false; + } + + void setDbInstance(Database db) { + _dbInstance = db; } void setAppInfo(PackageInfo appInfo) { @@ -220,146 +227,230 @@ class AppConfigProvider with ChangeNotifier { notifyListeners(); } + void setInstallationSource(Source value) { + _installationSource = value; + notifyListeners(); + } + Future setOverrideSslCheck(bool status) async { - try { - sharedPreferencesInstance.setBool('overrideSslCheck', status); - _overrideSslCheck = status; + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'overrideSslCheck', + value: status == true ? 1 : 0 + ); + if (updated == true) { + _overrideSslCheck = status == true ? 1 : 0; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setHideZeroValues(bool status) async { - try { - sharedPreferencesInstance.setBool('hideZeroValues', status); - _hideZeroValues = status; + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'hideZeroValues', + value: status == true ? 1 : 0 + ); + if (updated == true) { + _hideZeroValues = status == true ? 1 : 0; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setshowTimeLogs(bool status) async { - try { - sharedPreferencesInstance.setBool('showTimeLogs', status); - _showTimeLogs = status; + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'showTimeLogs', + value: status == true ? 1 : 0 + ); + if (updated == true) { + _showTimeLogs = status == true ? 1 : 0; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setShowIpLogs(bool status) async { - try { - sharedPreferencesInstance.setBool('showIpLogs', status); - _showIpLogs = status; + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'showIpLogs', + value: status == true ? 1 : 0 + ); + if (updated == true) { + _showIpLogs = status == true ? 1 : 0; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setSelectedTheme(int value) async { - try { - sharedPreferencesInstance.setInt('selectedTheme', value); + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'theme', + value: value + ); + if (updated == true) { _selectedTheme = value; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setUseDynamicColor(bool value) async { - try { - sharedPreferencesInstance.setBool('useDynamicColor', value); + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'useDynamicColor', + value: value == true ? 1 : 0 + ); + if (updated == true) { _useDynamicColor = value; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { + return false; + } + } + + Future setUseThemeColorForStatus(bool value) async { + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'useThemeColorForStatus', + value: value == true ? 1 : 0 + ); + if (updated == true) { + _useThemeColorForStatus = value; + notifyListeners(); + return true; + } + else { return false; } } Future setCombinedChartHome(bool value) async { - try { - sharedPreferencesInstance.setBool('combinedChart', value); - _combinedChartHome = value; + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'combinedChart', + value: value == true ? 1 : 0 + ); + if (updated == true) { + _combinedChartHome = value == true ? 1 : 0; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setStaticColor(int value) async { - try { - sharedPreferencesInstance.setInt('staticColor', value); + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'staticColor', + value: value + ); + if (updated == true) { _staticColor = value; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setHomeTopItemsOrder(List order) async { - try { - sharedPreferencesInstance.setStringList('homeTopItemsOrder', List.from(order.map((e) => e.name))); + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'homeTopItemsOrder', + value: jsonEncode(List.from(order.map((e) => e.name))) + ); + if (updated == true) { _homeTopItemsOrder = order; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } Future setHideServerAddress(bool value) async { - try { - sharedPreferencesInstance.setBool('hideServerAddress', value); - _hideServerAddress = value; + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'hideServerAddress', + value: value == true ? 1 : 0 + ); + if (updated == true) { + _hideServerAddress = value == true ? 1 : 0; notifyListeners(); return true; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } + else { return false; } } + Future setShowTopItemsChart(bool value) async { + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'showTopItemsChart', + value: value == true ? 1 : 0 + ); + if (updated == true) { + _showTopItemsChart = value == true ? 1 : 0; + notifyListeners(); + return true; + } + else { + return false; + } + } + + Future setDoNotRememberVersion(String value) async { - final updated = await sharedPreferencesInstance.setString('hideServerAddress', value); + final updated = await updateConfigQuery( + db: _dbInstance!, + column: 'doNotRememberVersion', + value: value + ); return updated; } - - void saveFromSharedPreferences() { - _selectedTheme = sharedPreferencesInstance.getInt('selectedTheme') ?? 0; - _overrideSslCheck = sharedPreferencesInstance.getBool('overrideSslCheck') ?? false; - _hideZeroValues = sharedPreferencesInstance.getBool('hideZeroValues') ?? false; - _useDynamicColor = sharedPreferencesInstance.getBool('useDynamicColor') ?? true; - _staticColor = sharedPreferencesInstance.getInt('staticColor') ?? 0; - _showTimeLogs = sharedPreferencesInstance.getBool('showTimeLogs') ?? false; - _doNotRememberVersion = sharedPreferencesInstance.getString('doNotRememberVersion'); - _showIpLogs = sharedPreferencesInstance.getBool('showIpLogs') ?? false; - _combinedChartHome = sharedPreferencesInstance.getBool('combinedChart') ?? false; - _hideServerAddress = sharedPreferencesInstance.getBool('hideServerAddress') ?? false; - if (sharedPreferencesInstance.getStringList('homeTopItemsOrder') != null) { + + void saveFromDb(Database dbInstance, Map dbData) { + _selectedTheme = dbData['theme'] ?? 0; + _overrideSslCheck = dbData['overrideSslCheck'] ?? 0; + _hideZeroValues = dbData['hideZeroValues']; + _useDynamicColor = convertFromIntToBool(dbData['useDynamicColor'])!; + _staticColor = dbData['staticColor'] ?? 0; + _useThemeColorForStatus = dbData['useThemeColorForStatus'] != null ? convertFromIntToBool(dbData['useThemeColorForStatus'])! : false; + _showTimeLogs = dbData['showTimeLogs'] ?? 0; + _doNotRememberVersion = dbData['doNotRememberVersion']; + _showIpLogs = dbData['showIpLogs'] ?? 0; + _combinedChartHome = dbData['combinedChart'] ?? 0; + _hideServerAddress = dbData['hideServerAddress']; + _showTopItemsChart = dbData['showTopItemsChart']; + if (dbData['homeTopItemsOrder'] != null) { try { - _homeTopItemsOrder = List.from( - List.from(sharedPreferencesInstance.getStringList('homeTopItemsOrder')!).map((e) { + final itemsOrder = List.from( + List.from(jsonDecode(dbData['homeTopItemsOrder'])).map((e) { switch (e) { case 'queriedDomains': return HomeTopItems.queriedDomains; @@ -381,10 +472,18 @@ class AppConfigProvider with ChangeNotifier { } }).where((e) => e != null).toList() ); + final missingItems = homeTopItemsDefaultOrder.where((e) => !itemsOrder.contains(e)); + _homeTopItemsOrder = [ + ...itemsOrder, + ...missingItems + ]; } catch (e) { Sentry.captureException(e); _homeTopItemsOrder = homeTopItemsDefaultOrder; } } + + _dbInstance = dbInstance; + notifyListeners(); } } \ No newline at end of file diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index 36185cc..bfcf19d 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -190,16 +190,6 @@ class ClientsProvider with ChangeNotifier { "blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [], }; - if (body['allowed_clients']!.contains(item)) { - body['allowed_clients'] = body['allowed_clients']!.where((e) => e != item).toList(); - } - else if (body['disallowed_clients']!.contains(item)) { - body['disallowed_clients'] = body['disallowed_clients']!.where((e) => e != item).toList(); - } - else if (body['blocked_hosts']!.contains(item)) { - body['blocked_hosts'] = body['blocked_hosts']!.where((e) => e != item).toList(); - } - if (type == AccessSettingsList.allowed) { body['allowed_clients']!.add(item); } @@ -233,18 +223,6 @@ class ClientsProvider with ChangeNotifier { } } - AccessSettingsList? checkClientList(String client) { - if (_clients!.clientsAllowedBlocked!.allowedClients.contains(client)) { - return AccessSettingsList.allowed; - } - else if (_clients!.clientsAllowedBlocked!.disallowedClients.contains(client)) { - return AccessSettingsList.disallowed; - } - else { - return null; - } - } - Future removeClientList(String client, AccessSettingsList type) async { Map> body = { "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], diff --git a/lib/providers/dhcp_provider.dart b/lib/providers/dhcp_provider.dart index 4fbd66b..637f870 100644 --- a/lib/providers/dhcp_provider.dart +++ b/lib/providers/dhcp_provider.dart @@ -69,7 +69,7 @@ class DhcpProvider with ChangeNotifier { if (result.successful == true) { DhcpModel data = dhcp!; - data.dhcpStatus!.staticLeases = data.dhcpStatus!.staticLeases.where((l) => l.mac != lease.mac).toList(); + data.dhcpStatus.staticLeases = data.dhcpStatus.staticLeases.where((l) => l.mac != lease.mac).toList(); setDhcpData(data); return true; } @@ -90,7 +90,7 @@ class DhcpProvider with ChangeNotifier { if (result.successful == true) { DhcpModel data = dhcp!; - data.dhcpStatus!.staticLeases.add(lease); + data.dhcpStatus.staticLeases.add(lease); setDhcpData(data); return result; } diff --git a/lib/providers/dns_provider.dart b/lib/providers/dns_provider.dart index a59c3c5..0929f3a 100644 --- a/lib/providers/dns_provider.dart +++ b/lib/providers/dns_provider.dart @@ -112,22 +112,6 @@ class DnsProvider with ChangeNotifier { } } - Future saveFallbackDnsConfig(Map value) async { - final result = await _serversProvider!.apiClient2!.setDnsConfig( - data: value - ); - - if (result.successful == true) { - DnsInfo data = dnsInfo!; - data.fallbackDns = List.from(value['fallback_dns']); - setDnsInfoData(data); - return result; - } - else { - return result; - } - } - Future saveCacheCacheConfig(Map value) async { final result = await _serversProvider!.apiClient2!.setDnsConfig( data: value @@ -151,26 +135,16 @@ class DnsProvider with ChangeNotifier { final result = await _serversProvider!.apiClient2!.setDnsConfig( data: value ); - - void updateValue(dynamic parameter, dynamic value) { - if (value != null) { - parameter = value; - } - } if (result.successful == true) { DnsInfo data = dnsInfo!; - updateValue(data.ratelimit, value['ratelimit']); - updateValue(data.ednsCsEnabled, value['edns_cs_enabled']); - updateValue(data.dnssecEnabled, value['dnssec_enabled']); - updateValue(data.disableIpv6, value['disable_ipv6']); - updateValue(data.blockingMode, value['blocking_mode']); - updateValue(data.blockingIpv4, value['blocking_ipv4']); - updateValue(data.blockingIpv6, value['blocking_ipv6']); - updateValue(data.blockedResponseTtl, value['blocked_response_ttl']); - updateValue(data.ratelimitSubnetLenIpv4, value['ratelimit_subnet_len_ipv4']); - updateValue(data.ratelimitSubnetLenIpv6, value['ratelimit_subnet_len_ipv6']); - updateValue(data.ratelimitWhitelist, value['ratelimit_whitelist']); + data.ratelimit = value['ratelimit']; + data.ednsCsEnabled = value['edns_cs_enabled']; + data.dnssecEnabled = value['dnssec_enabled']; + data.disableIpv6 = value['disable_ipv6']; + data.blockingMode = value['blocking_mode']; + data.blockingIpv4 = value['blocking_ipv4']; + data.blockingIpv6 = value['blocking_ipv6']; setDnsInfoData(data); return result; } diff --git a/lib/providers/filtering_provider.dart b/lib/providers/filtering_provider.dart index 9947233..a264429 100644 --- a/lib/providers/filtering_provider.dart +++ b/lib/providers/filtering_provider.dart @@ -133,6 +133,7 @@ class FilteringProvider with ChangeNotifier { if (result2.successful == true) { _filtering = result2.content as Filtering; notifyListeners(); + print(result.content); return { "success": true, "data": result.content @@ -288,23 +289,6 @@ class FilteringProvider with ChangeNotifier { } } - Future setCustomRules(List rules) async { - final newRules = rules.where((r) => r != " " && r != "").toList(); - final result = await _serversProvider!.apiClient2!.setCustomRules(rules: newRules); - - if (result.successful == true) { - Filtering filteringData = filtering!; - filteringData.userRules = newRules; - _filtering = filteringData; - notifyListeners(); - return true; - } - else { - notifyListeners(); - return false; - } - } - Future> addList({required String name, required String url, required String type}) async { final result1 = await _serversProvider!.apiClient2!.addFilteringList( data: { diff --git a/lib/providers/live_logs_provider.dart b/lib/providers/live_logs_provider.dart deleted file mode 100644 index 044dd57..0000000 --- a/lib/providers/live_logs_provider.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/widgets.dart'; - -import 'package:adguard_home_manager/models/logs.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; - -class LiveLogsProvider with ChangeNotifier { - ServersProvider? _serversProvider; - - update(ServersProvider? provider) { - _serversProvider = provider; - } - - bool _isDisposed = false; - - @override - void dispose() { - _isDisposed = true; - super.dispose(); - } - - List _logs = []; - - List get logs { - return _logs; - } - - DateTime? _lastTime; - - void startFetchLogs() { - _lastTime = DateTime.now(); - _fetchLogs(); - } - - void _fetchLogs() async { - if (_lastTime == null) return; - final result = await _serversProvider!.apiClient2!.getLogs( - count: 100 - ); - if (result.successful == false || result.content == null) return; - final valid = (result.content as LogsData).data.where((e) => e.time.isAfter(_lastTime!)); - _logs = [...valid, ..._logs]; - _lastTime = DateTime.now(); - notifyListeners(); - - await Future.delayed(const Duration(seconds: 2)); - if (_isDisposed == true) return; - _fetchLogs(); - } -} \ No newline at end of file diff --git a/lib/providers/logs_provider.dart b/lib/providers/logs_provider.dart index cba46c0..7861e6d 100644 --- a/lib/providers/logs_provider.dart +++ b/lib/providers/logs_provider.dart @@ -20,7 +20,7 @@ class LogsProvider with ChangeNotifier { DateTime? _logsOlderThan; String _selectedResultStatus = 'all'; String? _searchText; - List _selectedClients = []; + List? _selectedClients; int _logsQuantity = 100; int _offset = 0; @@ -30,7 +30,7 @@ class LogsProvider with ChangeNotifier { AppliedFiters _appliedFilters = AppliedFiters( selectedResultStatus: 'all', searchText: null, - clients: [] + clients: null ); LoadStatus get loadStatus { @@ -65,7 +65,7 @@ class LogsProvider with ChangeNotifier { return _offset; } - List get selectedClients { + List? get selectedClients { return _selectedClients; } @@ -114,15 +114,9 @@ class LogsProvider with ChangeNotifier { _offset = value; } - void setSelectedResultStatus({ - required String value, - bool? refetch - }) { + void setSelectedResultStatus(String value) { _selectedResultStatus = value; notifyListeners(); - if (refetch = true) { - filterLogs(); - } } void setSearchText(String? value) { @@ -131,7 +125,7 @@ class LogsProvider with ChangeNotifier { } void setSelectedClients(List? clients) { - _selectedClients = clients ?? []; + _selectedClients = clients; notifyListeners(); } @@ -177,18 +171,18 @@ class LogsProvider with ChangeNotifier { if (loadingMore != null && loadingMore == true && logsData != null) { LogsData newLogsData = result.content; newLogsData.data = [...logsData!.data, ...(result.content as LogsData).data]; - if (appliedFilters.clients.isNotEmpty) { + if (appliedFilters.clients != null) { newLogsData.data = newLogsData.data.where( - (item) => appliedFilters.clients.contains(item.client) + (item) => appliedFilters.clients!.contains(item.client) ).toList(); } _logsData = newLogsData; } else { LogsData newLogsData = result.content; - if (appliedFilters.clients.isNotEmpty) { + if (appliedFilters.clients != null) { newLogsData.data = newLogsData.data.where( - (item) => appliedFilters.clients.contains(item.client) + (item) => appliedFilters.clients!.contains(item.client) ).toList(); } _logsData = newLogsData; @@ -217,7 +211,7 @@ class LogsProvider with ChangeNotifier { _appliedFilters = AppliedFiters( selectedResultStatus: 'all', searchText: null, - clients: [] + clients: null ); if (result.successful == true) { @@ -254,9 +248,9 @@ class LogsProvider with ChangeNotifier { if (result.successful == true) { LogsData newLogsData = result.content as LogsData; - if (appliedFilters.clients.isNotEmpty) { + if (appliedFilters.clients != null) { newLogsData.data = newLogsData.data.where( - (item) => appliedFilters.clients.contains(item.client) + (item) => appliedFilters.clients!.contains(item.client) ).toList(); } _logsData = newLogsData; diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 2cceaab..0a0bc08 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:animations/animations.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/clients.dart'; import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart'; import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; @@ -29,13 +29,13 @@ class AddedList extends StatefulWidget { final bool splitView; const AddedList({ - super.key, + Key? key, required this.scrollController, required this.data, required this.onClientSelected, this.selectedClient, required this.splitView - }); + }) : super(key: key); @override State createState() => _AddedListState(); @@ -75,21 +75,21 @@ class _AddedListState extends State { void confirmEditClient(Client client) async { ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.savingChanges); + processModal.open(AppLocalizations.of(context)!.addingClient); final result = await clientsProvider.editClient(client); processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientUpdatedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientNotUpdated, color: Colors.red @@ -107,16 +107,16 @@ class _AddedListState extends State { if (result == true) { if (widget.splitView == true) { - Navigator.of(clientsNavigatorKey.currentContext!).popUntil((route) => false); + SplitView.of(context).popUntil(0); } - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientDeletedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientNotDeleted, color: Colors.red diff --git a/lib/screens/clients/client/active_client_tile.dart b/lib/screens/clients/client/active_client_tile.dart index 7a7a825..76c928c 100644 --- a/lib/screens/clients/client/active_client_tile.dart +++ b/lib/screens/clients/client/active_client_tile.dart @@ -28,11 +28,11 @@ class ActiveClientTile extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: OptionsMenu( - options: (_) => [ + options: [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: client.name != '' ? client.name! : client.ip, @@ -99,11 +99,11 @@ class ActiveClientTile extends StatelessWidget { } else { return OptionsMenu( - options: (_) => [ + options: [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: client.name != '' ? client.name! : client.ip, diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index 5af9e46..4f4037e 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -46,11 +46,11 @@ class _AddedClientTileState extends State { color: Colors.transparent, borderRadius: BorderRadius.circular(28), child: OptionsMenu( - options: (_) => [ + options: [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), successMessage: AppLocalizations.of(context)!.copiedClipboard, ) @@ -81,7 +81,7 @@ class _AddedClientTileState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.client.name, + widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w400, @@ -89,15 +89,6 @@ class _AddedClientTileState extends State { ), ), const SizedBox(height: 8), - Text( - widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 8), Row( children: [ Icon( @@ -173,16 +164,16 @@ class _AddedClientTileState extends State { } else { return OptionsMenu( - options: (_) => [ + options: [ if (widget.onEdit != null) MenuOption( title: AppLocalizations.of(context)!.seeDetails, icon: Icons.file_open_rounded, - action: () => widget.onEdit!(widget.client) + action: (_) => widget.onEdit!(widget.client) ), MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), successMessage: AppLocalizations.of(context)!.copiedClipboard, ) diff --git a/lib/screens/clients/client/blocked_services_section.dart b/lib/screens/clients/client/blocked_services_section.dart index 4a7fc11..f6effd4 100644 --- a/lib/screens/clients/client/blocked_services_section.dart +++ b/lib/screens/clients/client/blocked_services_section.dart @@ -22,7 +22,7 @@ class BlockedServicesSection extends StatelessWidget { return Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 24), child: Material( color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(28), @@ -31,8 +31,8 @@ class BlockedServicesSection extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 6 + horizontal: 20, + vertical: 5 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -56,7 +56,7 @@ class BlockedServicesSection extends StatelessWidget { ), ), ), - const SizedBox(height: 12), + const SizedBox(height: 10), Material( color: Colors.transparent, child: InkWell( @@ -69,7 +69,7 @@ class BlockedServicesSection extends StatelessWidget { : null, child: Padding( padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 32 + vertical: 8, horizontal: 24 ), child: Row( children: [ diff --git a/lib/screens/clients/client/blocking_schedule.dart b/lib/screens/clients/client/blocking_schedule.dart deleted file mode 100644 index f17bd4d..0000000 --- a/lib/screens/clients/client/blocking_schedule.dart +++ /dev/null @@ -1,209 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/clients/client/blocking_schedule_modal.dart'; -import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; -import 'package:adguard_home_manager/widgets/section_label.dart'; - -import 'package:adguard_home_manager/models/clients.dart'; - -class EditBlockingSchedule { - final String timezone; - final List weekday; - final int start; - final int end; - - const EditBlockingSchedule({ - required this.timezone, - required this.weekday, - required this.start, - required this.end, - }); -} - -class BlockingSchedule extends StatelessWidget { - final BlockedServicesSchedule blockedServicesSchedule; - final void Function(BlockedServicesSchedule) setBlockedServicesSchedule; - - const BlockingSchedule({ - super.key, - required this.blockedServicesSchedule, - required this.setBlockedServicesSchedule, - }); - - @override - Widget build(BuildContext context) { - void updateSchedule(EditBlockingSchedule v) { - final scheduleJson = blockedServicesSchedule.toJson(); - for (var weekday in v.weekday) { - scheduleJson[weekday] = { - "start": v.start, - "end": v.end - }; - } - scheduleJson["time_zone"] = v.timezone; - setBlockedServicesSchedule(BlockedServicesSchedule.fromJson(scheduleJson)); - } - - void openAddScheduleModal() { - showDialog( - context: context, - builder: (context) => BlockingScheduleModal( - onConfirm: updateSchedule, - ), - ); - } - - void openEditScheduleModal(String weekday) { - showDialog( - context: context, - builder: (context) => BlockingScheduleModal( - schedule: EditBlockingSchedule( - timezone: blockedServicesSchedule.timeZone!, - weekday: [weekday], - start: blockedServicesSchedule.toJson()[weekday]['start'], - end: blockedServicesSchedule.toJson()[weekday]['end'], - ), - onConfirm: updateSchedule, - ), - ); - } - - void onDeleteSchedule(String weekday) { - final scheduleJson = blockedServicesSchedule.toJson(); - scheduleJson[weekday] = null; - setBlockedServicesSchedule(BlockedServicesSchedule.fromJson(scheduleJson)); - } - - String formatTime(int time) { - final formatted = Duration(milliseconds: time); - final hours = formatted.inHours; - final minutes = formatted.inMinutes - hours*60; - return "${hours.toString().padLeft(2 , '0')}:${minutes.toString().padLeft(2, '0')}"; - } - - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SectionLabel(label: AppLocalizations.of(context)!.pauseServiceBlocking), - Padding( - padding: const EdgeInsets.only(right: 12), - child: IconButton( - onPressed: openAddScheduleModal, - icon: const Icon(Icons.add) - ), - ) - ], - ), - const SizedBox(height: 2), - if ( - blockedServicesSchedule.mon == null && - blockedServicesSchedule.tue == null && - blockedServicesSchedule.wed == null && - blockedServicesSchedule.thu == null && - blockedServicesSchedule.fri == null && - blockedServicesSchedule.sat == null && - blockedServicesSchedule.sun == null - ) Padding( - padding: const EdgeInsets.all(16), - child: Text( - AppLocalizations.of(context)!.noBlockingScheduleThisDevice, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ), - if (blockedServicesSchedule.mon != null) _ScheduleTile( - weekday: AppLocalizations.of(context)!.monday, - schedule: "${formatTime(blockedServicesSchedule.mon!.start!)} - ${formatTime(blockedServicesSchedule.mon!.end!)}", - onEdit: () => openEditScheduleModal("mon"), - onDelete: () => onDeleteSchedule("mon") - ), - if (blockedServicesSchedule.tue != null) _ScheduleTile( - weekday: AppLocalizations.of(context)!.tuesday, - schedule: "${formatTime(blockedServicesSchedule.tue!.start!)} - ${formatTime(blockedServicesSchedule.tue!.end!)}", - onEdit: () => openEditScheduleModal("tue"), - onDelete: () => onDeleteSchedule("tue") - ), - if (blockedServicesSchedule.wed != null) _ScheduleTile( - weekday: AppLocalizations.of(context)!.wednesday, - schedule: "${formatTime(blockedServicesSchedule.wed!.start!)} - ${formatTime(blockedServicesSchedule.wed!.end!)}", - onEdit: () => openEditScheduleModal("wed"), - onDelete: () => onDeleteSchedule("wed") - ), - if (blockedServicesSchedule.thu != null) _ScheduleTile( - weekday: AppLocalizations.of(context)!.thursday, - schedule: "${formatTime(blockedServicesSchedule.thu!.start!)} - ${formatTime(blockedServicesSchedule.thu!.end!)}", - onEdit: () => openEditScheduleModal("thu"), - onDelete: () => onDeleteSchedule("thu") - ), - if (blockedServicesSchedule.fri != null) _ScheduleTile( - weekday: AppLocalizations.of(context)!.friday, - schedule: "${formatTime(blockedServicesSchedule.fri!.start!)} - ${formatTime(blockedServicesSchedule.fri!.end!)}", - onEdit: () => openEditScheduleModal("fri"), - onDelete: () => onDeleteSchedule("fri") - ), - if (blockedServicesSchedule.sat != null) _ScheduleTile( - weekday: AppLocalizations.of(context)!.saturday, - schedule: "${formatTime(blockedServicesSchedule.sat!.start!)} - ${formatTime(blockedServicesSchedule.sat!.end!)}", - onEdit: () => openEditScheduleModal("sat"), - onDelete: () => onDeleteSchedule("sat") - ), - if (blockedServicesSchedule.sun != null) _ScheduleTile( - weekday: AppLocalizations.of(context)!.sunday, - schedule: "${formatTime(blockedServicesSchedule.sun!.start!)} - ${formatTime(blockedServicesSchedule.sun!.end!)}", - onEdit: () => openEditScheduleModal("sun"), - onDelete: () => onDeleteSchedule("sun") - ), - ], - ); - } -} - -class _ScheduleTile extends StatelessWidget { - final String weekday; - final String schedule; - final void Function() onEdit; - final void Function() onDelete; - - const _ScheduleTile({ - required this.weekday, - required this.schedule, - required this.onEdit, - required this.onDelete, - }); - - @override - Widget build(BuildContext context) { - return CustomListTile( - title: weekday, - subtitle: schedule, - trailing: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - onPressed: onEdit, - icon: const Icon(Icons.edit_rounded), - tooltip: AppLocalizations.of(context)!.edit, - ), - const SizedBox(width: 4), - IconButton( - onPressed: onDelete, - icon: const Icon(Icons.delete_rounded), - tooltip: AppLocalizations.of(context)!.delete, - ), - ], - ), - padding: const EdgeInsets.only( - left: 16, - top: 6, - right: 12, - bottom: 6 - ) - ); - } -} \ No newline at end of file diff --git a/lib/screens/clients/client/blocking_schedule_modal.dart b/lib/screens/clients/client/blocking_schedule_modal.dart deleted file mode 100644 index 1521ead..0000000 --- a/lib/screens/clients/client/blocking_schedule_modal.dart +++ /dev/null @@ -1,325 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:timezone/timezone.dart' as tz; -import 'package:timezone/data/latest.dart' as tz; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/clients/client/blocking_schedule.dart'; - -class BlockingScheduleModal extends StatefulWidget { - final EditBlockingSchedule? schedule; - final void Function(EditBlockingSchedule) onConfirm; - - const BlockingScheduleModal({ - super.key, - this.schedule, - required this.onConfirm, - }); - - @override - State createState() => _BlockingScheduleModalState(); -} - -class _BlockingScheduleModalState extends State { - final _weekdaysScrollController = ScrollController(); - - String? _timezone; - List _weekdays = []; - TimeOfDay? _from; - TimeOfDay? _to; - - bool _compareTimes(TimeOfDay startTime, TimeOfDay endTime) { - bool result = false; - int startTimeInt = (startTime.hour * 60 + startTime.minute) * 60; - int endTimeInt = (endTime.hour * 60 + endTime.minute) * 60; - - if (endTimeInt > startTimeInt) { - result = true; - } else { - result = false; - } - return result; - } - - bool _validate() { - return _timezone != null && - _weekdays.isNotEmpty && - _from != null && - _to != null && - _compareTimes(_from!, _to!); - } - - int _timeOfDayToInt(TimeOfDay timeOfDay) { - return Duration( - days: 0, - hours: timeOfDay.hour, - minutes: timeOfDay.minute, - seconds: 0 - ).inMilliseconds; - } - - TimeOfDay _intToTimeOfDay(int value) { - final duration = Duration(milliseconds: value); - final minutes = duration.inMinutes - duration.inHours*60; - return TimeOfDay(hour: duration.inHours, minute: minutes); - } - - @override - void initState() { - tz.initializeTimeZones(); - if (widget.schedule != null) { - _timezone = widget.schedule!.timezone; - _weekdays = widget.schedule!.weekday; - _from = _intToTimeOfDay(widget.schedule!.start); - _to = _intToTimeOfDay(widget.schedule!.end); - } - - super.initState(); - } - - @override - Widget build(BuildContext context) { - void onSelectWeekday(bool newStatus, String day) { - if (newStatus == true && !_weekdays.contains(day)) { - setState(() => _weekdays.add(day)); - } - else if (newStatus == false) { - setState(() => _weekdays = _weekdays.where((e) => e != day).toList()); - } - } - - void onConfirm() { - widget.onConfirm( - EditBlockingSchedule( - timezone: _timezone!, - weekday: _weekdays, - start: _timeOfDayToInt(_from!), - end: _timeOfDayToInt(_to!) - ) - ); - Navigator.pop(context); - } - - final valid = _validate(); - final validTimes = _from != null && _to != null - ? _compareTimes(_from!, _to!) - : null; - - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 500), - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.schedule_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), - Text( - widget.schedule != null - ? AppLocalizations.of(context)!.editSchedule - : AppLocalizations.of(context)!.newSchedule, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 24, - ), - ), - const SizedBox(height: 30), - LayoutBuilder( - builder: (context, constraints) => DropdownButtonFormField( - items: tz.timeZoneDatabase.locations.keys.map((item) => DropdownMenuItem( - value: item, - child: SizedBox( - width: constraints.maxWidth-48, - child: Text( - item, - overflow: TextOverflow.ellipsis, - ), - ), - )).toList(), - value: _timezone, - onChanged: (v) => setState(() => _timezone = v), - decoration: InputDecoration( - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - label: Text(AppLocalizations.of(context)!.timezone) - ), - borderRadius: BorderRadius.circular(20), - ), - ), - const SizedBox(height: 16), - SizedBox( - height: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 66 : 50, - child: Scrollbar( - controller: _weekdaysScrollController, - thumbVisibility: Platform.isMacOS || Platform.isLinux || Platform.isWindows, - interactive: Platform.isMacOS || Platform.isLinux || Platform.isWindows, - thickness: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 8 : 0, - child: Padding( - padding: EdgeInsets.only( - bottom: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 16 : 0 - ), - child: ListView( - controller: _weekdaysScrollController, - scrollDirection: Axis.horizontal, - children: [ - FilterChip( - label: Text(AppLocalizations.of(context)!.monday), - selected: _weekdays.contains("mon"), - onSelected: (value) => onSelectWeekday(value, "mon") - ), - const SizedBox(width: 8), - FilterChip( - label: Text(AppLocalizations.of(context)!.tuesday), - selected: _weekdays.contains("tue"), - onSelected: (value) => onSelectWeekday(value, "tue") - ), - const SizedBox(width: 8), - FilterChip( - label: Text(AppLocalizations.of(context)!.wednesday), - selected: _weekdays.contains("wed"), - onSelected: (value) => onSelectWeekday(value, "wed") - ), - const SizedBox(width: 8), - FilterChip( - label: Text(AppLocalizations.of(context)!.thursday), - selected: _weekdays.contains("thu"), - onSelected: (value) => onSelectWeekday(value, "thu") - ), - const SizedBox(width: 8), - FilterChip( - label: Text(AppLocalizations.of(context)!.friday), - selected: _weekdays.contains("fri"), - onSelected: (value) => onSelectWeekday(value, "fri") - ), - const SizedBox(width: 8), - FilterChip( - label: Text(AppLocalizations.of(context)!.saturday), - selected: _weekdays.contains("sat"), - onSelected: (value) => onSelectWeekday(value, "sat") - ), - const SizedBox(width: 8), - FilterChip( - label: Text(AppLocalizations.of(context)!.sunday), - selected: _weekdays.contains("sun"), - onSelected: (value) => onSelectWeekday(value, "sun") - ), - ], - ), - ), - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - onPressed: () async { - final selected = await showTimePicker( - context: context, - initialTime: _from ?? const TimeOfDay(hour: 0, minute: 0), - helpText: AppLocalizations.of(context)!.selectStartTime, - confirmText: AppLocalizations.of(context)!.confirm, - ); - setState(() => _from = selected); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Column( - children: [ - Text(AppLocalizations.of(context)!.from), - const SizedBox(height: 2), - Text(_from != null ? "${_from!.hour.toString().padLeft(2, '0')}:${_from!.minute.toString().padLeft(2, '0')}" : "--:--") - ], - ), - ) - ), - ElevatedButton( - onPressed: () async { - final selected = await showTimePicker( - context: context, - initialTime: _to ?? const TimeOfDay(hour: 23, minute: 59), - helpText: AppLocalizations.of(context)!.selectEndTime, - confirmText: AppLocalizations.of(context)!.confirm - ); - setState(() => _to = selected); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Column( - children: [ - Text(AppLocalizations.of(context)!.to), - const SizedBox(height: 2), - Text(_to != null ? "${_to!.hour.toString().padLeft(2, '0')}:${_to!.minute.toString().padLeft(2, '0')}" : "--:--") - ], - ), - ) - ), - ], - ), - if (validTimes == false) Padding( - padding: const EdgeInsets.only(top: 16), - child: Card( - color: const Color.fromARGB(255, 255, 182, 175), - elevation: 0, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.error_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - const SizedBox(width: 16), - Expanded( - child: Text( - AppLocalizations.of(context)!.startTimeBeforeEndTime, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ), - ], - ), - ), - ) - ) - ], - ), - ), - ), - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close) - ), - const SizedBox(width: 8), - TextButton( - onPressed: valid ? () => onConfirm() : null, - child: Text(AppLocalizations.of(context)!.confirm) - ), - ], - ) - ], - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/clients/client/client_form.dart b/lib/screens/clients/client/client_form.dart index 2d22dd7..2151288 100644 --- a/lib/screens/clients/client/client_form.dart +++ b/lib/screens/clients/client/client_form.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -8,9 +10,7 @@ import 'package:adguard_home_manager/screens/clients/client/identifiers_section. import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart'; import 'package:adguard_home_manager/screens/clients/client/tags_section.dart'; import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart'; -import 'package:adguard_home_manager/screens/clients/client/blocking_schedule.dart'; -import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; @@ -21,6 +21,7 @@ class ClientForm extends StatelessWidget { final bool isFullScreen; final Client? client; final TextEditingController nameController; + final void Function(bool) updateValidValues; final List identifiersControllers; final List selectedTags; final bool useGlobalSettingsFiltering; @@ -44,23 +45,13 @@ class ClientForm extends StatelessWidget { final void Function(bool) updateEnableSafeSearch; final void Function(SafeSearch) updateSafeSearch; final void Function(bool) updateUseGlobalSettingsServices; - final bool ignoreClientQueryLog; - final void Function(bool) updateIgnoreClientQueryLog; - final bool ignoreClientStatistics; - final void Function(bool) updateIgnoreClientStatistics; - final bool enableDnsCache; - final void Function(bool) updateEnableDnsCache; - final TextEditingController dnsCacheField; - final String? dnsCacheError; - final void Function(String?) updateDnsCacheError; - final BlockedServicesSchedule blockedServicesSchedule; - final void Function(BlockedServicesSchedule) setBlockedServicesSchedule; const ClientForm({ super.key, required this.isFullScreen, required this.client, required this.nameController, + required this.updateValidValues, required this.identifiersControllers, required this.selectedTags, required this.useGlobalSettingsFiltering, @@ -84,30 +75,26 @@ class ClientForm extends StatelessWidget { required this.updateEnableSafeSearch, required this.updateSafeSearch, required this.updateUseGlobalSettingsServices, - required this.ignoreClientQueryLog, - required this.ignoreClientStatistics, - required this.updateIgnoreClientQueryLog, - required this.updateIgnoreClientStatistics, - required this.enableDnsCache, - required this.updateEnableDnsCache, - required this.dnsCacheField, - required this.dnsCacheError, - required this.updateDnsCacheError, - required this.blockedServicesSchedule, - required this.setBlockedServicesSchedule, }); @override Widget build(BuildContext context) { - return Column( + return ListView( + padding: const EdgeInsets.only(top: 0), children: [ - const SizedBox(height: 8), + if (isFullScreen == true) const SizedBox(height: 24), + if (isFullScreen == false) const SizedBox(height: 6), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 24), child: TextFormField( enabled: client != null ? false : true, controller: nameController, - onChanged: (_) => {}, + onChanged: (_) => updateValidValues( + checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ) + ), decoration: InputDecoration( prefixIcon: const Icon(Icons.badge_rounded), border: const OutlineInputBorder( @@ -121,7 +108,7 @@ class ClientForm extends StatelessWidget { ), SectionLabel( label: AppLocalizations.of(context)!.tags, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + padding: const EdgeInsets.all(24), ), TagsSection( selectedTags: selectedTags, @@ -131,17 +118,28 @@ class ClientForm extends StatelessWidget { identifiersControllers: identifiersControllers, onUpdateIdentifiersControllers: (c) { updateIdentifiersControllers(c); + updateValidValues( + checkValidValues( + nameController: nameController, + identifiersControllers: identifiersControllers + ) + ); }, - onCheckValidValues: () => {} + onCheckValidValues: () => updateValidValues( + checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ) + ), ), SectionLabel( label: AppLocalizations.of(context)!.settings, padding: const EdgeInsets.only( - left: 16, right: 16, top: 12, bottom: 24 + left: 24, right: 24, top: 12, bottom: 24 ) ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 24), child: Material( color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(28), @@ -150,8 +148,8 @@ class ClientForm extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 6 + horizontal: 20, + vertical: 5 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -175,7 +173,7 @@ class ClientForm extends StatelessWidget { ), ), ), - const SizedBox(height: 8), + const SizedBox(height: 10), SettingsTile( label: AppLocalizations.of(context)!.enableFiltering, value: enableFiltering, @@ -197,7 +195,7 @@ class ClientForm extends StatelessWidget { CustomListTile( title: AppLocalizations.of(context)!.safeSearch, padding: const EdgeInsets.symmetric( - horizontal: 34, + horizontal: 42, vertical: 16 ), trailing: Padding( @@ -219,31 +217,9 @@ class ClientForm extends StatelessWidget { ) : null, ), - SectionLabel( - label: AppLocalizations.of(context)!.queryLogsAndStatistics, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - CustomSwitchListTile( - title: AppLocalizations.of(context)!.ignoreClientQueryLog, - value: ignoreClientQueryLog, - onChanged: updateIgnoreClientQueryLog, - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 6 - ), - ), - CustomSwitchListTile( - title: AppLocalizations.of(context)!.ignoreClientStatistics, - value: ignoreClientStatistics, - onChanged: updateIgnoreClientStatistics, - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 6 - ), - ), SectionLabel( label: AppLocalizations.of(context)!.blockedServices, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + padding: const EdgeInsets.all(24), ), BlockedServicesSection( useGlobalSettingsServices: useGlobalSettingsServices, @@ -253,45 +229,15 @@ class ClientForm extends StatelessWidget { ), UpstreamServersSection( upstreamServers: upstreamServers, - onCheckValidValues: () => {}, + onCheckValidValues: () => updateValidValues( + checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ) + ), onUpdateUpstreamServers: updateUpstreamServers ), - SectionLabel( - label: AppLocalizations.of(context)!.upstreamDnsCacheConfiguration, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - CustomSwitchListTile( - title: AppLocalizations.of(context)!.enableDnsCachingClient, - value: enableDnsCache, - onChanged: updateEnableDnsCache, - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 6 - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - child: TextFormField( - controller: dnsCacheField, - onChanged: (v) => updateDnsCacheError(!validateNumber(v) ? AppLocalizations.of(context)!.invalidValue : null), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.storage_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.dnsCacheSize, - errorText: dnsCacheError - ), - keyboardType: TextInputType.number, - ), - ), - BlockingSchedule( - blockedServicesSchedule: blockedServicesSchedule, - setBlockedServicesSchedule: setBlockedServicesSchedule, - ), - const SizedBox(height: 16), + SizedBox(height: Platform.isIOS ? 48 : 24) ], ); } diff --git a/lib/screens/clients/client/client_placeholder.dart b/lib/screens/clients/client/client_placeholder.dart index 0e8f018..cf4f485 100644 --- a/lib/screens/clients/client/client_placeholder.dart +++ b/lib/screens/clients/client/client_placeholder.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; class ClientPlaceholder extends StatelessWidget { - const ClientPlaceholder({super.key}); + const ClientPlaceholder({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return const Center( + return Center( child: Text("Select a client"), ); } diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 573e6cf..2ab5c75 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -10,16 +10,6 @@ import 'package:adguard_home_manager/models/safe_search.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/models/clients.dart'; -class ClientInitialData { - final String name; - final String ip; - - const ClientInitialData({ - required this.name, - required this.ip, - }); -} - class ControllerListItem { final String id; final TextEditingController controller; @@ -35,15 +25,13 @@ class ClientScreen extends StatefulWidget { final void Function(Client) onConfirm; final void Function(Client)? onDelete; final bool fullScreen; - final ClientInitialData? initialData; const ClientScreen({ super.key, this.client, required this.onConfirm, this.onDelete, - required this.fullScreen, - this.initialData, + required this.fullScreen }); @override @@ -51,8 +39,6 @@ class ClientScreen extends StatefulWidget { } class _ClientScreenState extends State { - final _scrollController = ScrollController(); - final Uuid uuid = const Uuid(); bool validValues = false; @@ -87,20 +73,6 @@ class _ClientScreenState extends State { List upstreamServers = []; - bool _ignoreClientQueryLog = false; - bool _ignoreClientStatistics = false; - - bool _enableDnsCache = false; - final _dnsCacheField = TextEditingController(); - String? _dnsCacheError; - - BlockedServicesSchedule _blockedServicesSchedule = BlockedServicesSchedule(); - - // VALIDATIONS - bool _nameValid = true; - bool _identifiersValid = true; - bool _dnsCacheValid = true; - void enableDisableGlobalSettingsFiltering() { if (useGlobalSettingsFiltering == true) { setState(() { @@ -148,22 +120,6 @@ class _ClientScreenState extends State { id: uuid.v4(), controller: TextEditingController(text: e) )).toList(); - _ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false; - _ignoreClientStatistics = widget.client!.ignoreStatistics ?? false; - _enableDnsCache = widget.client!.upstreamsCacheEnabled ?? false; - _dnsCacheField.text = widget.client!.upstreamsCacheSize != null - ? widget.client!.upstreamsCacheSize.toString() - : ""; - if (widget.client!.blockedServicesSchedule != null) { - _blockedServicesSchedule = widget.client!.blockedServicesSchedule!; - } - } - if (widget.initialData != null) { - nameController.text = widget.initialData!.name; - identifiersControllers[0] = ControllerListItem( - id: uuid.v4(), - controller: TextEditingController(text: widget.initialData!.ip) - ); } super.initState(); } @@ -184,40 +140,20 @@ class _ClientScreenState extends State { useGlobalBlockedServices: useGlobalSettingsServices, blockedServices: blockedServices, upstreams: List.from(upstreamServers.map((e) => e.controller.text)), - tags: selectedTags, - ignoreQuerylog: _ignoreClientQueryLog, - ignoreStatistics: _ignoreClientStatistics, - upstreamsCacheEnabled: _enableDnsCache, - upstreamsCacheSize: _dnsCacheField.text != "" - ? int.parse(_dnsCacheField.text) - : null, - blockedServicesSchedule: _blockedServicesSchedule + tags: selectedTags ); widget.onConfirm(client); } - void validateValues() { - _nameValid = nameController.text != ''; - _identifiersValid = identifiersControllers.isNotEmpty && identifiersControllers[0].controller.text != ''; - _dnsCacheValid = (_dnsCacheField.text == "" || _dnsCacheField.text != "" && RegExp(r'^\d+$').hasMatch(_dnsCacheField.text)); - if (_nameValid && _identifiersValid && _dnsCacheValid) { - createClient(); - Navigator.pop(context); - } - else { - _scrollController.animateTo( - 0, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 500) - ); - setState(() => {}); - } - } - List actions() { return [ IconButton( - onPressed: validateValues, + onPressed: validValues == true + ? () { + createClient(); + Navigator.pop(context); + } + : null, icon: const Icon(Icons.save_rounded), tooltip: AppLocalizations.of(context)!.save, ), @@ -235,90 +171,49 @@ class _ClientScreenState extends State { if (widget.fullScreen == true) { - return Material( - child: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - leading: IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close) - ), - title: Text( - widget.client != null - ? AppLocalizations.of(context)!.client - : AppLocalizations.of(context)!.addClient - ), - actions: actions(), - ) - ) - ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverList.list( - children: [ - if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors( - nameValid: _nameValid, - identifiersValid: _identifiersValid, - dnsCacheValid: _dnsCacheValid - ), - ClientForm( - isFullScreen: true, - client: widget.client, - nameController: nameController, - identifiersControllers: identifiersControllers, - selectedTags: selectedTags, - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - enableFiltering: enableFiltering, - enableParentalControl: enableParentalControl, - enableSafeBrowsing: enableSafeBrowsing, - enableSafeSearch: enableSafeSearch, - safeSearch: safeSearch, - blockedServices: blockedServices, - updateBlockedServices: (v) => setState(() => blockedServices = v), - upstreamServers: upstreamServers, - updateUpstreamServers: (v) => setState(() => upstreamServers = v), - defaultSafeSearch: defaultSafeSearch, - useGlobalSettingsServices: useGlobalSettingsServices, - updateSelectedTags: (v) => setState(() => selectedTags = v), - updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), - enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, - updateEnableFiltering: (v) => setState(() => enableFiltering = v), - updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), - updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), - updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), - updateSafeSearch: (v) => setState(() => safeSearch = v), - updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), - ignoreClientQueryLog: _ignoreClientQueryLog, - ignoreClientStatistics: _ignoreClientStatistics, - updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), - updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), - enableDnsCache: _enableDnsCache, - updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v), - dnsCacheField: _dnsCacheField, - dnsCacheError: _dnsCacheError, - updateDnsCacheError: (v) => setState(() => _dnsCacheError = v), - blockedServicesSchedule: _blockedServicesSchedule, - setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v), - ), - ], - ) - ], - ), - ) - ) + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close) + ), + title: Text( + widget.client != null + ? AppLocalizations.of(context)!.client + : AppLocalizations.of(context)!.addClient + ), + actions: actions(), + ), + body: ClientForm( + isFullScreen: true, + client: widget.client, + nameController: nameController, + updateValidValues: (v) => setState(() => validValues = v), + identifiersControllers: identifiersControllers, + selectedTags: selectedTags, + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + enableFiltering: enableFiltering, + enableParentalControl: enableParentalControl, + enableSafeBrowsing: enableSafeBrowsing, + enableSafeSearch: enableSafeSearch, + safeSearch: safeSearch, + blockedServices: blockedServices, + updateBlockedServices: (v) => setState(() => blockedServices = v), + upstreamServers: upstreamServers, + updateUpstreamServers: (v) => setState(() => upstreamServers = v), + defaultSafeSearch: defaultSafeSearch, + useGlobalSettingsServices: useGlobalSettingsServices, + updateSelectedTags: (v) => setState(() => selectedTags = v), + updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), + enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, + updateEnableFiltering: (v) => setState(() => enableFiltering = v), + updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), + updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), + updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), + updateSafeSearch: (v) => setState(() => safeSearch = v), + updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), + ), ), ); } @@ -356,54 +251,34 @@ class _ClientScreenState extends State { ), ), Flexible( - child: ListView( - controller: _scrollController, - children: [ - if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors( - nameValid: _nameValid, - identifiersValid: _identifiersValid, - dnsCacheValid: _dnsCacheValid - ), - ClientForm( - isFullScreen: false, - client: widget.client, - nameController: nameController, - identifiersControllers: identifiersControllers, - selectedTags: selectedTags, - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - enableFiltering: enableFiltering, - enableParentalControl: enableParentalControl, - enableSafeBrowsing: enableSafeBrowsing, - enableSafeSearch: enableSafeSearch, - safeSearch: safeSearch, - blockedServices: blockedServices, - updateBlockedServices: (v) => setState(() => blockedServices = v), - upstreamServers: upstreamServers, - updateUpstreamServers: (v) => setState(() => upstreamServers = v), - defaultSafeSearch: defaultSafeSearch, - useGlobalSettingsServices: useGlobalSettingsServices, - updateSelectedTags: (v) => setState(() => selectedTags = v), - updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), - enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, - updateEnableFiltering: (v) => setState(() => enableFiltering = v), - updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), - updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), - updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), - updateSafeSearch: (v) => setState(() => safeSearch = v), - updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), - ignoreClientQueryLog: _ignoreClientQueryLog, - ignoreClientStatistics: _ignoreClientStatistics, - updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), - updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), - enableDnsCache: _enableDnsCache, - updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v), - dnsCacheField: _dnsCacheField, - dnsCacheError: _dnsCacheError, - updateDnsCacheError: (v) => setState(() => _dnsCacheError = v), - blockedServicesSchedule: _blockedServicesSchedule, - setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v), - ), - ], + child: ClientForm( + isFullScreen: false, + client: widget.client, + nameController: nameController, + updateValidValues: (v) => setState(() => validValues = v), + identifiersControllers: identifiersControllers, + selectedTags: selectedTags, + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + enableFiltering: enableFiltering, + enableParentalControl: enableParentalControl, + enableSafeBrowsing: enableSafeBrowsing, + enableSafeSearch: enableSafeSearch, + safeSearch: safeSearch, + blockedServices: blockedServices, + updateBlockedServices: (v) => setState(() => blockedServices = v), + upstreamServers: upstreamServers, + updateUpstreamServers: (v) => setState(() => upstreamServers = v), + defaultSafeSearch: defaultSafeSearch, + useGlobalSettingsServices: useGlobalSettingsServices, + updateSelectedTags: (v) => setState(() => selectedTags = v), + updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), + enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, + updateEnableFiltering: (v) => setState(() => enableFiltering = v), + updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), + updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), + updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), + updateSafeSearch: (v) => setState(() => safeSearch = v), + updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), ), ) ], @@ -414,56 +289,3 @@ class _ClientScreenState extends State { } } -class _Errors extends StatelessWidget { - final bool nameValid; - final bool identifiersValid; - final bool dnsCacheValid; - - const _Errors({ - required this.nameValid, - required this.identifiersValid, - required this.dnsCacheValid, - }); - - @override - Widget build(BuildContext context) { - return Card( - elevation: 0, - color: Colors.red.withOpacity(0.2), - margin: const EdgeInsets.all(16), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.errors, - style: const TextStyle( - fontSize: 18 - ), - ), - const SizedBox(height: 8), - if (!nameValid) Text( - "● ${AppLocalizations.of(context)!.nameInvalid}", - style: const TextStyle( - fontSize: 14 - ), - ), - if (!identifiersValid) Text( - "● ${AppLocalizations.of(context)!.oneIdentifierRequired}", - style: const TextStyle( - fontSize: 14 - ), - ), - if (!dnsCacheValid) Text( - "● ${AppLocalizations.of(context)!.dnsCacheNumber}", - style: const TextStyle( - fontSize: 14 - ), - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart index 2c176c4..1f292c6 100644 --- a/lib/screens/clients/client/client_screen_functions.dart +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -74,13 +74,28 @@ void openSafeSearchModal({ ); } +bool checkValidValues({ + required TextEditingController nameController, + required List identifiersControllers +}) { + if ( + nameController.text != '' && + identifiersControllers.isNotEmpty && + identifiersControllers[0].controller.text != '' + ) { + return true; + } + else { + return false; + } +} + void openClientFormModal({ required BuildContext context, required double width, Client? client, required void Function(Client) onConfirm, void Function(Client)? onDelete, - ClientInitialData? initialData, }) { showGeneralDialog( context: context, @@ -106,13 +121,6 @@ void openClientFormModal({ client: client, onConfirm: onConfirm, onDelete: onDelete, - initialData: initialData, ), ); -} - -bool validateNumber(String value) { - if (value == "") return true; - final regexp = RegExp(r'^\d+$'); - return regexp.hasMatch(value); } \ No newline at end of file diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart index c5f9d4c..91a56eb 100644 --- a/lib/screens/clients/client/identifiers_section.dart +++ b/lib/screens/clients/client/identifiers_section.dart @@ -11,11 +11,11 @@ class IdentifiersSection extends StatefulWidget { final void Function() onCheckValidValues; const IdentifiersSection({ - super.key, + Key? key, required this.identifiersControllers, required this.onUpdateIdentifiersControllers, required this.onCheckValidValues - }); + }) : super(key: key); @override State createState() => _IdentifiersSectionState(); @@ -34,11 +34,11 @@ class _IdentifiersSectionState extends State { SectionLabel( label: AppLocalizations.of(context)!.identifiers, padding: const EdgeInsets.only( - left: 16, right: 16, top: 24, bottom: 12 + left: 24, right: 24, top: 24, bottom: 12 ) ), Padding( - padding: const EdgeInsets.only(right: 10), + padding: const EdgeInsets.only(right: 20), child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers([ ...widget.identifiersControllers, @@ -54,7 +54,7 @@ class _IdentifiersSectionState extends State { ), if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( padding: const EdgeInsets.only( - top: 12, bottom: 12, left: 16, right: 10 + top: 12, bottom: 12, left: 24, right: 20 ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -75,9 +75,9 @@ class _IdentifiersSectionState extends State { ), ), ), - const SizedBox(width: 12), + const SizedBox(width: 16), Padding( - padding: const EdgeInsets.only(bottom: 24), + padding: const EdgeInsets.only(bottom: 25), child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers( widget.identifiersControllers.where((e) => e.id != controller.id).toList() @@ -87,7 +87,7 @@ class _IdentifiersSectionState extends State { ) ], ), - )), + )).toList(), if (widget.identifiersControllers.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Text( diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index f12f8e4..bd4d2af 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -131,88 +131,55 @@ class _LogsListClientState extends State { setState(() => previousIp = widget.ip); } - return Material( - child: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - title: SafeArea( + return Scaffold( + appBar: AppBar( + title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), + centerTitle: true, + surfaceTintColor: isDesktop(MediaQuery.of(context).size.width) + ? Colors.transparent + : null, + actions: [ + if (!(Platform.isAndroid || Platform.isIOS)) ...[ + IconButton( + onPressed: fetchLogs, + icon: const Icon(Icons.refresh_rounded), + tooltip: AppLocalizations.of(context)!.refresh, + ), + const SizedBox(width: 8) + ] + ], + ), + body: Builder( + builder: (context) { + switch (loadStatus) { + case 0: + return SizedBox( + width: double.maxFinite, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), Text( - AppLocalizations.of(context)!.client, - style: const TextStyle( - fontSize: 24 - ), - ), - const SizedBox(height: 4), - Text( - widget.name != null && widget.name != '' ? widget.name! : widget.ip, + AppLocalizations.of(context)!.loadingLogs, + textAlign: TextAlign.center, style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.secondary + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ) ], ), - ), - surfaceTintColor: isDesktop(MediaQuery.of(context).size.width) - ? Colors.transparent - : null, - actions: [ - if (!(Platform.isAndroid || Platform.isIOS)) ...[ - IconButton( - onPressed: fetchLogs, - icon: const Icon(Icons.refresh_rounded), - tooltip: AppLocalizations.of(context)!.refresh, - ), - const SizedBox(width: 8) - ] - ], - ) - ) - ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => RefreshIndicator( - onRefresh: fetchLogs, - displacement: 95, - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (loadStatus == 0) SliverFillRemaining( - child: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingLogs, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ) - ), - if (loadStatus == 1 && logsData!.data.isNotEmpty) SliverList.builder( + ); + + case 1: + if (logsData!.data.isNotEmpty) { + return RefreshIndicator( + onRefresh: fetchLogs, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 0), itemCount: isLoadingMore == true ? logsData!.data.length+1 : logsData!.data.length, @@ -237,8 +204,7 @@ class _LogsListClientState extends State { context: context, builder: (context) => LogDetailsScreen( log: log, - dialog: true, - twoColumns: widget.splitView, + dialog: true ) ) } @@ -247,8 +213,7 @@ class _LogsListClientState extends State { MaterialPageRoute( builder: (context) => LogDetailsScreen( log: log, - dialog: false, - twoColumns: widget.splitView, + dialog: false ) ) ) @@ -259,49 +224,51 @@ class _LogsListClientState extends State { } } ), - if (loadStatus == 1 && logsData!.data.isEmpty) SliverFillRemaining( - child: Center( - child: Text( - AppLocalizations.of(context)!.noLogsDisplay, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), + ); + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noLogsDisplay, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), - if (loadStatus == 2) SliverFillRemaining( - child: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.logsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), + ); + } + + case 2: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, ), - ) - ], - ), - ), - ) - ) - ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.logsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/clients/client/remove_client_modal.dart b/lib/screens/clients/client/remove_client_modal.dart index b531540..4427d22 100644 --- a/lib/screens/clients/client/remove_client_modal.dart +++ b/lib/screens/clients/client/remove_client_modal.dart @@ -5,9 +5,9 @@ class RemoveClientModal extends StatelessWidget { final void Function() onConfirm; const RemoveClientModal({ - super.key, + Key? key, required this.onConfirm - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/clients/client/safe_search_modal.dart b/lib/screens/clients/client/safe_search_modal.dart index 1d539be..1be694c 100644 --- a/lib/screens/clients/client/safe_search_modal.dart +++ b/lib/screens/clients/client/safe_search_modal.dart @@ -11,11 +11,11 @@ class SafeSearchModal extends StatefulWidget { final void Function(SafeSearch) onConfirm; const SafeSearchModal({ - super.key, + Key? key, required this.safeSearch, required this.disabled, required this.onConfirm - }); + }) : super(key: key); @override State createState() => _SafeSearchModalState(); diff --git a/lib/screens/clients/client/services_modal.dart b/lib/screens/clients/client/services_modal.dart index b8f69dc..1a98275 100644 --- a/lib/screens/clients/client/services_modal.dart +++ b/lib/screens/clients/client/services_modal.dart @@ -10,10 +10,10 @@ class ServicesModal extends StatefulWidget { final void Function(List) onConfirm; const ServicesModal({ - super.key, + Key? key, required this.blockedServices, required this.onConfirm, - }); + }) : super(key: key); @override State createState() => _ServicesModalStateWidget(); diff --git a/lib/screens/clients/client/settings_tile.dart b/lib/screens/clients/client/settings_tile.dart index 9b26b2c..c2d6ede 100644 --- a/lib/screens/clients/client/settings_tile.dart +++ b/lib/screens/clients/client/settings_tile.dart @@ -7,12 +7,12 @@ class SettingsTile extends StatelessWidget { final bool useGlobalSettingsFiltering; const SettingsTile({ - super.key, + Key? key, required this.label, required this.value, this.onChange, required this.useGlobalSettingsFiltering - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -23,7 +23,10 @@ class SettingsTile extends StatelessWidget { ? value != null ? () => onChange!(!value!) : null : null, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 42, + vertical: 5 + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/screens/clients/client/tags_section.dart b/lib/screens/clients/client/tags_section.dart index 8d63a0e..a6fa67c 100644 --- a/lib/screens/clients/client/tags_section.dart +++ b/lib/screens/clients/client/tags_section.dart @@ -7,10 +7,10 @@ class TagsSection extends StatelessWidget { final void Function(List) onTagsSelected; const TagsSection({ - super.key, + Key? key, required this.selectedTags, required this.onTagsSelected - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -24,7 +24,7 @@ class TagsSection extends StatelessWidget { ) , child: Padding( padding: const EdgeInsets.symmetric( - vertical: 0, horizontal: 16 + vertical: 0, horizontal: 24 ), child: Row( children: [ diff --git a/lib/screens/clients/client/upstream_servers_section.dart b/lib/screens/clients/client/upstream_servers_section.dart index 56fb7aa..97c8974 100644 --- a/lib/screens/clients/client/upstream_servers_section.dart +++ b/lib/screens/clients/client/upstream_servers_section.dart @@ -11,11 +11,11 @@ class UpstreamServersSection extends StatefulWidget { final void Function(List) onUpdateUpstreamServers; const UpstreamServersSection({ - super.key, + Key? key, required this.upstreamServers, required this.onCheckValidValues, required this.onUpdateUpstreamServers - }); + }) : super(key: key); @override State createState() => _UpstreamServersSectionState(); @@ -33,10 +33,10 @@ class _UpstreamServersSectionState extends State { children: [ SectionLabel( label: AppLocalizations.of(context)!.upstreamServers, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + padding: const EdgeInsets.all(24), ), Padding( - padding: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.only(right: 20), child: IconButton( onPressed: () => setState(() => widget.upstreamServers.add( ControllerListItem( @@ -50,7 +50,7 @@ class _UpstreamServersSectionState extends State { ], ), if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding( - padding: const EdgeInsets.only(left: 16, right: 12), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Padding( padding: const EdgeInsets.only(bottom: 20), child: Row( @@ -71,7 +71,7 @@ class _UpstreamServersSectionState extends State { ), ), ), - const SizedBox(width: 12), + const SizedBox(width: 16), IconButton( onPressed: () => widget.onUpdateUpstreamServers( widget.upstreamServers.where((e) => e.id != controller.id).toList() @@ -81,7 +81,7 @@ class _UpstreamServersSectionState extends State { ], ), ), - )), + )).toList(), if (widget.upstreamServers.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 2ca57f1..3d494ae 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/clients_lists.dart'; import 'package:adguard_home_manager/models/clients.dart'; -final clientsNavigatorKey = GlobalKey(); - class Clients extends StatefulWidget { const Clients({super.key}); @@ -23,24 +23,27 @@ class _ClientsState extends State with TickerProviderStateMixin { return Scaffold( body: LayoutBuilder( builder: (context, constraints) { - if (constraints.maxWidth > 900) { - return Row( - children: [ - const Expanded( - flex: 1, - child: ClientsLists( - splitView: true, - ) - ), - Expanded( - flex: 2, - child: Navigator( - key: clientsNavigatorKey, - onGenerateRoute: (settings) => MaterialPageRoute(builder: (ctx) => const SizedBox()), + if (constraints.maxWidth > 1000) { + return SplitView.material( + hideDivider: true, + flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2), + placeholder: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + AppLocalizations.of(context)!.selectClientLeftColumn, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), ), + ), + child: const ClientsLists( + splitView: true, ) - ], - ); + ); } else { return const ClientsLists( diff --git a/lib/screens/clients/clients_lists.dart b/lib/screens/clients/clients_lists.dart index a2cfa19..bdc56b8 100644 --- a/lib/screens/clients/clients_lists.dart +++ b/lib/screens/clients/clients_lists.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/clients.dart'; import 'package:adguard_home_manager/screens/clients/added_list.dart'; import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; import 'package:adguard_home_manager/screens/clients/clients_list.dart'; @@ -68,13 +68,7 @@ class _ClientsListsState extends State with TickerProviderStateMix splitView: widget.splitView, ); if (widget.splitView) { - Navigator.of(clientsNavigatorKey.currentContext!).pushReplacement( - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => w, - transitionDuration: Duration.zero, - reverseTransitionDuration: Duration.zero, - ) - ); + SplitView.of(context).push(w); } else { Navigator.of(context).push( @@ -94,13 +88,7 @@ class _ClientsListsState extends State with TickerProviderStateMix splitView: widget.splitView, ); if (widget.splitView) { - Navigator.of(clientsNavigatorKey.currentContext!).pushReplacement( - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => w, - transitionDuration: Duration.zero, - reverseTransitionDuration: Duration.zero, - ) - ); + SplitView.of(context).push(w); } else { Navigator.of(context).push( diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 9571ae3..64fd098 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -14,7 +14,7 @@ import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class ClientsFab extends StatelessWidget { - const ClientsFab({super.key}); + const ClientsFab({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -25,26 +25,22 @@ class ClientsFab extends StatelessWidget { final width = MediaQuery.of(context).size.width; void confirmAddClient(Client client) async { - if (!context.mounted) return; - ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.addingClient); final result = await clientsProvider.addClient(client); - - if (!context.mounted) return; processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientAddedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientNotAdded, color: Colors.red diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index 2654c24..d8e1104 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -95,14 +95,14 @@ class _SearchClientsState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientDeletedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientNotDeleted, color: Colors.red @@ -119,14 +119,14 @@ class _SearchClientsState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientUpdatedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientNotUpdated, color: Colors.red @@ -205,7 +205,7 @@ class _SearchClientsState extends State { height: 1, decoration: BoxDecoration( color: showDivider == true - ? Theme.of(context).colorScheme.surfaceContainerHighest + ? Theme.of(context).colorScheme.surfaceVariant : Colors.transparent ), ), @@ -226,16 +226,16 @@ class _SearchClientsState extends State { itemCount: clientsScreen.length, padding: const EdgeInsets.only(bottom: 0), itemBuilder: (context, index) => OptionsMenu( - options: (v) => [ + options: [ MenuOption( icon: Icons.edit_rounded, title: AppLocalizations.of(context)!.edit, - action: () => openClientModal(v) + action: (v) => openClientModal(v) ), MenuOption( icon: Icons.delete_rounded, title: AppLocalizations.of(context)!.delete, - action: () => openDeleteModal(v) + action: (v) => openDeleteModal(v) ), ], value: clientsScreen[index], diff --git a/lib/screens/connect/connect.dart b/lib/screens/connect/connect.dart index 01ac7cc..3528718 100644 --- a/lib/screens/connect/connect.dart +++ b/lib/screens/connect/connect.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class Connect extends StatefulWidget { - const Connect({super.key}); + const Connect({Key? key}) : super(key: key); @override State createState() => _ConnectState(); @@ -61,28 +61,26 @@ class _ConnectState extends State { appBar: AppBar( title: Text(AppLocalizations.of(context)!.connect), ), - body: SafeArea( - child: Stack( - children: [ - ServersList( - context: context, - controllers: expandableControllerList, - onChange: expandOrContract, - scrollController: scrollController, - breakingWidth: 700, - ), - AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: isVisible ? - appConfigProvider.showingSnackbar - ? 90 : 20 - : -90, - right: 20, - child: const FabConnect() - ) - ], - ), + body: Stack( + children: [ + ServersList( + context: context, + controllers: expandableControllerList, + onChange: expandOrContract, + scrollController: scrollController, + breakingWidth: 700, + ), + AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: isVisible ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: const FabConnect() + ) + ], ), ); } diff --git a/lib/screens/connect/fab.dart b/lib/screens/connect/fab.dart index f826485..05ed1a2 100644 --- a/lib/screens/connect/fab.dart +++ b/lib/screens/connect/fab.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; class FabConnect extends StatelessWidget { - const FabConnect({super.key}); + const FabConnect({Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index e2de2ff..7dd4d8c 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -6,8 +6,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/filters/modals/custom_rules/edit_custom_rules.dart'; -import 'package:adguard_home_manager/screens/filters/modals/custom_rules/add_custom_rule.dart'; +import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule.dart'; import 'package:adguard_home_manager/screens/filters/details/add_list_modal.dart'; import 'package:adguard_home_manager/providers/filtering_provider.dart'; @@ -20,10 +19,10 @@ class AddFiltersButton extends StatelessWidget { final Widget Function(void Function()) widget; const AddFiltersButton({ - super.key, + Key? key, required this.type, required this.widget - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -33,8 +32,6 @@ class AddFiltersButton extends StatelessWidget { final width = MediaQuery.of(context).size.width; void confirmAddRule(String rule) async { - if (!context.mounted) return; - ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.addingRule); @@ -42,16 +39,15 @@ class AddFiltersButton extends StatelessWidget { processModal.close(); - if (!context.mounted) return; if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.ruleAddedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.ruleNotAdded, color: Colors.red @@ -59,33 +55,6 @@ class AddFiltersButton extends StatelessWidget { } } - void confirmEditCustomRules(List rules) async { - if (!context.mounted) return; - - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.savingCustomRules); - - final result = await filteringProvider.setCustomRules(rules); - - processModal.close(); - - if (!context.mounted) return; - if (result == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.customRulesUpdatedSuccessfully, - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.customRulesNotUpdated, - color: Colors.red - ); - } - } - void openAddCustomRule() { showGeneralDialog( context: context, @@ -113,36 +82,7 @@ class AddFiltersButton extends StatelessWidget { ); } - void openEditCustomRule() { - showGeneralDialog( - context: context, - barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)) - ?Colors.transparent - : Colors.black54, - transitionBuilder: (context, anim1, anim2, child) { - return SlideTransition( - position: Tween( - begin: const Offset(0, 1), - end: const Offset(0, 0) - ).animate( - CurvedAnimation( - parent: anim1, - curve: Curves.easeInOutCubicEmphasized - ) - ), - child: child, - ); - }, - pageBuilder: (context, animation, secondaryAnimation) => EditCustomRules( - fullScreen: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)), - onConfirm: confirmEditCustomRules, - ), - ); - } - void confirmAddList({required String name, required String url, required String type}) async { - if (!context.mounted) return; - ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.addingList); @@ -150,30 +90,29 @@ class AddFiltersButton extends StatelessWidget { processModal.close(); - if (!context.mounted) return; if (result['success'] == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: "${AppLocalizations.of(context)!.listAdded} ${result['data']}.", color: Colors.green ); } else if (result['success'] == false && result['error'] == 'invalid_url') { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listUrlInvalid, color: Colors.red ); } else if (result['success'] == false && result['error'] == 'url_exists') { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listAlreadyAdded, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listNotAdded, color: Colors.red @@ -207,25 +146,10 @@ class AddFiltersButton extends StatelessWidget { } } - switch (type) { - case 'blacklist': - case 'whitelist': - return widget( - () => openAddWhitelistBlacklist(), - ); - - case 'add_custom_rule': - return widget( - () => openAddCustomRule(), - ); - - case 'edit_custom_rule': - return widget( - () => openEditCustomRule(), - ); - - default: - return const SizedBox(); - } + return widget( + type == 'blacklist' || type == 'whitelist' + ? () => openAddWhitelistBlacklist() + : () => openAddCustomRule(), + ); } } \ No newline at end of file diff --git a/lib/screens/filters/custom_rules_list.dart b/lib/screens/filters/custom_rules_list.dart index 284e15f..8706f1a 100644 --- a/lib/screens/filters/custom_rules_list.dart +++ b/lib/screens/filters/custom_rules_list.dart @@ -6,7 +6,6 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/filters/add_button.dart'; -import 'package:adguard_home_manager/screens/filters/modals/custom_rules/sort_rules.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -21,12 +20,12 @@ class CustomRulesList extends StatefulWidget { final void Function(String) onRemoveCustomRule; const CustomRulesList({ - super.key, + Key? key, required this.loadStatus, required this.scrollController, required this.data, required this.onRemoveCustomRule - }); + }) : super(key: key); @override State createState() => _CustomRulesListState(); @@ -35,8 +34,6 @@ class CustomRulesList extends StatefulWidget { class _CustomRulesListState extends State { late bool isVisible; - CustomRulesSorting _sortingMethod = CustomRulesSorting.topBottom; - @override initState(){ super.initState(); @@ -63,8 +60,6 @@ class _CustomRulesListState extends State { final filteringProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - final renderData = _sortingMethod == CustomRulesSorting.bottomTop ? widget.data.reversed.toList() : widget.data.toList(); - bool checkIfComment(String value) { final regex = RegExp(r'^(!|#).*$'); if (regex.hasMatch(value)) { @@ -109,16 +104,6 @@ class _CustomRulesListState extends State { } } - void showSortingMethodModal() { - showDialog( - context: context, - builder: (ctx) => SortCustomRulesModal( - sortingMethod: _sortingMethod, - onSelect: (value) => setState(() => _sortingMethod = value), - ), - ); - } - return CustomTabContentList( loadingGenerator: () => SizedBox( width: double.maxFinite, @@ -139,10 +124,10 @@ class _CustomRulesListState extends State { ], ), ), - itemsCount: renderData.length, + itemsCount: widget.data.length, contentWidget: (index) => ListTile( title: Text( - renderData[index], + widget.data[index], style: TextStyle( color: checkIfComment(widget.data[index]) == true ? Theme.of(context).colorScheme.onSurface.withOpacity(0.6) @@ -150,9 +135,9 @@ class _CustomRulesListState extends State { fontWeight: FontWeight.normal, ), ), - subtitle: generateSubtitle(renderData[index]), + subtitle: generateSubtitle(widget.data[index]), trailing: IconButton( - onPressed: () => widget.onRemoveCustomRule(renderData[index]), + onPressed: () => widget.onRemoveCustomRule(widget.data[index]), icon: const Icon(Icons.delete) ), ), @@ -177,8 +162,8 @@ class _CustomRulesListState extends State { TextButton.icon( onPressed: () async { final result = await filteringProvider.fetchFilters(); - if (result == false && context.mounted) { - showSnackbar( + if (result == false) { + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.errorLoadFilters, color: Colors.red @@ -218,38 +203,20 @@ class _CustomRulesListState extends State { onRefresh: () async { final result = await filteringProvider.fetchFilters(); if (result == false) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.errorLoadFilters, color: Colors.red ); } }, - fab: Column( - children: [ - FloatingActionButton.small( - onPressed: showSortingMethodModal, - child: Icon(Icons.sort_rounded), - ), - const SizedBox(height: 16), - AddFiltersButton( - type: 'edit_custom_rule', - widget: (fn) => FloatingActionButton.small( - onPressed: fn, - child: const Icon(Icons.edit_rounded), - ), - ), - const SizedBox(height: 16), - AddFiltersButton( - type: 'add_custom_rule', - widget: (fn) => FloatingActionButton( - onPressed: fn, - child: const Icon(Icons.add), - ), - ), - ], + fab: AddFiltersButton( + type: 'custom_rule', + widget: (fn) => FloatingActionButton( + onPressed: fn, + child: const Icon(Icons.add), + ), ), - heightFabHidden: -180, fabVisible: isVisible, ); } diff --git a/lib/screens/filters/details/add_list_modal.dart b/lib/screens/filters/details/add_list_modal.dart index f153224..fceebf9 100644 --- a/lib/screens/filters/details/add_list_modal.dart +++ b/lib/screens/filters/details/add_list_modal.dart @@ -1,12 +1,11 @@ import 'dart:io'; -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/models/filtering.dart'; -class AddListModal extends StatelessWidget { +class AddListModal extends StatefulWidget { final String type; final Filter? list; final void Function({required String name, required String url, required String type})? onConfirm; @@ -14,74 +13,19 @@ class AddListModal extends StatelessWidget { final bool dialog; const AddListModal({ - super.key, + Key? key, required this.type, this.list, this.onConfirm, this.onEdit, required this.dialog - }); + }) : super(key: key); @override - Widget build(BuildContext context) { - if (dialog == true) { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400 - ), - child: _Content( - list: list, - onConfirm: onConfirm, - onEdit: onEdit, - type: type, - ) - ), - ); - } - else { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ), - color: Theme.of(context).dialogBackgroundColor - ), - child: SafeArea( - child: _Content( - list: list, - onConfirm: onConfirm, - onEdit: onEdit, - type: type, - ), - ) - ), - ); - } - } + State createState() => _AddListModalState(); } -class _Content extends StatefulWidget { - final String type; - final Filter? list; - final void Function({required String name, required String url, required String type})? onConfirm; - final void Function({required Filter list, required String type})? onEdit; - - const _Content({ - required this.type, - required this.list, - required this.onConfirm, - required this.onEdit, - }); - - @override - State<_Content> createState() => _ContentState(); -} - -class _ContentState extends State<_Content> { +class _AddListModalState extends State { final TextEditingController nameController = TextEditingController(); final TextEditingController urlController = TextEditingController(); String? urlError; @@ -98,7 +42,8 @@ class _ContentState extends State<_Content> { } void validateUrl(String value) { - if (Regexps.url.hasMatch(value)) { + final urlRegex = RegExp(r'^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})$'); + if (urlRegex.hasMatch(value)) { setState(() => urlError = null); } else { @@ -124,134 +69,162 @@ class _ContentState extends State<_Content> { } @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( + Widget build(BuildContext context) { + Widget content() { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + widget.type == 'whitelist' + ? Icons.verified_user_rounded + : Icons.gpp_bad_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + const SizedBox(height: 16), + Text( + widget.list != null + ? widget.type == 'whitelist' + ? AppLocalizations.of(context)!.editWhitelist + : AppLocalizations.of(context)!.editBlacklist + : widget.type == 'whitelist' + ? AppLocalizations.of(context)!.addWhitelist + : AppLocalizations.of(context)!.addBlacklist, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 16), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: nameController, + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.badge_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.name, + ), + ), + ), + Container(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: urlController, + onChanged: validateUrl, + enabled: widget.list != null ? false : true, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: urlError, + labelText: AppLocalizations.of(context)!.urlAbsolutePath, + ), + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - widget.type == 'whitelist' - ? Icons.verified_user_rounded - : Icons.gpp_bad_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - const SizedBox(height: 16), - Text( - widget.list != null - ? widget.type == 'whitelist' - ? AppLocalizations.of(context)!.editWhitelist - : AppLocalizations.of(context)!.editBlacklist - : widget.type == 'whitelist' - ? AppLocalizations.of(context)!.addWhitelist - : AppLocalizations.of(context)!.addBlacklist, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 16), - ], - ), - ], + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: nameController, - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.badge_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.name, - ), - ), - ), - Container(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: urlController, - onChanged: validateUrl, - enabled: widget.list != null ? false : true, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: urlError, - labelText: AppLocalizations.of(context)!.urlAbsolutePath, - ), - ), + const SizedBox(width: 20), + TextButton( + onPressed: () { + Navigator.pop(context); + if (widget.list != null) { + final Filter newList = Filter( + url: urlController.text, + name: nameController.text, + lastUpdated: widget.list!.lastUpdated, + id: widget.list!.id, + rulesCount: widget.list!.rulesCount, + enabled: widget.list!.enabled + ); + widget.onEdit!( + list: newList, + type: widget.type + ); + } + else { + widget.onConfirm!( + name: nameController.text, + url: urlController.text, + type: widget.type + ); + } + }, + child: Text( + widget.list != null + ? AppLocalizations.of(context)!.save + : AppLocalizations.of(context)!.confirm + ) ), ], ), ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 20), - TextButton( - onPressed: () { - Navigator.pop(context); - if (widget.list != null) { - final Filter newList = Filter( - url: urlController.text, - name: nameController.text, - lastUpdated: widget.list!.lastUpdated, - id: widget.list!.id, - rulesCount: widget.list!.rulesCount, - enabled: widget.list!.enabled - ); - widget.onEdit!( - list: newList, - type: widget.type - ); - } - else { - widget.onConfirm!( - name: nameController.text, - url: urlController.text, - type: widget.type - ); - } - }, - child: Text( - widget.list != null - ? AppLocalizations.of(context)!.save - : AppLocalizations.of(context)!.confirm - ) - ), - ], + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); + } + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 ), + child: content() ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ); + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ), + color: Theme.of(context).dialogBackgroundColor + ), + child: content() + ), + ); + } } } \ No newline at end of file diff --git a/lib/screens/filters/details/check_host_modal.dart b/lib/screens/filters/details/check_host_modal.dart index be0efce..5325ada 100644 --- a/lib/screens/filters/details/check_host_modal.dart +++ b/lib/screens/filters/details/check_host_modal.dart @@ -1,6 +1,5 @@ // ignore_for_file: use_build_context_synchronously -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -9,62 +8,27 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/functions/get_filtered_status.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; -class CheckHostModal extends StatelessWidget { +class CheckHostModal extends StatefulWidget { final bool dialog; const CheckHostModal({ - super.key, + Key? key, required this.dialog - }); + }) : super(key: key); @override - Widget build(BuildContext context) { - if (dialog == true) { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400 - ), - child: const _Content() - ), - ); - } - else { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Container( - width: double.maxFinite, - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - color: Theme.of(context).dialogBackgroundColor - ), - child: const SafeArea( - child: _Content() - ) - ), - ); - } - } + State createState() => _CheckHostModalState(); } -class _Content extends StatefulWidget { - const _Content(); - - @override - State<_Content> createState() => _ContentState(); -} - -class _ContentState extends State<_Content> { +class _CheckHostModalState extends State { final TextEditingController domainController = TextEditingController(); String? domainError; Widget? resultWidget; void validateDomain(String value) { - if (Regexps.domain.hasMatch(value)) { + final domainRegex = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$'); + if (domainRegex.hasMatch(value)) { setState(() => domainError = null); } else { @@ -95,29 +59,17 @@ class _ContentState extends State<_Content> { setState(() => resultWidget = checking()); final result = await serversProvider.apiClient2!.checkHostFiltered(host: domainController.text); - if (!mounted) return; - if (result.successful == true) { - final status = getFilteredStatus(context, appConfigProvider, result.content['reason'], true); - if (mounted) { - setState(() => resultWidget = Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - status['icon'], - size: 18, - color: status['filtered'] == true - ? appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - : appConfigProvider.useThemeColorForStatus - ? Theme.of(context).colorScheme.primary - : Colors.green, - ), - const SizedBox(width: 10), - Text( - status['label'], - style: TextStyle( + if (mounted) { + if (result.successful == true) { + final status = getFilteredStatus(context, appConfigProvider, result.content['reason'], true); + if (mounted) { + setState(() => resultWidget = Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + status['icon'], + size: 18, color: status['filtered'] == true ? appConfigProvider.useThemeColorForStatus == true ? Colors.grey @@ -125,6 +77,39 @@ class _ContentState extends State<_Content> { : appConfigProvider.useThemeColorForStatus ? Theme.of(context).colorScheme.primary : Colors.green, + ), + const SizedBox(width: 10), + Text( + status['label'], + style: TextStyle( + color: status['filtered'] == true + ? appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + : appConfigProvider.useThemeColorForStatus + ? Theme.of(context).colorScheme.primary + : Colors.green, + fontWeight: FontWeight.w500 + ), + ) + ], + )); + } + } + else { + setState(() => resultWidget = Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.cancel, + size: 18, + color: Colors.red, + ), + const SizedBox(width: 10), + Text( + AppLocalizations.of(context)!.check, + style: const TextStyle( + color: Colors.red, fontWeight: FontWeight.w500 ), ) @@ -132,134 +117,143 @@ class _ContentState extends State<_Content> { )); } } - else { - setState(() => resultWidget = Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.cancel, - size: 18, - color: Colors.red, - ), - const SizedBox(width: 10), - Text( - AppLocalizations.of(context)!.check, - style: const TextStyle( - color: Colors.red, - fontWeight: FontWeight.w500 - ), - ) - ], - )); - } } - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.shield_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.checkHostFiltered, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 16), - ], - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: domainController, - onChanged: validateDomain, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: domainError, - labelText: AppLocalizations.of(context)!.domain, - ), - ), - ), - if (resultWidget != null) Padding( - padding: const EdgeInsets.all(24), - child: resultWidget, - ), - if (resultWidget == null) Padding( - padding: const EdgeInsets.all(24), - child: Center( - child: Text( - AppLocalizations.of(context)!.insertDomain, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - ), - ), - ), - ), - ], - ), - ), - ), - Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only( - bottom: 24, - right: 24 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, + Widget content() { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.shield_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.checkHostFiltered, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 16), + ], + ), + ], ), - const SizedBox(width: 20), - TextButton( - onPressed: domainController.text != '' && domainError == null - ? () => checkHost() - : null, - child: Text( - AppLocalizations.of(context)!.check, - style: TextStyle( - color: domainController.text != '' && domainError == null - ? Theme.of(context).colorScheme.primary - : Colors.grey + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: domainController, + onChanged: validateDomain, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: domainError, + labelText: AppLocalizations.of(context)!.domain, + ), + ), + ), + if (resultWidget != null) Padding( + padding: const EdgeInsets.all(24), + child: resultWidget, + ), + if (resultWidget == null) Padding( + padding: const EdgeInsets.all(24), + child: Center( + child: Text( + AppLocalizations.of(context)!.insertDomain, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + ), ), ), ), ], ), - ) - ], - ) - ], - ); + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 24, + right: 24 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close), + ), + const SizedBox(width: 20), + TextButton( + onPressed: domainController.text != '' && domainError == null + ? () => checkHost() + : null, + child: Text( + AppLocalizations.of(context)!.check, + style: TextStyle( + color: domainController.text != '' && domainError == null + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ), + ), + ], + ), + ) + ], + ) + ], + ); + } + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: content() + ), + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + width: double.maxFinite, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + color: Theme.of(context).dialogBackgroundColor + ), + child: content() + ), + ); + } } } \ No newline at end of file diff --git a/lib/screens/filters/details/list_details_screen.dart b/lib/screens/filters/details/list_details_screen.dart index 188854c..b5e305c 100644 --- a/lib/screens/filters/details/list_details_screen.dart +++ b/lib/screens/filters/details/list_details_screen.dart @@ -25,11 +25,11 @@ class ListDetailsScreen extends StatefulWidget { final bool dialog; const ListDetailsScreen({ - super.key, + Key? key, required this.listId, required this.type, required this.dialog - }); + }) : super(key: key); @override State createState() => _ListDetailsScreenState(); @@ -64,6 +64,8 @@ class _ListDetailsScreenState extends State { final filteringProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Filter? list; try { list = filteringProvider.filtering != null @@ -94,14 +96,14 @@ class _ListDetailsScreenState extends State { ); processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listDataUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listDataNotUpdated, color: Colors.red @@ -109,6 +111,177 @@ class _ListDetailsScreenState extends State { } } + List content() { + return [ + CustomListTile( + icon: Icons.shield_rounded, + title: AppLocalizations.of(context)!.currentStatus, + subtitleWidget: Text( + list!.enabled == true + ? AppLocalizations.of(context)!.enabled + : AppLocalizations.of(context)!.disabled, + style: TextStyle( + color: list.enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + fontWeight: FontWeight.w500 + ), + ), + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + CustomListTile( + icon: Icons.badge_rounded, + title: AppLocalizations.of(context)!.name, + subtitle: list.name, + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + CustomListTile( + icon: Icons.link_rounded, + title: "URL", + subtitle: list.url, + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + trailing: IconButton( + onPressed: () => openUrl(list!.url), + icon: const Icon(Icons.open_in_browser_rounded), + tooltip: AppLocalizations.of(context)!.openListUrl, + ), + ), + CustomListTile( + icon: Icons.list_rounded, + title: AppLocalizations.of(context)!.rules, + subtitle: list.rulesCount.toString(), + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + CustomListTile( + icon: Icons.shield_rounded, + title: AppLocalizations.of(context)!.listType, + subtitle: widget.type == 'whitelist' + ? AppLocalizations.of(context)!.whitelist + : AppLocalizations.of(context)!.blacklist, + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + if (list.lastUpdated != null) CustomListTile( + icon: Icons.schedule_rounded, + title: AppLocalizations.of(context)!.latestUpdate, + subtitle: convertTimestampLocalTimezone(list.lastUpdated!, 'dd-MM-yyyy HH:mm'), + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + if (widget.dialog == true) Container(height: 16) + ]; + } + + List actions() { + return [ + IconButton( + onPressed: () => { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (ctx) => AddListModal( + list: list, + type: widget.type, + onEdit: ({required Filter list, required String type}) async => updateList( + action: FilteringListActions.edit, + filterList: list + ), + dialog: true, + ), + ) + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (ctx) => AddListModal( + list: list, + type: widget.type, + onEdit: ({required Filter list, required String type}) async => updateList( + action: FilteringListActions.edit, + filterList: list + ), + dialog: false, + ), + isScrollControlled: true, + backgroundColor: Colors.transparent + ) + } + }, + icon: const Icon(Icons.edit), + tooltip: AppLocalizations.of(context)!.edit, + ), + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (c) => DeleteListModal( + onConfirm: () async { + ProcessModal processModal = ProcessModal(); + processModal.open(AppLocalizations.of(context)!.deletingList); + final result = await filteringProvider.deleteList( + listUrl: list!.url, + type: widget.type, + ); + processModal.close(); + if (result == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.listDeleted, + color: Colors.green + ); + Navigator.pop(context); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.listNotDeleted, + color: Colors.red + ); + } + } + ) + ); + }, + icon: const Icon(Icons.delete), + tooltip: AppLocalizations.of(context)!.delete, + ), + const SizedBox(width: 10), + ]; + } + if (widget.dialog == true) { return Dialog( child: ConstrainedBox( @@ -157,11 +330,7 @@ class _ListDetailsScreenState extends State { ? AppLocalizations.of(context)!.disableList : AppLocalizations.of(context)!.enableList, ), - _Actions( - list: list, - type: widget.type, - updateList: (action, filterList) => updateList(action: action, filterList: filterList), - ) + ...actions() ], ) ], @@ -171,13 +340,7 @@ class _ListDetailsScreenState extends State { child: list != null ? SingleChildScrollView( child: Wrap( - children: [ - _Content( - isDialog: widget.dialog, - list: list, - type: widget.type, - ) - ], + children: content(), ), ) : Center( @@ -198,266 +361,51 @@ class _ListDetailsScreenState extends State { return Dialog.fullscreen( child: Scaffold( appBar: AppBar( - title: Text(AppLocalizations.of(context)!.listDetails), - actions: list != null - ? [ - _Actions( - list: list, - type: widget.type, - updateList: (action, filterList) => updateList(action: action, filterList: filterList), - ) - ] - : null, - ), - body: SafeArea( - child: Stack( - children: [ - if (list != null) ListView( - children: [ - _Content( - isDialog: widget.dialog, - list: list, - type: widget.type, - ) - ], - ), - if (list == null) Center( - child: Text( - AppLocalizations.of(context)!.listNotAvailable, - style: const TextStyle( - fontSize: 24, - ), - ), - ), - if (list != null) AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: fabVisible ? - appConfigProvider.showingSnackbar - ? 70 : (Platform.isIOS ? 40 : 20) - : -70, - right: 20, - child: FloatingActionButton( - onPressed: () => updateList( - action: list!.enabled == true - ? FilteringListActions.disable - : FilteringListActions.enable, - filterList: list - ), - child: Icon( - list.enabled == true - ? Icons.gpp_bad_rounded - : Icons.verified_user_rounded, - ), - ), - ) - ], + leading: CloseButton( + onPressed: () => Navigator.pop(context), ), + title: Text(AppLocalizations.of(context)!.listDetails), + actions: list != null ? actions() : null, + ), + body: Stack( + children: [ + if (list != null) ListView( + children: content(), + ), + if (list == null) Center( + child: Text( + AppLocalizations.of(context)!.listNotAvailable, + style: const TextStyle( + fontSize: 24, + ), + ), + ), + if (list != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible ? + appConfigProvider.showingSnackbar + ? 70 : (Platform.isIOS ? 40 : 20) + : -70, + right: 20, + child: FloatingActionButton( + onPressed: () => updateList( + action: list!.enabled == true + ? FilteringListActions.disable + : FilteringListActions.enable, + filterList: list + ), + child: Icon( + list.enabled == true + ? Icons.gpp_bad_rounded + : Icons.verified_user_rounded, + ), + ), + ) + ], ), ), ); } } -} - -class _Content extends StatelessWidget { - final Filter list; - final bool isDialog; - final String type; - - const _Content({ - required this.list, - required this.isDialog, - required this.type, - }); - - @override - Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); - - return Column( - children: [ - CustomListTile( - icon: Icons.shield_rounded, - title: AppLocalizations.of(context)!.currentStatus, - subtitleWidget: Text( - list.enabled == true - ? AppLocalizations.of(context)!.enabled - : AppLocalizations.of(context)!.disabled, - style: TextStyle( - color: list.enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - fontWeight: FontWeight.w500 - ), - ), - padding: isDialog == true - ? const EdgeInsets.symmetric( - horizontal: 24, - vertical: 8 - ) - : null, - ), - CustomListTile( - icon: Icons.badge_rounded, - title: AppLocalizations.of(context)!.name, - subtitle: list.name, - padding: isDialog == true - ? const EdgeInsets.symmetric( - horizontal: 24, - vertical: 8 - ) - : null, - ), - CustomListTile( - icon: Icons.link_rounded, - title: "URL", - subtitle: list.url, - padding: isDialog == true - ? const EdgeInsets.symmetric( - horizontal: 24, - vertical: 8 - ) - : null, - trailing: IconButton( - onPressed: () => openUrl(list.url), - icon: const Icon(Icons.open_in_browser_rounded), - tooltip: AppLocalizations.of(context)!.openListUrl, - ), - ), - CustomListTile( - icon: Icons.list_rounded, - title: AppLocalizations.of(context)!.rules, - subtitle: list.rulesCount.toString(), - padding: isDialog == true - ? const EdgeInsets.symmetric( - horizontal: 24, - vertical: 8 - ) - : null, - ), - CustomListTile( - icon: Icons.shield_rounded, - title: AppLocalizations.of(context)!.listType, - subtitle: type == 'whitelist' - ? AppLocalizations.of(context)!.whitelist - : AppLocalizations.of(context)!.blacklist, - padding: isDialog == true - ? const EdgeInsets.symmetric( - horizontal: 24, - vertical: 8 - ) - : null, - ), - if (list.lastUpdated != null) CustomListTile( - icon: Icons.schedule_rounded, - title: AppLocalizations.of(context)!.latestUpdate, - subtitle: convertTimestampLocalTimezone(list.lastUpdated!, 'dd-MM-yyyy HH:mm'), - padding: isDialog == true - ? const EdgeInsets.symmetric( - horizontal: 24, - vertical: 8 - ) - : null, - ), - if (isDialog == true) Container(height: 16) - ], - ); - } -} - -class _Actions extends StatelessWidget { - final Filter? list; - final String type; - final void Function(FilteringListActions, Filter) updateList; - - const _Actions({ - required this.list, - required this.type, - required this.updateList, - }); - - @override - Widget build(BuildContext context) { - final filteringProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - return Row( - children: [ - IconButton( - onPressed: () => { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (ctx) => AddListModal( - list: list, - type: type, - onEdit: ({required Filter list, required String type}) async => updateList(FilteringListActions.edit, list), - dialog: true, - ), - ) - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (ctx) => AddListModal( - list: list, - type: type, - onEdit: ({required Filter list, required String type}) async => updateList(FilteringListActions.edit, list), - dialog: false, - ), - isScrollControlled: true, - backgroundColor: Colors.transparent - ) - } - }, - icon: const Icon(Icons.edit), - tooltip: AppLocalizations.of(context)!.edit, - ), - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (c) => DeleteListModal( - onConfirm: () async { - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.deletingList); - final result = await filteringProvider.deleteList( - listUrl: list!.url, - type: type, - ); - processModal.close(); - if (result == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.listDeleted, - color: Colors.green - ); - Navigator.pop(context); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.listNotDeleted, - color: Colors.red - ); - } - } - ) - ); - }, - icon: const Icon(Icons.delete), - tooltip: AppLocalizations.of(context)!.delete, - ), - const SizedBox(width: 10), - ], - ); - } } \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index ab624e0..c0a7e20 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -24,7 +24,7 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/clients.dart'; class Filters extends StatefulWidget { - const Filters({super.key}); + const Filters({Key? key}) : super(key: key); @override State createState() => _FiltersState(); @@ -57,14 +57,14 @@ class _FiltersState extends State { if (!mounted) return; processModal.close(); if (result['success'] == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: "${result['data']['updated']} ${AppLocalizations.of(context)!.listsUpdated}", color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listsNotUpdated, color: Colors.red @@ -109,14 +109,14 @@ class _FiltersState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.filteringStatusUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.filteringStatusNotUpdated, color: Colors.red @@ -133,14 +133,14 @@ class _FiltersState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.updateFrequencyChanged, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.updateFrequencyNotChanged, color: Colors.red @@ -163,14 +163,14 @@ class _FiltersState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.ruleRemovedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.ruleNotRemoved, color: Colors.red @@ -188,25 +188,31 @@ class _FiltersState extends State { } void openListDetails(Filter filter, String type) { - if (width > 900) { - showDialog( - context: context, - builder: (context) => ListDetailsScreen( - listId: filter.id, - type: type, - dialog: width > 900 || !(Platform.isAndroid | Platform.isIOS), - ), - ); - } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => ListDetailsScreen( - listId: filter.id, - type: type, - dialog: width > 900 || !(Platform.isAndroid | Platform.isIOS), - ), - )); - } + showGeneralDialog( + context: context, + barrierColor: !(width > 900 || !(Platform.isAndroid | Platform.isIOS)) + ?Colors.transparent + : Colors.black54, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0) + ).animate( + CurvedAnimation( + parent: anim1, + curve: Curves.easeInOutCubicEmphasized + ) + ), + child: child, + ); + }, + pageBuilder: (context, animation, secondaryAnimation) => ListDetailsScreen( + listId: filter.id, + type: type, + dialog: width > 900 || !(Platform.isAndroid | Platform.isIOS), + ), + ); } List actions() { diff --git a/lib/screens/filters/filters_list.dart b/lib/screens/filters/filters_list.dart index b9d4617..f4cca03 100644 --- a/lib/screens/filters/filters_list.dart +++ b/lib/screens/filters/filters_list.dart @@ -27,13 +27,13 @@ class FiltersList extends StatefulWidget { final void Function(Filter, String) onOpenDetailsScreen; const FiltersList({ - super.key, + Key? key, required this.loadStatus, required this.scrollController, required this.data, required this.type, required this.onOpenDetailsScreen - }); + }) : super(key: key); @override State createState() => _FiltersListState(); @@ -133,7 +133,7 @@ class _FiltersListState extends State { onPressed: () async { final result = await filteringProvider.fetchFilters(); if (result == false && mounted) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.errorLoadFilters, color: Colors.red @@ -173,7 +173,7 @@ class _FiltersListState extends State { onRefresh: () async { final result = await filteringProvider.fetchFilters(); if (result == false && mounted) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.errorLoadFilters, color: Colors.red diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index a659fa8..3401bac 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -84,7 +84,7 @@ class FiltersTripleColumn extends StatelessWidget { onPressed: () async { final result = await filteringProvider.fetchFilters(); if (result == false && context.mounted) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.errorLoadFilters, color: Colors.red @@ -247,34 +247,21 @@ class FiltersTripleColumn extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Flexible( - child: Text( - AppLocalizations.of(context)!.customRules, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500 - ), + Text( + AppLocalizations.of(context)!.customRules, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500 ), ), - const SizedBox(width: 16), AddFiltersButton( - type: 'edit_custom_rule', - widget: (fn) => IconButton( - onPressed: fn, - icon: const Icon(Icons.edit_rounded), - tooltip: AppLocalizations.of(context)!.editCustomRules, - ) - ), - const SizedBox(width: 8), - AddFiltersButton( - type: 'add_custom_rule', + type: '', widget: (fn) => IconButton( onPressed: fn, icon: const Icon(Icons.add_rounded), tooltip: AppLocalizations.of(context)!.addCustomRule, ) - ), + ) ], ), ), @@ -296,11 +283,11 @@ class FiltersTripleColumn extends StatelessWidget { ), ], child: OptionsMenu( - options: (_) => [ + options: [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: filteringProvider.filtering!.userRules[index], successMessage: AppLocalizations.of(context)!.copiedClipboard, ) diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 4d2f57f..4cb6865 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -56,17 +56,16 @@ class ListOptionsMenu extends StatelessWidget { ); processModal.close(); - - if (!context.mounted) return; + if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listDataUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.listDataNotUpdated, color: Colors.red @@ -146,7 +145,7 @@ class ListOptionsMenu extends StatelessWidget { color: Colors.transparent, child: InkWell( child: OptionsMenu( - options: (_) => [ + options: [ MenuOption( title: list.enabled == true ? AppLocalizations.of(context)!.disable @@ -154,12 +153,12 @@ class ListOptionsMenu extends StatelessWidget { icon: list.enabled == true ? Icons.gpp_bad_rounded : Icons.verified_user_rounded, - action: () => enableDisable() + action: (_) => enableDisable() ), MenuOption( title: AppLocalizations.of(context)!.copyListUrl, icon: Icons.copy_rounded, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: list.url, successMessage: AppLocalizations.of(context)!.listUrlCopied ) @@ -167,12 +166,12 @@ class ListOptionsMenu extends StatelessWidget { MenuOption( title: AppLocalizations.of(context)!.openListUrl, icon: Icons.open_in_browser_rounded, - action: () => openUrl(list.url) + action: (_) => openUrl(list.url) ), MenuOption( title: AppLocalizations.of(context)!.selectionMode, icon: Icons.check_rounded, - action: () => Future.delayed( + action: (_) => Future.delayed( const Duration(milliseconds: 0), () => openSelectionMode() ) diff --git a/lib/screens/filters/modals/add_custom_rule.dart b/lib/screens/filters/modals/add_custom_rule.dart new file mode 100644 index 0000000..c7e5bcd --- /dev/null +++ b/lib/screens/filters/modals/add_custom_rule.dart @@ -0,0 +1,414 @@ +import 'package:flutter/material.dart'; +import 'package:segmented_button_slide/segmented_button_slide.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/functions/open_url.dart'; +import 'package:adguard_home_manager/constants/urls.dart'; + +class AddCustomRule extends StatefulWidget { + final void Function(String) onConfirm; + final bool fullScreen; + + const AddCustomRule({ + Key? key, + required this.onConfirm, + required this.fullScreen + }) : super(key: key); + + @override + State createState() => _AddCustomRuleState(); +} + +enum BlockingPresets { block, unblock, custom } + +class _AddCustomRuleState extends State { + final TextEditingController domainController = TextEditingController(); + String? domainError; + + BlockingPresets preset = BlockingPresets.block; + + bool addImportant = false; + + bool checkValidValues() { + if ( + domainController.text != '' && + domainError == null + ) { + return true; + } + else { + return false; + } + } + + void validateDomain(String value) { + final domainRegex = RegExp(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$'); + if (domainRegex.hasMatch(value)) { + setState(() => domainError = null); + } + else { + setState(() => domainError = AppLocalizations.of(context)!.domainNotValid); + } + checkValidValues(); + } + + String buildRule({String?value}) { + String rule = ""; + + String fieldValue = value ?? domainController.text; + + if (preset == BlockingPresets.block) { + rule = "||${fieldValue.trim()}^"; + } + else if (preset == BlockingPresets.unblock) { + rule = "@@||${fieldValue.trim()}^"; + } + else { + rule = fieldValue.trim(); + } + + if (addImportant == true) { + rule = "$rule\$important"; + } + + return rule; + } + + @override + Widget build(BuildContext context) { + + List content() { + return [ + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: Theme.of(context).colorScheme.primary + ) + ), + child: Text( + buildRule(), + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w500 + ), + ) + ), + ], + ), + Container(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: domainController, + onChanged: (value) => setState(() => {}), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: domainError, + labelText: AppLocalizations.of(context)!.domain, + ), + ), + ), + Container(height: 30), + SegmentedButtonSlide( + entries: [ + SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.block), + SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.unblock), + SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.custom), + ], + selectedEntry: preset.index, + onChange: (v) => setState(() => preset = BlockingPresets.values[v]), + colors: SegmentedButtonSlideColors( + barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2), + backgroundSelectedColor: Theme.of(context).colorScheme.primary, + foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary, + foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface, + hoverColor: Theme.of(context).colorScheme.onSurfaceVariant, + ), + textOverflow: TextOverflow.ellipsis, + fontSize: 14, + height: 40, + margin: const EdgeInsets.symmetric( + horizontal: 24, + ), + ), + Container(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => setState(() => addImportant = !addImportant), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + AppLocalizations.of(context)!.addImportant, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + Switch( + value: addImportant, + onChanged: (value) => setState(() => addImportant = value), + ) + ], + ), + ), + ), + ), + Container(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Card( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + children: [ + Icon( + Icons.info, + color: Theme.of(context).colorScheme.onSurface + ), + const SizedBox(width: 20), + Text( + AppLocalizations.of(context)!.examples, + style: TextStyle( + fontSize: 18, + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + const SizedBox(height: 20), + SizedBox( + width: double.maxFinite, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "||example.org^", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example1, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 20), + Text( + "@@||example.org^", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example2, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 20), + Text( + "! Here goes a comment", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + Text( + "# Also a comment", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example3, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 20), + Text( + "/REGEX/", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example4, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + ], + ), + ) + ], + ), + ), + ), + ), + Container(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => openUrl(Urls.customRuleDocs), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + AppLocalizations.of(context)!.moreInformation, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 15), + child: Icon( + Icons.open_in_new, + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + ), + ), + ), + Container(height: 20) + ]; + } + + if (widget.fullScreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + leading: CloseButton(onPressed: () => Navigator.pop(context)), + title: Text(AppLocalizations.of(context)!.addCustomRule), + actions: [ + IconButton( + onPressed: checkValidValues() == true + ? () { + Navigator.pop(context); + widget.onConfirm(buildRule()); + } + : null, + icon: const Icon(Icons.check) + ), + const SizedBox(width: 10) + ], + ), + body: ListView( + children: content(), + ) + ), + ); + } + else { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.clear_rounded), + tooltip: AppLocalizations.of(context)!.close, + ), + const SizedBox(width: 8), + Text( + AppLocalizations.of(context)!.addCustomRule, + style: const TextStyle( + fontSize: 22 + ), + ), + ], + ), + IconButton( + onPressed: checkValidValues() == true + ? () { + Navigator.pop(context); + widget.onConfirm(buildRule()); + } + : null, + icon: const Icon(Icons.check) + ) + ], + ), + ), + Flexible( + child: SingleChildScrollView( + child: Wrap( + alignment: WrapAlignment.center, + children: content(), + ), + ), + ) + ], + ), + ), + ); + } + } +} \ No newline at end of file diff --git a/lib/screens/filters/modals/blocked_services_screen.dart b/lib/screens/filters/modals/blocked_services_screen.dart index ac54c61..6169d0b 100644 --- a/lib/screens/filters/modals/blocked_services_screen.dart +++ b/lib/screens/filters/modals/blocked_services_screen.dart @@ -17,9 +17,9 @@ class BlockedServicesScreen extends StatefulWidget { final bool fullScreen; const BlockedServicesScreen({ - super.key, + Key? key, required this.fullScreen - }); + }) : super(key: key); @override State createState() => _BlockedServicesScreenStateWidget(); @@ -68,14 +68,14 @@ class _BlockedServicesScreenStateWidget extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.blockedServicesUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.blockedServicesNotUpdated, color: Colors.red @@ -83,136 +83,134 @@ class _BlockedServicesScreenStateWidget extends State { } } - if (widget.fullScreen == true) { - return Dialog.fullscreen( - child: Material( - child: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - leading: CloseButton(onPressed: () => Navigator.pop(context)), - title: Text(AppLocalizations.of(context)!.blockedServices), - actions: [ - IconButton( - onPressed: updateBlockedServices, - icon: const Icon( - Icons.save_rounded - ), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 10) - ], + Widget body() { + switch (filteringProvider.blockedServicesLoadStatus) { + case LoadStatus.loading: + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingBlockedServicesList, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ) - ) - ], - body: SafeArea( - top: false, - bottom: true, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loading) SliverFillRemaining( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingBlockedServicesList, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], + ], + ), + ); + + case LoadStatus.loaded: + return ListView.builder( + itemCount: filteringProvider.blockedServices!.services.length, + itemBuilder: (context, index) => Material( + color: Colors.transparent, + child: InkWell( + onTap: () => updateValues( + values.contains(filteringProvider.blockedServices!.services[index].id), + filteringProvider.blockedServices!.services[index] + ), + child: Padding( + padding: const EdgeInsets.only( + top: 6, + bottom: 6, + right: 12, + left: 24 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + filteringProvider.blockedServices!.services[index].name, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface ), ), - ), - if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loaded) SliverList.builder( - itemCount: filteringProvider.blockedServices!.services.length, - itemBuilder: (context, index) => Material( - color: Colors.transparent, - child: InkWell( - onTap: () => updateValues( - values.contains(filteringProvider.blockedServices!.services[index].id), - filteringProvider.blockedServices!.services[index] - ), - child: Padding( - padding: const EdgeInsets.only( - top: 6, - bottom: 6, - right: 12, - left: 24 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - filteringProvider.blockedServices!.services[index].name, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Checkbox( - value: values.contains(filteringProvider.blockedServices!.services[index].id), - onChanged: (value) => updateValues( - value!, - filteringProvider.blockedServices!.services[index] - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5) - ), - ) - ], - ), - ), + Checkbox( + value: values.contains(filteringProvider.blockedServices!.services[index].id), + onChanged: (value) => updateValues( + value!, + filteringProvider.blockedServices!.services[index] + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5) ), ) - ), - if (filteringProvider.blockedServicesLoadStatus == LoadStatus.error) SliverFillRemaining( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.blockedServicesListNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ), - ) - ], + ], + ), ), - ) + ), ) + ); + + case LoadStatus.error: + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.blockedServicesListNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + } + + if (widget.fullScreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + leading: CloseButton(onPressed: () => Navigator.pop(context)), + title: Text(AppLocalizations.of(context)!.blockedServices), + actions: [ + IconButton( + onPressed: updateBlockedServices, + icon: const Icon( + Icons.save_rounded + ), + tooltip: AppLocalizations.of(context)!.save, + ), + const SizedBox(width: 10) + ], + ), + body: RefreshIndicator( + onRefresh: () async { + final result = await filteringProvider.loadBlockedServices(); + if (result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.blockedServicesListNotLoaded, + color: Colors.red + ); + } + }, + child: body() ), ), ); @@ -258,106 +256,7 @@ class _BlockedServicesScreenStateWidget extends State { ), ), Expanded( - child: Builder( - builder: (ctx) { - switch (filteringProvider.blockedServicesLoadStatus) { - case LoadStatus.loading: - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingBlockedServicesList, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return ListView.builder( - itemCount: filteringProvider.blockedServices!.services.length, - itemBuilder: (context, index) => Material( - color: Colors.transparent, - child: InkWell( - onTap: () => updateValues( - values.contains(filteringProvider.blockedServices!.services[index].id), - filteringProvider.blockedServices!.services[index] - ), - child: Padding( - padding: const EdgeInsets.only( - top: 6, - bottom: 6, - right: 12, - left: 24 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - filteringProvider.blockedServices!.services[index].name, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Checkbox( - value: values.contains(filteringProvider.blockedServices!.services[index].id), - onChanged: (value) => updateValues( - value!, - filteringProvider.blockedServices!.services[index] - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5) - ), - ) - ], - ), - ), - ), - ) - ); - - case LoadStatus.error: - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.blockedServicesListNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - }, - ) + child: body() ), ], ) @@ -367,7 +266,6 @@ class _BlockedServicesScreenStateWidget extends State { } } - void openBlockedServicesModal({ required BuildContext context, required double width, diff --git a/lib/screens/filters/modals/custom_rules/add_custom_rule.dart b/lib/screens/filters/modals/custom_rules/add_custom_rule.dart deleted file mode 100644 index 674c200..0000000 --- a/lib/screens/filters/modals/custom_rules/add_custom_rule.dart +++ /dev/null @@ -1,354 +0,0 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; -import 'package:flutter/material.dart'; -import 'package:segmented_button_slide/segmented_button_slide.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/filters/modals/custom_rules/custom_rule_docs.dart'; - -enum _BlockingPresets { block, unblock, custom } - -class AddCustomRule extends StatefulWidget { - final void Function(String) onConfirm; - final bool fullScreen; - - const AddCustomRule({ - super.key, - required this.onConfirm, - required this.fullScreen - }); - - @override - State createState() => _AddCustomRuleState(); -} - -class _AddCustomRuleState extends State { - final TextEditingController _domainController = TextEditingController(); - String? _domainError; - - _BlockingPresets _preset = _BlockingPresets.block; - - bool _addImportant = false; - - bool _checkValidValues() { - if ( - _domainController.text != '' && - _domainError == null - ) { - return true; - } - else { - return false; - } - } - - void validateDomain(String value) { - if (Regexps.domain.hasMatch(value)) { - setState(() => _domainError = null); - } - else { - setState(() => _domainError = AppLocalizations.of(context)!.domainNotValid); - } - _checkValidValues(); - } - - @override - Widget build(BuildContext context) { - if (widget.fullScreen == true) { - return Dialog.fullscreen( - child: Material( - child: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - leading: CloseButton(onPressed: () => Navigator.pop(context)), - title: Text(AppLocalizations.of(context)!.addCustomRule), - actions: [ - IconButton( - onPressed: _checkValidValues() == true - ? () { - Navigator.pop(context); - widget.onConfirm( - _buildRule( - domainController: _domainController, - important: _addImportant, - preset: _preset - ) - ); - } - : null, - icon: const Icon(Icons.check) - ), - const SizedBox(width: 10) - ], - ) - ) - ], - body: SafeArea( - top: false, - bottom: true, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverList.list( - children: [ - _CustomRuleEditor( - domainController: _domainController, - domainError: _domainError, - important: _addImportant, - preset: _preset, - setImportant: (v) => setState(() => _addImportant = v), - setPreset: (v) => setState(() => _preset = v), - validateDomain: validateDomain - ) - ] - ) - ], - ), - ) - ) - ), - ), - ); - } - else { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.clear_rounded), - tooltip: AppLocalizations.of(context)!.close, - ), - const SizedBox(width: 8), - Text( - AppLocalizations.of(context)!.addCustomRule, - style: const TextStyle( - fontSize: 22 - ), - ), - ], - ), - IconButton( - onPressed: _checkValidValues() == true - ? () { - Navigator.pop(context); - widget.onConfirm( - _buildRule( - domainController: _domainController, - important: _addImportant, - preset: _preset - ) - ); - } - : null, - icon: const Icon(Icons.check) - ) - ], - ), - ), - Flexible( - child: SingleChildScrollView( - child: Wrap( - alignment: WrapAlignment.center, - children: [ - _CustomRuleEditor( - domainController: _domainController, - domainError: _domainError, - important: _addImportant, - preset: _preset, - setImportant: (v) => setState(() => _addImportant = v), - setPreset: (v) => setState(() => _preset = v), - validateDomain: validateDomain - ) - ] - ), - ), - ) - ], - ), - ), - ); - } - } -} - -class _CustomRuleEditor extends StatelessWidget { - final TextEditingController domainController; - final String? domainError; - final bool important; - final void Function(bool) setImportant; - final _BlockingPresets preset; - final void Function(_BlockingPresets) setPreset; - final void Function(String) validateDomain; - - const _CustomRuleEditor({ - required this.domainController, - required this.domainError, - required this.important, - required this.setImportant, - required this.preset, - required this.setPreset, - required this.validateDomain, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(30), - border: Border.all( - color: Theme.of(context).colorScheme.primary - ) - ), - child: Text( - _buildRule( - domainController: domainController, - important: important, - preset: preset, - ), - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w500 - ), - ) - ), - ], - ), - Container(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: domainController, - onChanged: validateDomain, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: domainError, - labelText: AppLocalizations.of(context)!.domain, - ), - ), - ), - Container(height: 30), - SegmentedButtonSlide( - entries: [ - SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.block), - SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.unblock), - SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.custom), - ], - selectedEntry: preset.index, - onChange: (v) => setPreset(_BlockingPresets.values[v]), - colors: SegmentedButtonSlideColors( - barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2), - backgroundSelectedColor: Theme.of(context).colorScheme.primary, - ), - textOverflow: TextOverflow.ellipsis, - height: 40, - margin: const EdgeInsets.symmetric( - horizontal: 24, - ), - selectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, - fontWeight: FontWeight.w700 - ), - unselectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - hoverTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - Container(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setImportant(!important), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - AppLocalizations.of(context)!.addImportant, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ), - Switch( - value: important, - onChanged: setImportant, - ) - ], - ), - ), - ), - ), - Container(height: 20), - const CustomRuleDocs(), - Container(height: 20) - ] - ); - } -} - - String _buildRule({ - String? value, - required TextEditingController domainController, - required _BlockingPresets preset, - required bool important -}) { - String rule = ""; - String fieldValue = value ?? domainController.text; - if (preset == _BlockingPresets.block) { - rule = "||${fieldValue.trim()}^"; - } - else if (preset == _BlockingPresets.unblock) { - rule = "@@||${fieldValue.trim()}^"; - } - else { - rule = fieldValue.trim(); - } - if (important == true) { - rule = "$rule\$important"; - } - return rule; - } \ No newline at end of file diff --git a/lib/screens/filters/modals/custom_rules/custom_rule_docs.dart b/lib/screens/filters/modals/custom_rules/custom_rule_docs.dart deleted file mode 100644 index 3d2c359..0000000 --- a/lib/screens/filters/modals/custom_rules/custom_rule_docs.dart +++ /dev/null @@ -1,166 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/constants/urls.dart'; -import 'package:adguard_home_manager/functions/open_url.dart'; - -class CustomRuleDocs extends StatelessWidget { - const CustomRuleDocs({super.key}); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Row( - children: [ - Icon( - Icons.info, - color: Theme.of(context).colorScheme.onSurface - ), - const SizedBox(width: 20), - Text( - AppLocalizations.of(context)!.examples, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - const SizedBox(height: 20), - SizedBox( - width: double.maxFinite, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "||example.org^", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example1, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 20), - Text( - "@@||example.org^", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example2, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 20), - Text( - "! Here goes a comment", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - Text( - "# Also a comment", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example3, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 20), - Text( - "/REGEX/", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example4, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - ], - ), - ) - ], - ), - ), - ), - ), - Container(height: 8), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => openUrl(Urls.customRuleDocs), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - AppLocalizations.of(context)!.moreInformation, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 15), - child: Icon( - Icons.open_in_new, - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - ), - ), - ), - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/filters/modals/custom_rules/edit_custom_rules.dart b/lib/screens/filters/modals/custom_rules/edit_custom_rules.dart deleted file mode 100644 index b063411..0000000 --- a/lib/screens/filters/modals/custom_rules/edit_custom_rules.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/filters/modals/custom_rules/custom_rule_docs.dart'; - -import 'package:adguard_home_manager/providers/filtering_provider.dart'; - -class EditCustomRules extends StatefulWidget { - final bool fullScreen; - final void Function(List) onConfirm; - - const EditCustomRules({ - super.key, - required this.fullScreen, - required this.onConfirm, - }); - - @override - State createState() => _EditCustomRulesState(); -} - -class _EditCustomRulesState extends State { - final _fieldController = TextEditingController(); - - @override - void initState() { - final filteringProvider = Provider.of(context, listen: false); - if (filteringProvider.filtering != null) { - _fieldController.text = filteringProvider.filtering!.userRules.join("\n"); - } - - super.initState(); - } - - @override - Widget build(BuildContext context) { - if (widget.fullScreen == true) { - return Dialog.fullscreen( - child: Material( - child: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - leading: CloseButton(onPressed: () => Navigator.pop(context)), - title: Text(AppLocalizations.of(context)!.editCustomRules), - actions: [ - IconButton( - onPressed: () { - Navigator.pop(context); - widget.onConfirm(_fieldController.text.split("\n")); - }, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 10) - ], - ) - ) - ], - body: SafeArea( - top: false, - bottom: true, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverList.list( - children: [ - _CustomRulesRawEditor(fieldController: _fieldController) - ] - ) - ], - ), - ) - ) - ), - ), - ); - } - else { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.clear_rounded), - tooltip: AppLocalizations.of(context)!.close, - ), - const SizedBox(width: 8), - Text( - AppLocalizations.of(context)!.editCustomRules, - style: const TextStyle( - fontSize: 22 - ), - ), - ], - ), - IconButton( - onPressed: () { - Navigator.pop(context); - widget.onConfirm(_fieldController.text.split("\n")); - }, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ) - ], - ), - ), - Flexible( - child: SingleChildScrollView( - child: Wrap( - alignment: WrapAlignment.center, - children: [ - _CustomRulesRawEditor(fieldController: _fieldController) - ] - ), - ), - ) - ], - ), - ), - ); - } - } -} - -class _CustomRulesRawEditor extends StatelessWidget { - final TextEditingController fieldController; - - const _CustomRulesRawEditor({ - required this.fieldController - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - const SizedBox(height: 24), - SizedBox( - height: 300, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: TextField( - controller: fieldController, - decoration: InputDecoration( - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.rules, - floatingLabelBehavior: FloatingLabelBehavior.always - ), - autocorrect: false, - expands: true, - minLines: null, - maxLines: null, - textAlignVertical: TextAlignVertical.top, - ), - ), - ), - const SizedBox(height: 24), - const CustomRuleDocs(), - const SizedBox(height: 16), - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/filters/modals/custom_rules/sort_rules.dart b/lib/screens/filters/modals/custom_rules/sort_rules.dart deleted file mode 100644 index 7ae0870..0000000 --- a/lib/screens/filters/modals/custom_rules/sort_rules.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/constants/enums.dart'; - -class SortCustomRulesModal extends StatelessWidget { - final CustomRulesSorting sortingMethod; - final void Function(CustomRulesSorting) onSelect; - - const SortCustomRulesModal({ - super.key, - required this.sortingMethod, - required this.onSelect, - }); - - @override - Widget build(BuildContext context) { - return AlertDialog( - contentPadding: const EdgeInsets.symmetric(vertical: 16), - scrollable: true, - title: Column( - children: [ - Icon( - Icons.sort_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.sortingOptions, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - content: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: Column( - children: [ - _CustomListTileDialog( - title: AppLocalizations.of(context)!.topToBottom, - icon: Icons.arrow_downward_rounded, - onTap: () { - Navigator.pop(context); - onSelect(CustomRulesSorting.topBottom); - }, - isSelected: sortingMethod == CustomRulesSorting.topBottom - ), - _CustomListTileDialog( - title: AppLocalizations.of(context)!.bottomToTop, - icon: Icons.arrow_upward_rounded, - onTap: () { - Navigator.pop(context); - onSelect(CustomRulesSorting.bottomTop); - }, - isSelected: sortingMethod == CustomRulesSorting.bottomTop - ), - ] - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close) - ) - ], - ) - ], - ); - } -} - -class _CustomListTileDialog extends StatelessWidget { - final String title; - final IconData? icon; - final void Function()? onTap; - final bool isSelected; - - const _CustomListTileDialog({ - required this.title, - required this.icon, - required this.onTap, - required this.isSelected, - }); - - @override - Widget build(BuildContext context) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - child: Row( - children: [ - Icon( - icon, - color: Theme.of(context).colorScheme.onSurface, - ), - const SizedBox(width: 24), - Flexible( - child: Text( - title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ), - const SizedBox(width: 24), - Icon( - isSelected == true ? Icons.radio_button_checked_rounded : Icons.radio_button_unchecked_rounded, - color: Theme.of(context).colorScheme.primary, - ) - ], - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/filters/modals/delete_list_modal.dart b/lib/screens/filters/modals/delete_list_modal.dart index 86e7e88..5d751e4 100644 --- a/lib/screens/filters/modals/delete_list_modal.dart +++ b/lib/screens/filters/modals/delete_list_modal.dart @@ -5,9 +5,9 @@ class DeleteListModal extends StatelessWidget { final void Function() onConfirm; const DeleteListModal({ - super.key, + Key? key, required this.onConfirm - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/filters/modals/remove_custom_rule_modal.dart b/lib/screens/filters/modals/remove_custom_rule_modal.dart index ec281f7..f1c4352 100644 --- a/lib/screens/filters/modals/remove_custom_rule_modal.dart +++ b/lib/screens/filters/modals/remove_custom_rule_modal.dart @@ -5,9 +5,9 @@ class RemoveCustomRule extends StatelessWidget { final void Function() onConfirm; const RemoveCustomRule({ - super.key, + Key? key, required this.onConfirm - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/filters/modals/update_interval_lists_modal.dart b/lib/screens/filters/modals/update_interval_lists_modal.dart index ce7058b..4827082 100644 --- a/lib/screens/filters/modals/update_interval_lists_modal.dart +++ b/lib/screens/filters/modals/update_interval_lists_modal.dart @@ -66,12 +66,10 @@ class _UpdateIntervalListsModalState extends State { topRight: Radius.circular(28) ), ), - child: SafeArea( - child: _Content( - selectedOption: selectedOption, - onUpdateValue: _updateRadioValue, - onConfirm: () => widget.onChange(selectedOption!), - ), + child: _Content( + selectedOption: selectedOption, + onUpdateValue: _updateRadioValue, + onConfirm: () => widget.onChange(selectedOption!), ) ), ); @@ -235,10 +233,10 @@ class _Content extends StatelessWidget { } : null, style: ButtonStyle( - overlayColor: WidgetStateProperty.all( + overlayColor: MaterialStateProperty.all( Theme.of(context).colorScheme.primary.withOpacity(0.1) ), - foregroundColor: WidgetStateProperty.all( + foregroundColor: MaterialStateProperty.all( selectedOption != null ? Theme.of(context).colorScheme.primary : Colors.grey, diff --git a/lib/screens/filters/selection/delete_selection_modal.dart b/lib/screens/filters/selection/delete_selection_modal.dart index ecf77e5..944d515 100644 --- a/lib/screens/filters/selection/delete_selection_modal.dart +++ b/lib/screens/filters/selection/delete_selection_modal.dart @@ -9,11 +9,11 @@ class DeleteSelectionModal extends StatefulWidget { final void Function() onDelete; const DeleteSelectionModal({ - super.key, + Key? key, required this.selectedBlacklists, required this.selectedWhitelists, required this.onDelete, - }); + }) : super(key: key); @override State createState() => _DeleteSelectionModalState(); diff --git a/lib/screens/filters/selection/enable_disable_selection_modal.dart b/lib/screens/filters/selection/enable_disable_selection_modal.dart index d14fe7c..fdb809c 100644 --- a/lib/screens/filters/selection/enable_disable_selection_modal.dart +++ b/lib/screens/filters/selection/enable_disable_selection_modal.dart @@ -9,11 +9,11 @@ class EnableDisableSelectionModal extends StatefulWidget { final void Function() onDelete; const EnableDisableSelectionModal({ - super.key, + Key? key, required this.selectedBlacklists, required this.selectedWhitelists, required this.onDelete, - }); + }) : super(key: key); @override State createState() => _EnableDisableSelectionModalState(); diff --git a/lib/screens/filters/selection/selection_lists.dart b/lib/screens/filters/selection/selection_lists.dart index a7988e9..9562e74 100644 --- a/lib/screens/filters/selection/selection_lists.dart +++ b/lib/screens/filters/selection/selection_lists.dart @@ -13,13 +13,13 @@ class SelectionList extends StatelessWidget { final void Function() unselectAll; const SelectionList({ - super.key, + Key? key, required this.lists, required this.selectedLists, required this.onSelect, required this.selectAll, required this.unselectAll, - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -72,18 +72,19 @@ class SelectionSliverList extends StatelessWidget { final void Function() unselectAll; const SelectionSliverList({ - super.key, + Key? key, required this.lists, required this.selectedLists, required this.onSelect, required this.selectAll, required this.unselectAll, - }); + }) : super(key: key); @override Widget build(BuildContext context) { return SafeArea( top: false, + bottom: false, child: Builder( builder: (BuildContext context) { return CustomScrollView( @@ -231,10 +232,11 @@ class _CheckboxTile extends StatelessWidget { final bool isSelected; const _CheckboxTile({ + Key? key, required this.list, required this.onSelect, required this.isSelected, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index 5907012..1475454 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -82,8 +82,8 @@ class _SelectionScreenState extends State with TickerProviderSt blacklists: _selectedBlacklists, whitelists: _selectedWhitelists ); + if (!mounted) return; processModal.close(); - if (!context.mounted) return; showDialog( context: context, builder: (ctx) => SelectionResultModal( @@ -113,8 +113,8 @@ class _SelectionScreenState extends State with TickerProviderSt blacklists: _selectedBlacklists, whitelists: _selectedWhitelists ); + if (!mounted) return; processModal.close(); - if (!context.mounted) return; showDialog( context: context, builder: (ctx) => SelectionResultModal( diff --git a/lib/screens/home/appbar.dart b/lib/screens/home/appbar.dart index d4b447c..365e606 100644 --- a/lib/screens/home/appbar.dart +++ b/lib/screens/home/appbar.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -18,9 +16,9 @@ class HomeAppBar extends StatelessWidget { final bool innerBoxScrolled; const HomeAppBar({ - super.key, + Key? key, required this.innerBoxScrolled - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -34,7 +32,6 @@ class HomeAppBar extends StatelessWidget { void navigateServers() { Future.delayed(const Duration(milliseconds: 0), (() { - if (!context.mounted) return; Navigator.of(context).push( MaterialPageRoute(builder: (context) => const Servers()) ); @@ -121,14 +118,6 @@ class HomeAppBar extends StatelessWidget { ], ), actions: [ - if (!(Platform.isAndroid || Platform.isIOS)) ...[ - IconButton( - onPressed: () => statusProvider.getServerStatus(), - icon: const Icon(Icons.refresh_rounded), - tooltip: AppLocalizations.of(context)!.refresh, - ), - const SizedBox(width: 8), - ], PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( @@ -152,8 +141,7 @@ class HomeAppBar extends StatelessWidget { ), ) ] - ), - const SizedBox(width: 8), + ) ], ); } diff --git a/lib/screens/home/chart.dart b/lib/screens/home/chart.dart index ac1fce5..ba97f79 100644 --- a/lib/screens/home/chart.dart +++ b/lib/screens/home/chart.dart @@ -1,20 +1,17 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/line_chart.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -class HomeChart extends StatefulWidget { +class HomeChart extends StatelessWidget { final List data; final String label; final String primaryValue; final String secondaryValue; final Color color; final int hoursInterval; - final void Function() onTapTitle; - final bool isDesktop; const HomeChart({ super.key, @@ -23,29 +20,20 @@ class HomeChart extends StatefulWidget { required this.primaryValue, required this.secondaryValue, required this.color, - required this.hoursInterval, - required this.onTapTitle, - required this.isDesktop, + required this.hoursInterval }); - @override - State createState() => _HomeChartState(); -} - -class _HomeChartState extends State { - bool _isHover = false; - @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - final bool isEmpty = widget.data.every((i) => i == 0); + final bool isEmpty = data.every((i) => i == 0); if (!(appConfigProvider.hideZeroValues == true && isEmpty == true)) { List dateTimes = []; - DateTime currentDate = DateTime.now().subtract(Duration(hours: widget.hoursInterval*widget.data.length)); - for (var i = 0; i < widget.data.length; i++) { - currentDate = currentDate.add(Duration(hours: widget.hoursInterval)); + DateTime currentDate = DateTime.now().subtract(Duration(hours: hoursInterval*data.length+1)); + for (var i = 0; i < data.length; i++) { + currentDate = currentDate.add(Duration(hours: hoursInterval)); dateTimes.add(currentDate); } @@ -61,87 +49,60 @@ class _HomeChartState extends State { ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: isEmpty - ? CrossAxisAlignment.center - : CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( - child: MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) => setState(() => _isHover = true), - onExit: (_) => setState(() => _isHover = false), - child: GestureDetector( - onTapDown: (_) => setState(() => _isHover = true), - onTapUp: (_) => setState(() => _isHover = false), - onTap: !isEmpty ? () => widget.onTapTitle () : null, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Text( - widget.label, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: _isHover && !isEmpty - ? Theme.of(context).colorScheme.onSurfaceVariant - : Theme.of(context).colorScheme.onSurface, - ), - ), - ), - if (!isEmpty) ...[ - const SizedBox(width: 4), - Icon( - Icons.chevron_right_rounded, - size: 20, - color: _isHover && !isEmpty - ? Theme.of(context).colorScheme.onSurfaceVariant - : Theme.of(context).colorScheme.onSurface, - ) - ], - ], - ), + child: Text( + label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface ), ), ), - if (!isEmpty) Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - widget.primaryValue, - style: TextStyle( - color: widget.color, - fontSize: 18, - fontWeight: FontWeight.w500 - ), - ), - Text( - widget.secondaryValue, - style: TextStyle( - fontSize: 12, - color: widget.color - ), + !isEmpty + ? Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + primaryValue, + style: TextStyle( + color: color, + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + Text( + secondaryValue, + style: TextStyle( + fontSize: 12, + color: color + ), + ) + ], ) - ], - ), - if (isEmpty && !widget.isDesktop) Column( - children: [ - Icon( - Icons.show_chart_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 16, - ), - Text( - AppLocalizations.of(context)!.noData, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + : Row( + children: [ + Text( + primaryValue, + style: TextStyle( + color: color, + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + const SizedBox(width: 10), + Text( + "($secondaryValue)", + style: TextStyle( + fontSize: 12, + color: color + ), + ) + ], ) - ], - ) ], ), ), @@ -149,35 +110,13 @@ class _HomeChartState extends State { width: double.maxFinite, height: 150, child: CustomLineChart( - data: widget.data, - color: widget.color, + data: data, + color: color, dates: dateTimes, - daysInterval: widget.hoursInterval == 24, + daysInterval: hoursInterval == 24, context: context, ) ), - if (isEmpty && widget.isDesktop) SizedBox( - height: 150, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.show_chart_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 30, - ), - const SizedBox(height: 20), - Text( - AppLocalizations.of(context)!.noDataChart, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ) - ], - ), - ) ], ), ), diff --git a/lib/screens/home/combined_chart.dart b/lib/screens/home/combined_chart.dart index f951cea..89519db 100644 --- a/lib/screens/home/combined_chart.dart +++ b/lib/screens/home/combined_chart.dart @@ -37,7 +37,7 @@ class CombinedChartItem { } class CombinedHomeChart extends StatelessWidget { - const CombinedHomeChart({super.key}); + const CombinedHomeChart({Key? key}) : super(key: key); List? removeZero(List list) { final removed = list.where((i) => i > 0); @@ -266,11 +266,12 @@ class _Legend extends StatelessWidget { final String? secondaryValue; const _Legend({ + Key? key, required this.label, required this.color, required this.primaryValue, this.secondaryValue - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/home/fab.dart b/lib/screens/home/fab.dart index 7e465be..bd77be7 100644 --- a/lib/screens/home/fab.dart +++ b/lib/screens/home/fab.dart @@ -7,7 +7,7 @@ import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/constants/enums.dart'; class HomeFab extends StatelessWidget { - const HomeFab({super.key}); + const HomeFab({Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 7a60115..9e5de00 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -14,8 +14,6 @@ import 'package:adguard_home_manager/screens/home/appbar.dart'; import 'package:adguard_home_manager/screens/home/fab.dart'; import 'package:adguard_home_manager/screens/home/chart.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; @@ -40,9 +38,6 @@ class _HomeState extends State { withLoadingIndicator: statusProvider.serverStatus != null ? false : true ); - final clientsProvider = Provider.of(context, listen: false); - clientsProvider.fetchClients(updateLoading: false); - super.initState(); isVisible = true; @@ -66,7 +61,6 @@ class _HomeState extends State { Widget build(BuildContext context) { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; @@ -93,7 +87,7 @@ class _HomeState extends State { onRefresh: () async { final result = await statusProvider.getServerStatus(); if (mounted && result == false) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.serverStatusNotRefreshed, color: Colors.red @@ -149,15 +143,6 @@ class _HomeState extends State { secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms", color: Colors.blue, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, - onTapTitle: () { - logsProvider.setSelectedResultStatus( - value: "all", - refetch: true - ); - logsProvider.filterLogs(); - appConfigProvider.setSelectedScreen(2); - }, - isDesktop: width > 700, ), ), FractionallySizedBox( @@ -169,14 +154,6 @@ class _HomeState extends State { secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", color: Colors.red, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, - onTapTitle: () { - logsProvider.setSelectedResultStatus( - value: "blocked", - refetch: true - ); - appConfigProvider.setSelectedScreen(2); - }, - isDesktop: width > 700, ), ), FractionallySizedBox( @@ -188,14 +165,6 @@ class _HomeState extends State { secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", color: Colors.green, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, - onTapTitle: () { - logsProvider.setSelectedResultStatus( - value: "blocked_safebrowsing", - refetch: true - ); - appConfigProvider.setSelectedScreen(2); - }, - isDesktop: width > 700, ), ), FractionallySizedBox( @@ -207,15 +176,6 @@ class _HomeState extends State { secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedParental/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", color: Colors.orange, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, - onTapTitle: () { - logsProvider.setSelectedResultStatus( - value: "blocked_parental", - refetch: true - ); - logsProvider.filterLogs(); - appConfigProvider.setSelectedScreen(2); - }, - isDesktop: width > 700, ), ), ], @@ -227,8 +187,6 @@ class _HomeState extends State { ), TopItemsLists(order: appConfigProvider.homeTopItemsOrder), - - const SizedBox(height: 16), ], ), if (statusProvider.loadStatus == LoadStatus.error) SliverFillRemaining( diff --git a/lib/screens/home/management_modal/management_modal.dart b/lib/screens/home/management_modal/management_modal.dart index 5a931ad..79fe043 100644 --- a/lib/screens/home/management_modal/management_modal.dart +++ b/lib/screens/home/management_modal/management_modal.dart @@ -19,9 +19,9 @@ class ManagementModal extends StatefulWidget { final bool dialog; const ManagementModal({ - super.key, + Key? key, required this.dialog - }); + }) : super(key: key); @override State createState() => _ManagementModalState(); @@ -82,7 +82,7 @@ class _ManagementModalState extends State with SingleTickerProv time: time ); if (mounted && result == false) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.invalidUsernamePassword, color: Colors.red @@ -141,35 +141,33 @@ class _ManagementModalState extends State with SingleTickerProv topRight: Radius.circular(28) ) ), - child: SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: _Modal( - expandableController: expandableController, - updateBlocking: updateBlocking, - disableWithCountdown: disableWithCountdown, - animation: animation, - ) - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: _Modal( + expandableController: expandableController, + updateBlocking: updateBlocking, + disableWithCountdown: disableWithCountdown, + animation: animation, + ) ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close), - ), - ], - ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close), + ), + ], ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ), + ), + if (Platform.isIOS) const SizedBox(height: 16) + ], ), ); } @@ -203,24 +201,24 @@ class _Modal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.shield_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text( - AppLocalizations.of(context)!.manageServer, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface, - ), + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.shield_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Text( + AppLocalizations.of(context)!.manageServer, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface, ), ), + ), ], ), ], diff --git a/lib/screens/home/management_modal/small_switch.dart b/lib/screens/home/management_modal/small_switch.dart index 192360c..4e34d68 100644 --- a/lib/screens/home/management_modal/small_switch.dart +++ b/lib/screens/home/management_modal/small_switch.dart @@ -8,13 +8,13 @@ class SmallSwitch extends StatelessWidget { final bool disabled; const SmallSwitch({ - super.key, + Key? key, required this.label, required this.icon, required this.value, required this.onChange, required this.disabled, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart index bfa43e7..569d30c 100644 --- a/lib/screens/home/top_items/row_item.dart +++ b/lib/screens/home/top_items/row_item.dart @@ -8,7 +8,7 @@ import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -class RowItem extends StatelessWidget { +class RowItem extends StatefulWidget { final HomeTopItems type; final Color chartColor; final String domain; @@ -16,7 +16,7 @@ class RowItem extends StatelessWidget { final bool clients; final bool showColor; final String? unit; - final List Function(dynamic) options; + final List options; final void Function(dynamic)? onTapEntry; const RowItem({ @@ -32,14 +32,61 @@ class RowItem extends StatelessWidget { this.unit, }); + @override + State createState() => _RowItemState(); +} + +class _RowItemState extends State with TickerProviderStateMixin { + late AnimationController expandController; + late Animation animation; + + @override + void initState() { + super.initState(); + prepareAnimations(); + _runExpandCheck(); + } + + void prepareAnimations() { + expandController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 250) + ); + animation = CurvedAnimation( + parent: expandController, + curve: Curves.ease, + ); + } + + void _runExpandCheck() { + if (widget.showColor) { + expandController.forward(); + } + else { + expandController.reverse(); + } + } + + @override + void didUpdateWidget(oldWidget) { + super.didUpdateWidget(oldWidget); + _runExpandCheck(); + } + + @override + void dispose() { + expandController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final statusProvider = Provider.of(context); String? name; - if (clients == true) { + if (widget.clients == true) { try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(domain)).name; + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(widget.domain)).name; } catch (e) { // ---- // } @@ -48,9 +95,9 @@ class RowItem extends StatelessWidget { return Material( color: Colors.transparent, child: OptionsMenu( - value: domain, - options: options, - onTap: onTapEntry, + value: widget.domain, + options: widget.options, + onTap: widget.onTapEntry, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20, @@ -62,13 +109,18 @@ class RowItem extends StatelessWidget { Flexible( child: Row( children: [ - if (showColor == true) Container( - margin: const EdgeInsets.only(right: 16), - width: 12, - height: 12, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: chartColor + SizeTransition( + axisAlignment: 1.0, + sizeFactor: animation, + axis: Axis.horizontal, + child: Container( + margin: const EdgeInsets.only(right: 16), + width: 12, + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: widget.chartColor + ), ), ), Expanded( @@ -76,7 +128,7 @@ class RowItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - domain, + widget.domain, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 16, @@ -102,7 +154,7 @@ class RowItem extends StatelessWidget { ), const SizedBox(width: 16), Text( - number, + widget.number, style: TextStyle( color: Theme.of(context).colorScheme.onSurface ), @@ -115,7 +167,7 @@ class RowItem extends StatelessWidget { } } -class OthersRowItem extends StatelessWidget { +class OthersRowItem extends StatefulWidget { final List> items; final bool showColor; @@ -125,60 +177,111 @@ class OthersRowItem extends StatelessWidget { required this.showColor, }); + @override + State createState() => _OthersRowItemState(); +} + +class _OthersRowItemState extends State with SingleTickerProviderStateMixin { + late AnimationController expandController; + late Animation animation; + + @override + void initState() { + super.initState(); + prepareAnimations(); + _runExpandCheck(); + } + + void prepareAnimations() { + expandController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 250) + ); + animation = CurvedAnimation( + parent: expandController, + curve: Curves.ease, + ); + } + + void _runExpandCheck() { + if (widget.showColor) { + expandController.forward(); + } + else { + expandController.reverse(); + } + } + + @override + void didUpdateWidget(oldWidget) { + super.didUpdateWidget(oldWidget); + _runExpandCheck(); + } + + @override + void dispose() { + expandController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - if (items.length <= 5) { + if (widget.items.length <= 5) { return const SizedBox(); } - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Row( - children: [ - if (showColor == true) Container( - margin: const EdgeInsets.only(right: 16), - width: 12, - height: 12, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Colors.grey + return SizeTransition( + axisAlignment: 1.0, + sizeFactor: animation, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(right: 16), + width: 12, + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.grey + ), ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.others, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.others, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), - ), - const SizedBox(width: 16), - Text( - List.from( - items.sublist(5, items.length).map((e) => e.values.first.toInt()) - ).reduce((a, b) => a + b).toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], + const SizedBox(width: 16), + Text( + List.from( + widget.items.sublist(5, widget.items.length).map((e) => e.values.first.toInt()) + ).reduce((a, b) => a + b).toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), ), ); } diff --git a/lib/screens/home/top_items/top_items_lists.dart b/lib/screens/home/top_items/top_items_lists.dart index 3ca6e24..7f230fb 100644 --- a/lib/screens/home/top_items/top_items_lists.dart +++ b/lib/screens/home/top_items/top_items_lists.dart @@ -6,7 +6,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/home/top_items/top_items_section.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -31,7 +30,6 @@ class TopItemsLists extends StatelessWidget { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); - final clientsProvider = Provider.of(context); List bottom = [ Padding( @@ -51,7 +49,7 @@ class TopItemsLists extends StatelessWidget { AppliedFiters( selectedResultStatus: 'all', searchText: value, - clients: [] + clients: null ) ); appConfigProvider.setSelectedScreen(2); @@ -83,14 +81,14 @@ class TopItemsLists extends StatelessWidget { if (!context.mounted) return; if (rules == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.userFilteringRulesUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated, color: Colors.red @@ -98,53 +96,10 @@ class TopItemsLists extends StatelessWidget { } } - void copyValueClipboard(dynamic value) { + void copyValueClipboard(value) { copyToClipboard(value: value, successMessage: AppLocalizations.of(context)!.copiedClipboard); } - void blockUnblockClient(dynamic client) async { - final currentList = clientsProvider.checkClientList(client); - final newList = currentList == AccessSettingsList.allowed || currentList == null - ? AccessSettingsList.disallowed - : AccessSettingsList.allowed; - - ProcessModal processModal = ProcessModal(); - processModal.open( - currentList == AccessSettingsList.allowed || currentList == null - ? AppLocalizations.of(context)!.blockingClient - : AppLocalizations.of(context)!.unblockingClient - ); - - final result = await clientsProvider.addClientList(client, newList); - if (!context.mounted) return; - - processModal.close(); - - if (result.successful == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.clientAddedSuccessfully, - color: Colors.green - ); - } - else if (result.successful == false && result.content == 'client_another_list') { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.clientAnotherList, - color: Colors.red - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: newList == AccessSettingsList.allowed || newList == AccessSettingsList.disallowed - ? AppLocalizations.of(context)!.clientNotRemoved - : AppLocalizations.of(context)!.domainNotAdded, - color: Colors.red - ); - } - } - return Column( children: order.asMap().entries.map((item) { switch (item.value) { @@ -158,16 +113,16 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: (v) => [ + menuOptions: [ MenuOption( title: AppLocalizations.of(context)!.blockDomain, icon: Icons.block_rounded, - action: () => blockUnblock(domain: v.toString(), newStatus: 'block') + action: (v) => blockUnblock(domain: v.toString(), newStatus: 'block') ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyValueClipboard(v) + action: copyValueClipboard ), ], onTapEntry: (v) => filterDomainLogs(value: v.toString()), @@ -186,16 +141,16 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: (v) => [ + menuOptions: [ MenuOption( title: AppLocalizations.of(context)!.unblockDomain, icon: Icons.check_rounded, - action: () => blockUnblock(domain: v, newStatus: 'unblock') + action: (v) => blockUnblock(domain: v, newStatus: 'unblock') ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyValueClipboard(v) + action: copyValueClipboard ) ], onTapEntry: (v) => filterDomainLogs(value: v), @@ -214,21 +169,12 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: (v) => [ - if (clientsProvider.clients?.clientsAllowedBlocked != null) MenuOption( - title: clientsProvider.checkClientList(v) == AccessSettingsList.allowed || clientsProvider.checkClientList(v) == null - ? AppLocalizations.of(context)!.blockClient - : AppLocalizations.of(context)!.unblockClient, - icon: clientsProvider.checkClientList(v) == AccessSettingsList.allowed || clientsProvider.checkClientList(v) == null - ? Icons.block_rounded - : Icons.check_rounded, - action: () => blockUnblockClient(v) - ), + menuOptions: [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyValueClipboard(v) - ), + action: copyValueClipboard + ) ], onTapEntry: (v) => filterClientLogs(value: v), ), @@ -247,11 +193,11 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: (v) => [ + menuOptions: [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyValueClipboard(v) + action: copyValueClipboard ) ], ), @@ -271,11 +217,11 @@ class TopItemsLists extends StatelessWidget { withChart: false, withProgressBar: false, buildValue: (v) => "${doubleFormat(v*1000, Platform.localeName)} ms", - menuOptions: (v) => [ + menuOptions: [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyValueClipboard(v) + action: copyValueClipboard ) ], ), diff --git a/lib/screens/home/top_items/top_items_screen.dart b/lib/screens/home/top_items/top_items_screen.dart index bf0a0f5..01ac83e 100644 --- a/lib/screens/home/top_items/top_items_screen.dart +++ b/lib/screens/home/top_items/top_items_screen.dart @@ -9,16 +9,12 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/options_menu.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; -import 'package:adguard_home_manager/widgets/floating_search_bar.dart'; import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -enum _SortingOptions { highestToLowest, lowestToHighest } -final GlobalKey _searchButtonKey = GlobalKey(); - class TopItemsScreen extends StatefulWidget { final HomeTopItems type; final String title; @@ -26,7 +22,7 @@ class TopItemsScreen extends StatefulWidget { final List> data; final bool withProgressBar; final String Function(dynamic) buildValue; - final List Function(dynamic) options; + final List options; final void Function(dynamic)? onTapEntry; final bool isFullscreen; @@ -48,19 +44,15 @@ class TopItemsScreen extends StatefulWidget { } class _TopItemsScreenState extends State { - _SortingOptions _sortingOptions = _SortingOptions.highestToLowest; + bool searchActive = false; final TextEditingController searchController = TextEditingController(); - String? _currentSearchValue = ""; List> data = []; List> screenData = []; void search(String value) { List> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList(); - setState(() { - screenData = newValues; - _currentSearchValue = searchController.text; - }); + setState(() => screenData = newValues); } @override @@ -76,136 +68,69 @@ class _TopItemsScreenState extends State { for (var element in data) { total = total + double.parse(element.values.toList()[0].toString()); } - - final sortedValues = _sortingOptions == _SortingOptions.lowestToHighest - ? screenData.reversed.toList() - : screenData.toList(); - - void showSearchDialog() { - showDialog( - context: context, - builder: (context) => FloatingSearchBar( - existingSearchValue: _currentSearchValue, - searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?, - onSearchCompleted: (v) { - List> newValues = widget.data.where((item) => item.keys.toList()[0].contains(v)).toList(); - setState(() { - screenData = newValues; - _currentSearchValue = v; - }); - }, - ), - ); - } if (widget.isFullscreen == true) { - return Scaffold( - body: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - title: Text(widget.title), - actions: [ - IconButton( - key: _searchButtonKey, - onPressed: showSearchDialog, - icon: const Icon(Icons.search_rounded), - tooltip: AppLocalizations.of(context)!.search, - ), - PopupMenuButton( - icon: const Icon(Icons.sort_rounded), - itemBuilder: (context) => [ - PopupMenuItem( - onTap: () => setState(() => _sortingOptions = _SortingOptions.highestToLowest), - child: Row( - children: [ - const Icon(Icons.arrow_downward_rounded), - const SizedBox(width: 8), - Expanded( - child: Text(AppLocalizations.of(context)!.fromHighestToLowest) - ), - const SizedBox(width: 16), - Icon( - _sortingOptions == _SortingOptions.highestToLowest - ? Icons.radio_button_checked_rounded - : Icons.radio_button_unchecked_rounded, - color: _sortingOptions == _SortingOptions.highestToLowest - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurfaceVariant, - ) - ], - ) - ), - PopupMenuItem( - onTap: () => setState(() => _sortingOptions = _SortingOptions.lowestToHighest), - child: Row( - children: [ - const Icon(Icons.arrow_upward_rounded), - const SizedBox(width: 8), - Expanded( - child: Text(AppLocalizations.of(context)!.fromLowestToHighest) - ), - const SizedBox(width: 16), - Icon( - _sortingOptions == _SortingOptions.lowestToHighest - ? Icons.radio_button_checked_rounded - : Icons.radio_button_unchecked_rounded, - color: _sortingOptions == _SortingOptions.lowestToHighest - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurfaceVariant, - ) - ], - ) - ), - ], - ), - const SizedBox(width: 8) - ], - ) - ) - ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (sortedValues.isEmpty) Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.noItemsSearch, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + title: searchActive == true + ? Padding( + padding: const EdgeInsets.only(bottom: 3), + child: TextFormField( + controller: searchController, + onChanged: search, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)!.search, + hintStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 18 ), + border: InputBorder.none, ), - ), - if (sortedValues.isNotEmpty) SliverPadding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewPadding.bottom), - sliver: SliverList.builder( - itemCount: sortedValues.length, - itemBuilder: (context, index) => _Item( - data: sortedValues[index], - isClient: widget.isClient, - options: widget.options, - total: total, - withProgressBar: widget.withProgressBar, - onTapEntry: widget.onTapEntry, - buildValue: widget.buildValue, - ), + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 18 ), + autofocus: true, ), - ], + ) + : Text(widget.title), + leading: searchActive == true ? + IconButton( + onPressed: () => setState(() { + searchActive = false; + searchController.text = ''; + screenData = data; + }), + icon: const Icon(Icons.arrow_back), + tooltip: AppLocalizations.of(context)!.exitSearch, + ) : null, + actions: [ + if (searchActive == false) IconButton( + onPressed: () => setState(() => searchActive = true), + icon: const Icon(Icons.search), + tooltip: AppLocalizations.of(context)!.search, ), - ) - ) + if (searchActive == true) IconButton( + onPressed: () => setState(() { + searchController.text = ''; + screenData = data; + }), + icon: const Icon(Icons.clear_rounded), + tooltip: AppLocalizations.of(context)!.clearSearch, + ), + const SizedBox(width: 8) + ], + ), + body: _Content( + buildValue: widget.buildValue, + isClient: widget.isClient, + onTapEntry: widget.onTapEntry, + options: widget.options, + screenData: screenData, + total: total, + withProgressBar: widget.withProgressBar, + ), ), ); } @@ -261,29 +186,14 @@ class _TopItemsScreenState extends State { ), ), Expanded( - child: sortedValues.isNotEmpty ? ListView.builder( - itemCount: sortedValues.length, - itemBuilder: (context, index) => _Item( - data: sortedValues[index], - isClient: widget.isClient, - options: widget.options, - withProgressBar: widget.withProgressBar, - onTapEntry: widget.onTapEntry, - buildValue: widget.buildValue, - total: total, - ), - ) : Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.noItemsSearch, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), + child: _Content( + buildValue: widget.buildValue, + isClient: widget.isClient, + onTapEntry: widget.onTapEntry, + options: widget.options, + screenData: screenData, + total: total, + withProgressBar: widget.withProgressBar, ), ), ], @@ -294,17 +204,17 @@ class _TopItemsScreenState extends State { } } -class _Item extends StatelessWidget { - final dynamic data; +class _Content extends StatelessWidget { + final List> screenData; final bool? isClient; - final List Function(dynamic) options; + final List options; final bool withProgressBar; final void Function(dynamic)? onTapEntry; final String Function(dynamic) buildValue; final double total; - const _Item({ - required this.data, + const _Content({ + required this.screenData, required this.isClient, required this.options, required this.withProgressBar, @@ -317,75 +227,98 @@ class _Item extends StatelessWidget { Widget build(BuildContext context) { final statusProvider = Provider.of(context); - String? name; - if (isClient != null && isClient == true) { - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(data.keys.toList()[0])).name; - } catch (e) { - // ---- // - } - } - - return OptionsMenu( - options: options, - value: data.keys.toList()[0], - onTap: onTapEntry != null - ? (v) { - onTapEntry!(v); - Navigator.pop(context); + if (screenData.isNotEmpty) { + return ListView.builder( + padding: const EdgeInsets.only(top: 0), + itemCount: screenData.length, + itemBuilder: (context, index) { + String? name; + if (isClient != null && isClient == true) { + try { + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(screenData[index].keys.toList()[0])).name; + } catch (e) { + // ---- // + } } - : null, - child: CustomListTile( - title: data.keys.toList()[0], - trailing: Text( - buildValue(data.values.toList()[0]), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - subtitleWidget: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (name != null) ...[ - Text( - name, + + return OptionsMenu( + options: options, + value: screenData[index].keys.toList()[0], + onTap: onTapEntry != null + ? (v) { + onTapEntry!(v); + Navigator.pop(context); + } + : null, + child: CustomListTile( + title: screenData[index].keys.toList()[0], + trailing: Text( + buildValue(screenData[index].values.toList()[0]), style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurface + color: Theme.of(context).colorScheme.onSurfaceVariant ), ), - const SizedBox(height: 5), - ], - if (withProgressBar == true) Row( - children: [ - SizedBox( - width: 50, - child: Text( - "${doubleFormat((data.values.toList()[0]/total*100), Platform.localeName)}%", - style: TextStyle( - color: Theme.of(context).listTileTheme.textColor + subtitleWidget: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (name != null) ...[ + Text( + name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurface + ), ), + const SizedBox(height: 5), + ], + if (withProgressBar == true) Row( + children: [ + SizedBox( + width: 50, + child: Text( + "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", + style: TextStyle( + color: Theme.of(context).listTileTheme.textColor + ), + ), + ), + const SizedBox(width: 10), + Flexible( + child: LinearPercentIndicator( + animation: true, + lineHeight: 4, + animationDuration: 500, + curve: Curves.easeOut, + percent: screenData[index].values.toList()[0]/total, + barRadius: const Radius.circular(5), + progressColor: Theme.of(context).colorScheme.primary, + backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + ), + ), + const SizedBox(width: 10), + ], ), - ), - const SizedBox(width: 10), - Flexible( - child: LinearPercentIndicator( - animation: true, - lineHeight: 4, - animationDuration: 500, - curve: Curves.easeOut, - percent: data.values.toList()[0]/total, - barRadius: const Radius.circular(5), - progressColor: Theme.of(context).colorScheme.primary, - backgroundColor: Theme.of(context).colorScheme.surfaceTint.withOpacity(0.2), - ), - ), - const SizedBox(width: 10), - ], + ], + ) ), - ], - ) - ), - ); + ); + } + ); + } + else { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.noItemsSearch, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ); + } } } \ No newline at end of file diff --git a/lib/screens/home/top_items/top_items_section.dart b/lib/screens/home/top_items/top_items_section.dart index 708a3d0..f084b3f 100644 --- a/lib/screens/home/top_items/top_items_section.dart +++ b/lib/screens/home/top_items/top_items_section.dart @@ -3,24 +3,25 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/home/top_items/row_item.dart'; import 'package:adguard_home_manager/screens/home/top_items/top_items_screen.dart'; import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; -import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; -class TopItemsSection extends StatelessWidget { +class TopItemsSection extends StatefulWidget { final HomeTopItems type; final String label; final List> data; final bool withChart; final bool withProgressBar; final String Function(dynamic) buildValue; - final List Function(dynamic) menuOptions; + final List menuOptions; final void Function(dynamic)? onTapEntry; const TopItemsSection({ @@ -36,31 +37,44 @@ class TopItemsSection extends StatelessWidget { }); @override - Widget build(BuildContext context) { - final colors = [ - Colors.red, - Colors.green, - Colors.blue, - Colors.orange, - Colors.teal, - Colors.grey - ]; + State createState() => _TopItemsState(); +} +class _TopItemsState extends State { + bool _showChart = true; + + final colors = [ + Colors.red, + Colors.green, + Colors.blue, + Colors.orange, + Colors.teal, + Colors.grey + ]; + + @override + void initState() { + _showChart = Provider.of(context, listen: false).showTopItemsChart; + super.initState(); + } + + @override + Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - final withChart = type != HomeTopItems.avgUpstreamResponseTime; + final withChart = widget.type != HomeTopItems.avgUpstreamResponseTime; - Map ringData() { + Map chartData() { Map values = {}; - data.sublist(0, data.length > 5 ? 5 : data.length).forEach((element) { + widget.data.sublist(0, widget.data.length > 5 ? 5 : widget.data.length).forEach((element) { values = { ...values, element.keys.first: element.values.first.toDouble() }; }); - if (data.length > 5) { + if (widget.data.length > 5) { final int rest = List.from( - data.sublist(5, data.length).map((e) => e.values.first.toInt()) + widget.data.sublist(5, widget.data.length).map((e) => e.values.first.toInt()) ).reduce((a, b) => a + b); values = { ...values, @@ -70,37 +84,25 @@ class TopItemsSection extends StatelessWidget { return values; } - List> lineData() { - List> values = []; - data.sublist(0, data.length > 5 ? 5 : data.length).forEach((element) { - values.add({ - "label": element.keys.first, - "value": element.values.first.toDouble() - }); - }); - if (data.length > 5) { - final int rest = List.from( - data.sublist(5, data.length).map((e) => e.values.first.toInt()) - ).reduce((a, b) => a + b); - values.add({ - "label": AppLocalizations.of(context)!.others, - "value": rest.toDouble() - }); - } - return values; - } - - final List> lineChartData = lineData(); - final mapData = lineChartData.map((e) => e["value"]); - final double total = mapData.isNotEmpty - ? mapData.reduce((a, b) => a + b) - : 0; + final Widget noItems = Padding( + padding: const EdgeInsets.only( + bottom: 20, + top: 10 + ), + child: Text( + AppLocalizations.of(context)!.noItems, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); return SizedBox( child: Column( children: [ - if (data.isEmpty) _NoData(label: label), - if (data.isNotEmpty && width > 700) Padding( + if (widget.data.isEmpty) noItems, + if (widget.data.isNotEmpty && width > 700) Padding( padding: EdgeInsets.only(bottom: withChart == false ? 16 : 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -114,7 +116,7 @@ class TopItemsSection extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16), child: CustomPieChart( - data: ringData(), + data: chartData(), colors: colors ) ), @@ -130,7 +132,7 @@ class TopItemsSection extends StatelessWidget { bottom: 16 ), child: Text( - label, + widget.label, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w500 @@ -139,16 +141,16 @@ class TopItemsSection extends StatelessWidget { ), _ItemsList( colors: colors, - data: data, - clients: type == HomeTopItems.recurrentClients, - type: type, - showChart: withChart, - buildValue: buildValue, - menuOptions: menuOptions, - onTapEntry: onTapEntry, + data: widget.data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: withChart == true ? _showChart : false, + buildValue: widget.buildValue, + menuOptions: widget.menuOptions, + onTapEntry: widget.onTapEntry, ), if (withChart == true) OthersRowItem( - items: data, + items: widget.data, showColor: true, ) ] @@ -157,59 +159,128 @@ class TopItemsSection extends StatelessWidget { ], ), ), - if (data.isNotEmpty && width <= 700) ...[ - Text( - label, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 8), - if (withChart == true) Padding( - padding: const EdgeInsets.all(16), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: SizedBox( - height: 20, - child: LayoutBuilder( - builder: (context, constraints) => Row( - children: lineChartData.asMap().entries.map((e) => Tooltip( - message:'${e.value["label"]} (${doubleFormat((e.value["value"]/total)*100, Platform.localeName)}%)', - child: Container( - width: constraints.maxWidth*(e.value["value"]/total), - decoration: BoxDecoration( - color: colors[e.key] + if (widget.data.isNotEmpty && width <= 700) Builder( + builder: (context) { + if (widget.withChart == true) { + return Column( + children: [ + ExpansionPanelList( + expandedHeaderPadding: const EdgeInsets.all(0), + elevation: 0, + expansionCallback: (_, isExpanded) => setState(() => _showChart = isExpanded), + animationDuration: const Duration(milliseconds: 250), + children: [ + ExpansionPanel( + headerBuilder: (context, isExpanded) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + ], + ), ), + body: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + SizedBox( + height: 150, + child: CustomPieChart( + data: chartData(), + colors: colors + ) + ), + const SizedBox(height: 16), + ], + ), + ), + isExpanded: _showChart ), - )).toList() - ) - ) - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 8), - child: _ItemsList( - colors: colors, - data: data, - clients: type == HomeTopItems.recurrentClients, - type: type, - showChart: withChart, - buildValue: buildValue, - menuOptions: menuOptions, - onTapEntry: onTapEntry, - ), - ), - OthersRowItem( - items: data, - showColor: withChart, - ), - ], + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: _ItemsList( + colors: colors, + data: widget.data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: _showChart, + buildValue: widget.buildValue, + menuOptions: widget.menuOptions, + onTapEntry: widget.onTapEntry, + ), + ), + if (widget.withChart == true) OthersRowItem( + items: widget.data, + showColor: _showChart, + ), + const SizedBox(height: 16), + ], + ); + } + else { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: _ItemsList( + colors: colors, + data: widget.data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: false, + buildValue: widget.buildValue, + menuOptions: widget.menuOptions, + onTapEntry: widget.onTapEntry, + ), + ), + if (widget.withChart == true) OthersRowItem( + items: widget.data, + showColor: false, + ), + const SizedBox(height: 16), + ], + ); + } + }, + ), - if (data.length > 5) ...[ + if (widget.data.length > 5) ...[ Padding( padding: const EdgeInsets.only(right: 20), child: Row( @@ -217,37 +288,37 @@ class TopItemsSection extends StatelessWidget { children: [ TextButton( onPressed: () => { - if (width > 700) { - showDialog( - context: context, - builder: (context) => TopItemsScreen( - type: type, - title: label, - isClient: type == HomeTopItems.recurrentClients, - data: data, - withProgressBar: withProgressBar, - buildValue: buildValue, - options: menuOptions, - onTapEntry: onTapEntry, - isFullscreen: !(width > 700 || !(Platform.isAndroid | Platform.isIOS)), - ), + showGeneralDialog( + context: context, + barrierColor: !(width > 700 || !(Platform.isAndroid | Platform.isIOS)) + ?Colors.transparent + : Colors.black54, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0) + ).animate( + CurvedAnimation( + parent: anim1, + curve: Curves.easeInOutCubicEmphasized + ) + ), + child: child, + ); + }, + pageBuilder: (context, animation, secondaryAnimation) => TopItemsScreen( + type: widget.type, + title: widget.label, + isClient: widget.type == HomeTopItems.recurrentClients, + data: widget.data, + withProgressBar: widget.withProgressBar, + buildValue: widget.buildValue, + options: widget.menuOptions, + onTapEntry: widget.onTapEntry, + isFullscreen: !(width > 700 || !(Platform.isAndroid | Platform.isIOS)), ) - } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => TopItemsScreen( - type: type, - title: label, - isClient: type == HomeTopItems.recurrentClients, - data: data, - withProgressBar: withProgressBar, - buildValue: buildValue, - options: menuOptions, - onTapEntry: onTapEntry, - isFullscreen: !(width > 700 || !(Platform.isAndroid | Platform.isIOS)), - ), - )) - } + ) }, child: Row( mainAxisSize: MainAxisSize.min, @@ -279,7 +350,7 @@ class _ItemsList extends StatelessWidget { final HomeTopItems type; final bool showChart; final String Function(dynamic) buildValue; - final List Function(dynamic) menuOptions; + final List menuOptions; final void Function(dynamic)? onTapEntry; const _ItemsList({ @@ -310,38 +381,4 @@ class _ItemsList extends StatelessWidget { )).toList() ); } -} - -class _NoData extends StatelessWidget { - final String label; - - const _NoData({ - required this.label - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Text( - label, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 24), - Text( - AppLocalizations.of(context)!.noDataThisSection, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 16), - ], - ); - } } \ No newline at end of file diff --git a/lib/screens/logs/configuration/config_widgets.dart b/lib/screens/logs/configuration/config_widgets.dart new file mode 100644 index 0000000..1fa59f9 --- /dev/null +++ b/lib/screens/logs/configuration/config_widgets.dart @@ -0,0 +1,276 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/logs/configuration/logs_config_modal.dart'; + +class LogsConfigOptions extends StatelessWidget { + final bool generalSwitch; + final void Function(bool) updateGeneralSwitch; + final bool anonymizeClientIp; + final void Function(bool) updateAnonymizeClientIp; + final List retentionItems; + final double? retentionTime; + final void Function(double?) updateRetentionTime; + final void Function() onClear; + final void Function() onConfirm; + + const LogsConfigOptions({ + super.key, + required this.generalSwitch, + required this.updateGeneralSwitch, + required this.anonymizeClientIp, + required this.updateAnonymizeClientIp, + required this.retentionItems, + required this.retentionTime, + required this.updateRetentionTime, + required this.onClear, + required this.onConfirm + }); + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.settings, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.logsSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 16), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: () => updateGeneralSwitch(!generalSwitch), + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.enableLog, + style: const TextStyle( + fontSize: 18, + ), + ), + Switch( + value: generalSwitch, + onChanged: updateGeneralSwitch, + ) + ], + ), + ), + ), + ), + ), + Container(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: Column( + children: [ + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => updateAnonymizeClientIp(!anonymizeClientIp), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + AppLocalizations.of(context)!.anonymizeClientIp, + style: const TextStyle( + fontSize: 16 + ), + ), + ), + Switch( + value: anonymizeClientIp, + onChanged: updateAnonymizeClientIp, + ) + ], + ), + ), + ), + ), + Container(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: DropdownButtonFormField( + items: retentionItems.map((item) => DropdownMenuItem( + value: item.value, + child: Text(item.label), + )).toList(), + value: retentionTime, + onChanged: (value) => updateRetentionTime(value as double), + decoration: InputDecoration( + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + label: Text(AppLocalizations.of(context)!.retentionTime) + ), + borderRadius: BorderRadius.circular(20), + ), + ), + ], + ), + ) + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (width > 500) TextButton( + onPressed: () { + Navigator.pop(context); + onClear(); + }, + child: Text(AppLocalizations.of(context)!.clearLogs) + ), + if (width <= 500) IconButton( + onPressed: () { + Navigator.pop(context); + onClear(); + }, + icon: const Icon(Icons.delete_rounded), + tooltip: AppLocalizations.of(context)!.clearLogs, + ), + Row( + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 20), + TextButton( + onPressed: retentionTime != null + ? () { + Navigator.pop(context); + onConfirm(); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: retentionTime != null + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ) + ), + ], + ) + ], + ), + ), + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); + } +} + +class ConfigLogsLoading extends StatelessWidget { + const ConfigLogsLoading({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.loadingLogsSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ), + ); + } +} + +class ConfigLogsError extends StatelessWidget { + const ConfigLogsError({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.logSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/logs/configuration/logs_config_modal.dart b/lib/screens/logs/configuration/logs_config_modal.dart new file mode 100644 index 0000000..6352f36 --- /dev/null +++ b/lib/screens/logs/configuration/logs_config_modal.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/logs/configuration/config_widgets.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class RetentionItem { + final String label; + final double value; + + const RetentionItem({ + required this.label, + required this.value, + }); +} + +class LogsConfigModal extends StatefulWidget { + final BuildContext context; + final void Function(Map) onConfirm; + final void Function() onClear; + final bool dialog; + final String serverVersion; + + const LogsConfigModal({ + super.key, + required this.context, + required this.onConfirm, + required this.onClear, + required this.dialog, + required this.serverVersion + }); + + @override + State createState() => _LogsConfigModalState(); +} + +class _LogsConfigModalState extends State { + bool generalSwitch = false; + bool anonymizeClientIp = false; + double? retentionTime; + + List retentionItems = []; + + LoadStatus loadStatus = LoadStatus.loading; + + void loadData() async { + final serversProvider = Provider.of(context, listen: false); + + final result = await serversProvider.apiClient2!.getQueryLogInfo(); + + if (mounted) { + if (result.successful == true) { + setState(() { + generalSwitch = result.content['enabled']; + anonymizeClientIp = result.content['anonymize_client_ip']; + retentionTime = result.content['interval'] != null + ? double.parse(result.content['interval'].toString()) + : null; + loadStatus = LoadStatus.loaded; + }); + } + else { + setState(() => loadStatus = LoadStatus.error); + } + } + } + + @override + void initState() { + retentionItems = [ + RetentionItem( + label: AppLocalizations.of(widget.context)!.hours6, + value: 21600000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.hours24, + value: 86400000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days7, + value: 604800000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days30, + value: 2592000000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days90, + value: 7776000000 + ), + ]; + + loadData(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return const ConfigLogsLoading(); + + case LoadStatus.loaded: + return LogsConfigOptions( + generalSwitch: generalSwitch, + updateGeneralSwitch: (v) => setState(() => generalSwitch = v), + anonymizeClientIp: anonymizeClientIp, + updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), + retentionItems: retentionItems, + retentionTime: retentionTime, + updateRetentionTime: (v) => setState(() => retentionTime = v), + onClear: () => widget.onClear(), + onConfirm: () => widget.onConfirm({ + "enabled": generalSwitch, + "interval": retentionTime, + "anonymize_client_ip": anonymizeClientIp + }) + ); + + case LoadStatus.error: + return const ConfigLogsError(); + + default: + return const SizedBox(); + } + }, + ) + ), + ); + } + else { + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ), + color: Theme.of(context).dialogBackgroundColor + ), + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return const ConfigLogsLoading(); + + case LoadStatus.loaded: + return LogsConfigOptions( + generalSwitch: generalSwitch, + updateGeneralSwitch: (v) => setState(() => generalSwitch = v), + anonymizeClientIp: anonymizeClientIp, + updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), + retentionItems: retentionItems, + retentionTime: retentionTime, + updateRetentionTime: (v) => setState(() => retentionTime = v), + onClear: () => widget.onClear(), + onConfirm: () => widget.onConfirm({ + "enabled": generalSwitch, + "interval": retentionTime, + "anonymize_client_ip": anonymizeClientIp + }) + ); + + case LoadStatus.error: + return const ConfigLogsError(); + + default: + return const SizedBox(); + } + }, + ) + ); + } + } +} \ No newline at end of file diff --git a/lib/screens/logs/details/log_details_screen.dart b/lib/screens/logs/details/log_details_screen.dart index 5ffe18e..ff88eaa 100644 --- a/lib/screens/logs/details/log_details_screen.dart +++ b/lib/screens/logs/details/log_details_screen.dart @@ -10,7 +10,6 @@ import 'package:adguard_home_manager/screens/logs/details/log_list_tile.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; -import 'package:adguard_home_manager/functions/copy_clipboard.dart'; import 'package:adguard_home_manager/constants/urls.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/functions/get_filtered_status.dart'; @@ -24,14 +23,12 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; class LogDetailsScreen extends StatelessWidget { final Log log; final bool dialog; - final bool twoColumns; const LogDetailsScreen({ - super.key, + Key? key, required this.log, - required this.dialog, - required this.twoColumns, - }); + required this.dialog + }) : super(key: key); @override Widget build(BuildContext context) { @@ -40,6 +37,27 @@ class LogDetailsScreen extends StatelessWidget { final width = MediaQuery.of(context).size.width; + Filter? getList(int id) { + try { + return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () { + return statusProvider.filteringStatus!.whitelistFilters.firstWhere((filter) => filter.id == id); + }); + } catch (_) { + return null; + } + } + + Widget getResult() { + final filter = getFilteredStatus(context, appConfigProvider, log.reason, true); + return Text( + filter['label'], + style: TextStyle( + color: filter['color'], + fontWeight: FontWeight.w500 + ), + ); + } + void blockUnblock(String domain, String newStatus) async { final ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingUserFilters); @@ -52,14 +70,14 @@ class LogDetailsScreen extends StatelessWidget { processModal.close(); if (rules == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.userFilteringRulesUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated, color: Colors.red @@ -67,6 +85,132 @@ class LogDetailsScreen extends StatelessWidget { } } + List content() { + return [ + SectionLabel(label: AppLocalizations.of(context)!.status), + LogListTile( + icon: Icons.shield_rounded, + title: AppLocalizations.of(context)!.result, + subtitleWidget: getResult(), + trailing: log.cached == true + ? Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5 + ), + decoration: BoxDecoration( + color: Theme.of(context).floatingActionButtonTheme.backgroundColor, + borderRadius: BorderRadius.circular(30) + ), + child: Text( + "CACHE", + style: TextStyle( + fontSize: 12, + color: Theme.of(context).floatingActionButtonTheme.foregroundColor, + fontWeight: FontWeight.w500 + ), + ), + ) + : null, + ), + if (log.rule != null) LogListTile( + icon: Icons.block, + title: AppLocalizations.of(context)!.blockingRule, + subtitle: log.rule + ), + LogListTile( + icon: Icons.schedule, + title: AppLocalizations.of(context)!.time, + subtitle: convertTimestampLocalTimezone(log.time, 'HH:mm:ss') + ), + SectionLabel(label: AppLocalizations.of(context)!.request), + LogListTile( + icon: Icons.domain_rounded, + title: AppLocalizations.of(context)!.domain, + subtitle: log.question.name + ), + LogListTile( + icon: Icons.category_rounded, + title: AppLocalizations.of(context)!.type, + subtitle: log.question.type + ), + LogListTile( + icon: Icons.class_rounded, + title: AppLocalizations.of(context)!.clas, + subtitle: log.question.questionClass + ), + SectionLabel(label: AppLocalizations.of(context)!.response), + if (log.upstream != null && log.upstream != '') LogListTile( + icon: Icons.dns_rounded, + title: AppLocalizations.of(context)!.dnsServer, + subtitle: log.upstream + ), + LogListTile( + icon: Icons.timer_rounded, + title: AppLocalizations.of(context)!.elapsedTime, + subtitle: "${double.parse(log.elapsedMs).toStringAsFixed(2)} ms" + ), + if (log.status != null) LogListTile( + icon: Icons.system_update_alt_rounded, + title: AppLocalizations.of(context)!.responseCode, + subtitle: log.status + ), + SectionLabel(label: AppLocalizations.of(context)!.client), + LogListTile( + icon: Icons.smartphone_rounded, + title: AppLocalizations.of(context)!.deviceIp, + subtitle: log.client + ), + if (log.clientInfo != null && log.clientInfo!.name != '') LogListTile( + icon: Icons.abc_rounded, + title: AppLocalizations.of(context)!.deviceName, + subtitle: log.clientInfo!.name + ), + if (log.rules.isNotEmpty) ...[ + SectionLabel(label: AppLocalizations.of(context)!.rules), + ...log.rules.map((rule) { + final Filter? list = getList(rule.filterListId); + if (list != null) { + return LogListTile( + icon: Icons.rule_rounded, + title: rule.text, + subtitle: list.name + ); + } + else { + return const SizedBox(); + } + }).toList() + ], + if (log.answer.isNotEmpty) ...[ + SectionLabel(label: AppLocalizations.of(context)!.answers), + ...log.answer.map((a) => LogListTile( + icon: Icons.download_rounded, + title: a.value, + subtitle: "TTL: ${a.ttl.toString()}", + trailing: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5 + ), + decoration: BoxDecoration( + color: Theme.of(context).floatingActionButtonTheme.backgroundColor, + borderRadius: BorderRadius.circular(30) + ), + child: Text( + a.type, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).floatingActionButtonTheme.foregroundColor, + fontWeight: FontWeight.w500 + ), + ), + ) + )).toList() + ] + ]; + } + if (dialog) { return Dialog( child: ConstrainedBox( @@ -127,9 +271,7 @@ class LogDetailsScreen extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: ListView( - children: [ - _Content(log: log) - ] + children: content(), ) ), ) @@ -150,7 +292,6 @@ class LogDetailsScreen extends StatelessWidget { centerTitle: false, forceElevated: innerBoxIsScrolled, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - automaticallyImplyLeading: twoColumns != true, title: Text(AppLocalizations.of(context)!.logDetails), actions: [ IconButton( @@ -181,6 +322,7 @@ class LogDetailsScreen extends StatelessWidget { ], body: SafeArea( top: false, + bottom: false, child: Builder( builder: (context) => CustomScrollView( slivers: [ @@ -188,9 +330,7 @@ class LogDetailsScreen extends StatelessWidget { handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), ), SliverList.list( - children: [ - _Content(log: log) - ] + children: content() ) ], ), @@ -200,174 +340,4 @@ class LogDetailsScreen extends StatelessWidget { ); } } -} - -class _Content extends StatelessWidget { - final Log log; - - const _Content({ - required this.log, - }); - - @override - Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); - final statusProvider = Provider.of(context); - - Widget getResult() { - final filter = getFilteredStatus(context, appConfigProvider, log.reason, true); - return Text( - filter['label'], - style: TextStyle( - color: filter['color'], - fontWeight: FontWeight.w500 - ), - ); - } - - Filter? getList(int id) { - try { - return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () { - return statusProvider.filteringStatus!.whitelistFilters.firstWhere((filter) => filter.id == id); - }); - } catch (_) { - return null; - } - } - - return Column( - children: [ - SectionLabel(label: AppLocalizations.of(context)!.status), - LogListTile( - icon: Icons.shield_rounded, - title: AppLocalizations.of(context)!.result, - subtitleWidget: getResult(), - trailing: log.cached == true - ? Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5 - ), - decoration: BoxDecoration( - color: Theme.of(context).floatingActionButtonTheme.backgroundColor, - borderRadius: BorderRadius.circular(30) - ), - child: Text( - "CACHE", - style: TextStyle( - fontSize: 12, - color: Theme.of(context).floatingActionButtonTheme.foregroundColor, - fontWeight: FontWeight.w500 - ), - ), - ) - : null, - ), - if (log.rule != null) LogListTile( - icon: Icons.block, - title: AppLocalizations.of(context)!.blockingRule, - subtitle: log.rule - ), - LogListTile( - icon: Icons.calendar_month_rounded, - title: AppLocalizations.of(context)!.date, - subtitle: convertTimestampLocalTimezone(log.time, 'dd-MM-yyyy') - ), - LogListTile( - icon: Icons.schedule, - title: AppLocalizations.of(context)!.time, - subtitle: convertTimestampLocalTimezone(log.time, 'HH:mm:ss') - ), - SectionLabel(label: AppLocalizations.of(context)!.request), - if (log.question.name != null) LogListTile( - icon: Icons.domain_rounded, - title: AppLocalizations.of(context)!.domain, - subtitle: log.question.name, - onTap: () => copyToClipboard(value: log.question.name!, successMessage: AppLocalizations.of(context)!.domainCopiedClipboard), - ), - LogListTile( - icon: Icons.category_rounded, - title: AppLocalizations.of(context)!.type, - subtitle: log.question.type - ), - LogListTile( - icon: Icons.class_rounded, - title: AppLocalizations.of(context)!.clas, - subtitle: log.question.questionClass - ), - SectionLabel(label: AppLocalizations.of(context)!.response), - if (log.upstream != null && log.upstream != '') LogListTile( - icon: Icons.dns_rounded, - title: AppLocalizations.of(context)!.dnsServer, - subtitle: log.upstream, - onTap: () => copyToClipboard(value: log.upstream!, successMessage: AppLocalizations.of(context)!.dnsServerAddressCopied) - ), - LogListTile( - icon: Icons.timer_rounded, - title: AppLocalizations.of(context)!.elapsedTime, - subtitle: "${double.parse(log.elapsedMs).toStringAsFixed(2)} ms" - ), - if (log.status != null) LogListTile( - icon: Icons.system_update_alt_rounded, - title: AppLocalizations.of(context)!.responseCode, - subtitle: log.status - ), - SectionLabel(label: AppLocalizations.of(context)!.client), - LogListTile( - icon: Icons.smartphone_rounded, - title: AppLocalizations.of(context)!.deviceIp, - subtitle: log.client, - onTap: () => copyToClipboard(value: log.client, successMessage: AppLocalizations.of(context)!.clientIpCopied), - ), - if (log.clientInfo != null && log.clientInfo!.name != '') LogListTile( - icon: Icons.abc_rounded, - title: AppLocalizations.of(context)!.deviceName, - subtitle: log.clientInfo!.name, - onTap: () => copyToClipboard(value: log.clientInfo!.name, successMessage: AppLocalizations.of(context)!.clientNameCopied), - ), - if (log.rules.isNotEmpty) ...[ - SectionLabel(label: AppLocalizations.of(context)!.rules), - ...log.rules.map((rule) { - final Filter? list = getList(rule.filterListId); - if (list != null) { - return LogListTile( - icon: Icons.rule_rounded, - title: rule.text, - subtitle: list.name - ); - } - else { - return const SizedBox(); - } - }) - ], - if (log.answer.isNotEmpty) ...[ - SectionLabel(label: AppLocalizations.of(context)!.answers), - ...log.answer.map((a) => LogListTile( - icon: Icons.download_rounded, - title: a.value, - subtitle: "TTL: ${a.ttl.toString()}", - trailing: Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5 - ), - decoration: BoxDecoration( - color: Theme.of(context).floatingActionButtonTheme.backgroundColor, - borderRadius: BorderRadius.circular(30) - ), - child: Text( - a.type, - style: TextStyle( - fontSize: 12, - color: Theme.of(context).floatingActionButtonTheme.foregroundColor, - fontWeight: FontWeight.w500 - ), - ), - ) - )) - ] - ], - ); - } } \ No newline at end of file diff --git a/lib/screens/logs/details/log_list_tile.dart b/lib/screens/logs/details/log_list_tile.dart index 15edd6b..c45f0f8 100644 --- a/lib/screens/logs/details/log_list_tile.dart +++ b/lib/screens/logs/details/log_list_tile.dart @@ -6,68 +6,63 @@ class LogListTile extends StatelessWidget { final String? subtitle; final Widget? subtitleWidget; final Widget? trailing; - final void Function()? onTap; const LogListTile({ - super.key, + Key? key, required this.icon, required this.title, this.subtitle, this.subtitleWidget, this.trailing, - this.onTap, - }); + }) : super(key: key); @override Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Row( - children: [ - Icon( - icon, - size: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + Icon( + icon, + size: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface ), - const SizedBox(height: 3), - subtitleWidget ?? Text( - subtitle!, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).listTileTheme.textColor, - ), - ) - ], - ), + ), + const SizedBox(height: 3), + subtitleWidget ?? Text( + subtitle!, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).listTileTheme.textColor, + ), + ) + ], ), - ], - ), + ), + ], ), - if (trailing != null) ...[ - const SizedBox(width: 16), - trailing! - ] - ], - ), + ), + if (trailing != null) ...[ + const SizedBox(width: 16), + trailing! + ] + ], ), ); } diff --git a/lib/screens/logs/filters/clients_modal.dart b/lib/screens/logs/filters/clients_modal.dart index 05b7213..1730d8e 100644 --- a/lib/screens/logs/filters/clients_modal.dart +++ b/lib/screens/logs/filters/clients_modal.dart @@ -2,30 +2,11 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:segmented_button_slide/segmented_button_slide.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.dart'; -import 'package:adguard_home_manager/functions/is_ip.dart'; -import 'package:adguard_home_manager/widgets/list_bottom_sheet.dart'; -import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; - -import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; -class _ClientLog { - final String ip; - final String? name; - final List? ids; - - const _ClientLog({ - required this.ip, - required this.name, - this.ids, - }); -} - class ClientsModal extends StatefulWidget { final List? value; final bool dialog; @@ -41,86 +22,17 @@ class ClientsModal extends StatefulWidget { } class _ClientsModalState extends State { - List<_ClientLog> _filteredClients = []; - final _searchController = TextEditingController(); - int _selectedList = 0; + List selectedClients = []; @override void initState() { - final clientsProvider = Provider.of(context, listen: false); - final statusProvider = Provider.of(context, listen: false); - _filteredClients = clientsProvider.clients!.autoClients.map((e) { - String? name; - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ip)).name; - } catch (e) { - // ---- // - } - return _ClientLog( - ip: e.ip, - name: name - ); - }).toList(); - + setState(() => selectedClients = widget.value ?? []); super.initState(); } @override Widget build(BuildContext context) { - final clientsProvider = Provider.of(context); - final logsProvider = Provider.of(context); - final statusProvider = Provider.of(context); - - void onSearch({required String value, int? selectedList}) { - if ((selectedList ?? _selectedList) == 1) { - final filtered = clientsProvider.clients!.clients.map((e) { - String? name; - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ids[0])).name; - } catch (e) { - // ---- // - } - return _ClientLog( - ip: e.ids[0], - name: name, - ids: e.ids - ); - }).where( - (c) => c.ip.contains(value.toLowerCase()) || (c.name != null && c.name!.toLowerCase().contains(value.toLowerCase())) - ).toList(); - setState(() => _filteredClients = filtered); - } - else { - final filtered = clientsProvider.clients!.autoClients.map((e) { - String? name; - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ip)).name; - } catch (e) { - // ---- // - } - return _ClientLog( - ip: e.ip, - name: name - ); - }).where( - (c) => c.ip.contains(value.toLowerCase()) || (c.name != null && c.name!.toLowerCase().contains(value.toLowerCase())) - ).toList(); - setState(() => _filteredClients = filtered); - } - } - - void onListChange(int list) { - onSearch(value: _searchController.text, selectedList: list); - } - - void searchAddedClient(_ClientLog client) { - final ips = client.ids?.where((e) => isIpAddress(e) == true).toList(); - if (ips == null || ips.isEmpty) return; - logsProvider.setSearchText(ips[0]); - logsProvider.filterLogs(); - Navigator.of(context).pop(); - Navigator.pop(context); - } + final height = MediaQuery.of(context).size.height; if (widget.dialog == true) { return Dialog( @@ -128,292 +40,194 @@ class _ClientsModalState extends State { constraints: const BoxConstraints( maxWidth: 500 ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - CloseButton( - onPressed: () => Navigator.pop(context), - ), - const SizedBox(width: 12), - Text( - AppLocalizations.of(context)!.clients, - style: const TextStyle( - fontSize: 22 - ), - ) - ], - ), - ), - Flexible( - child: ListView( - children: [ - _SearchField( - controller: _searchController, - onClear: () => setState(() => _searchController.text = ""), - onSearch: (v) => onSearch(value: v) - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Flexible( - child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo) - ) - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: SegmentedButtonSlide( - entries: [ - SegmentedButtonSlideEntry(icon: Icons.devices, label: AppLocalizations.of(context)!.activeClients), - SegmentedButtonSlideEntry(icon: Icons.add_rounded, label: AppLocalizations.of(context)!.added), - ], - selectedEntry: _selectedList, - onChange: (v) { - onListChange(v); - setState(() => _selectedList = v); - }, - colors: SegmentedButtonSlideColors( - barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2), - backgroundSelectedColor: Theme.of(context).colorScheme.primary, - ), - selectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, - ), - unselectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - hoverTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ListView.builder( - primary: false, - shrinkWrap: true, - itemCount: _filteredClients.length, - itemBuilder: (context, index) => _ListItem( - title: _filteredClients[index].ip, - subtitle: _selectedList == 0 ? _filteredClients[index].name : _filteredClients[index].ids?.join(", "), - checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip), - isAddedClient: _selectedList == 0, - onSearchAddedClient: () => searchAddedClient(_filteredClients[index]), - onChanged: (isSelected) { - if (isSelected == true) { - logsProvider.setSelectedClients([ - ...logsProvider.selectedClients, - _filteredClients[index].ip - ]); - } - else { - logsProvider.setSelectedClients( - logsProvider.selectedClients.where( - (item) => item != _filteredClients[index].ip - ).toList() - ); - } - } - ) - ), - ], - ) - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], + child: _ModalContent( + selectedClients: selectedClients, + onClientsSelected: (v) => setState(() => selectedClients = v), ) ), ); } else { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: ListBottomSheet( - icon: Icons.smartphone_rounded, - title: AppLocalizations.of(context)!.clients, - children: [ - _SearchField( - controller: _searchController, - onClear: () => setState(() => _searchController.text = ""), - onSearch: (v) => onSearch(value: v) + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: height-50 + ), + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Flexible( - child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo) - ) - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: SegmentedButtonSlide( - entries: [ - SegmentedButtonSlideEntry(icon: Icons.devices, label: AppLocalizations.of(context)!.activeClients), - SegmentedButtonSlideEntry(icon: Icons.add_rounded, label: AppLocalizations.of(context)!.added), - ], - selectedEntry: _selectedList, - onChange: (v) { - onListChange(v); - setState(() => _selectedList = v); - }, - colors: SegmentedButtonSlideColors( - barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2), - backgroundSelectedColor: Theme.of(context).colorScheme.primary, - ), - selectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, - fontWeight: FontWeight.w700 - ), - unselectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - hoverTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ListView.builder( - shrinkWrap: true, - primary: false, - itemCount: _filteredClients.length, - itemBuilder: (context, index) => _ListItem( - title: _selectedList == 0 ? _filteredClients[index].ip : _filteredClients[index].name ?? "", - subtitle: _selectedList == 0 ? _filteredClients[index].name : _filteredClients[index].ids?.join(", "), - checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip), - isAddedClient: _selectedList == 1, - onSearchAddedClient: _filteredClients[index].ids != null && _filteredClients[index].ids!.where((e) => isIpAddress(e) == true).isNotEmpty ? () => searchAddedClient(_filteredClients[index]) : null, - onChanged: (isSelected) { - if (isSelected == true) { - logsProvider.setSelectedClients([ - ...logsProvider.selectedClients, - _filteredClients[index].ip - ]); - } - else { - logsProvider.setSelectedClients( - logsProvider.selectedClients.where( - (item) => item != _filteredClients[index].ip - ).toList() - ); - } - } - ) - ), - ] + color: Theme.of(context).dialogBackgroundColor + ), + child: _ModalContent( + selectedClients: selectedClients, + onClientsSelected: (v) => setState(() => selectedClients = v), + ) ), ); } } } -class _SearchField extends StatelessWidget { - final TextEditingController controller; - final void Function(String) onSearch; - final void Function() onClear; +class _ModalContent extends StatelessWidget { + final List selectedClients; + final void Function(List) onClientsSelected; - const _SearchField({ - required this.controller, - required this.onClear, - required this.onSearch, + const _ModalContent({ + required this.selectedClients, + required this.onClientsSelected, }); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: TextFormField( - controller: controller, - onChanged: onSearch, - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.search, - prefixIcon: const Icon(Icons.search_rounded), - border: InputBorder.none, - filled: true, - fillColor: Colors.grey.withOpacity(0.2), - suffixIcon: controller.text != "" - ? IconButton( - onPressed: onClear, - icon: const Icon( - Icons.close_rounded, - size: 20, - ), - tooltip: AppLocalizations.of(context)!.clearSearch, + final clientsProvider = Provider.of(context); + final logsProvider = Provider.of(context); + + void apply() async { + logsProvider.setSelectedClients( + selectedClients.isNotEmpty ? selectedClients : null + ); + + Navigator.pop(context); + } + + void selectAll() { + onClientsSelected( + clientsProvider.clients!.autoClients.map((item) => item.ip).toList() + ); + } + + void unselectAll() { + onClientsSelected([]); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 24, + bottom: 16, + ), + child: Icon( + Icons.smartphone_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + Text( + AppLocalizations.of(context)!.clients, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 16), + ], + ), + Flexible( + child: ListView.builder( + itemCount: clientsProvider.clients!.autoClients.length, + itemBuilder: (context, index) => _ListItem( + label: clientsProvider.clients!.autoClients[index].ip, + checkboxActive: selectedClients.contains(clientsProvider.clients!.autoClients[index].ip), + onChanged: (isSelected) { + if (isSelected == true) { + onClientsSelected([ + ...selectedClients, + clientsProvider.clients!.autoClients[index].ip + ]); + } + else { + onClientsSelected( + selectedClients.where( + (item) => item != clientsProvider.clients!.autoClients[index].ip + ).toList() + ); + } + } + ) + ) + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: selectedClients.length == clientsProvider.clients!.autoClients.length + ? () => unselectAll() + : () => selectAll(), + child: Text( + selectedClients.length == clientsProvider.clients!.autoClients.length + ? AppLocalizations.of(context)!.unselectAll + : AppLocalizations.of(context)!.selectAll ) - : null + ), + TextButton( + onPressed: apply, + child: Text(AppLocalizations.of(context)!.apply) + ) + ], ), ), - ), + if (Platform.isIOS) const SizedBox(height: 16) + ], ); } } class _ListItem extends StatelessWidget { - final String title; - final String? subtitle; + final String label; final bool checkboxActive; final void Function(bool) onChanged; - final bool isAddedClient; - final void Function()? onSearchAddedClient; const _ListItem({ - required this.title, - this.subtitle, + required this.label, required this.checkboxActive, required this.onChanged, - required this.isAddedClient, - required this.onSearchAddedClient, }); @override Widget build(BuildContext context) { - if (isAddedClient == true) { - return CustomListTile( - title: title, - subtitle: subtitle, - trailing: TextButton( - onPressed: onSearchAddedClient, - child: Text(AppLocalizations.of(context)!.select) + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onChanged(!checkboxActive), + child: Padding( + padding: const EdgeInsets.only( + left: 24, + top: 8, + right: 12, + bottom: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + label, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + Checkbox( + value: checkboxActive, + onChanged: (v) => onChanged(!checkboxActive), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5) + ), + ) + ], + ), ), - ); - } - else { - return CustomCheckboxListTile( - value: checkboxActive, - onChanged: (v) => onChanged(v), - title: title, - subtitle: subtitle, - padding: const EdgeInsets.only( - left: 24, - top: 8, - right: 12, - bottom: 8 - ), - ); - } + ), + ); } } \ No newline at end of file diff --git a/lib/screens/logs/filters/filter_status_modal.dart b/lib/screens/logs/filters/filter_status_modal.dart index d4e4155..d3c1079 100644 --- a/lib/screens/logs/filters/filter_status_modal.dart +++ b/lib/screens/logs/filters/filter_status_modal.dart @@ -1,11 +1,9 @@ - +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/list_bottom_sheet.dart'; - import 'package:adguard_home_manager/providers/logs_provider.dart'; class FilterStatusModal extends StatefulWidget { @@ -35,141 +33,167 @@ class _FilterStatusModalState extends State { Widget build(BuildContext context) { final logsProvider = Provider.of(context); + void apply() async { + logsProvider.setSelectedResultStatus(selectedResultStatus); + + Navigator.pop(context); + } + if (widget.dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 400 ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Padding( - padding: const EdgeInsets.only(top: 16, left: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - CloseButton( - onPressed: () => Navigator.pop(context), - ), - const SizedBox(width: 12), - Text( - AppLocalizations.of(context)!.responseStatus, - style: const TextStyle( - fontSize: 22 - ), - ) - ], - ), - ), - Container(height: 16), - _ItemsList( - selectedResultStatus: logsProvider.selectedResultStatus, - updateSelectedResultStatus: (v) => logsProvider.setSelectedResultStatus(value: v), - ), - Container(height: 16) - ], - ), - ), - ), - ], + child: _Content( + onApply: apply, + updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v), + selectedResultStatus: selectedResultStatus, ) ), ); } else { - return SizedBox( - height: 700, - child: ListBottomSheet( - icon: Icons.shield_rounded, - title: AppLocalizations.of(context)!.responseStatus, - initialChildSize: 1, - minChildSize: 0.5, - children: [ - _ItemsList( - selectedResultStatus: logsProvider.selectedResultStatus, - updateSelectedResultStatus: (v) => logsProvider.setSelectedResultStatus(value: v), - ) - ] + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ), + color: Theme.of(context).dialogBackgroundColor ), + child: _Content( + onApply: apply, + updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v), + selectedResultStatus: selectedResultStatus, + ) ); } } } -class _ItemsList extends StatelessWidget { +class _Content extends StatelessWidget { final String selectedResultStatus; final void Function(String) updateSelectedResultStatus; + final void Function() onApply; - const _ItemsList({ + const _Content({ required this.selectedResultStatus, - required this.updateSelectedResultStatus + required this.updateSelectedResultStatus, + required this.onApply, }); @override Widget build(BuildContext context) { return Column( + mainAxisSize: MainAxisSize.min, children: [ - _Item( - selectedResultStatus: selectedResultStatus, - id: "all", - icon: Icons.shield_rounded, - label: AppLocalizations.of(context)!.all, - onChanged: updateSelectedResultStatus + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 24, + bottom: 16, + ), + child: Icon( + Icons.shield_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + Text( + AppLocalizations.of(context)!.responseStatus, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ) + ], + ), + Container(height: 16), + _Item( + selectedResultStatus: selectedResultStatus, + id: "all", + icon: Icons.shield_rounded, + label: AppLocalizations.of(context)!.all, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "filtered", + icon: Icons.shield_rounded, + label: AppLocalizations.of(context)!.filtered, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "processed", + icon: Icons.verified_user_rounded, + label: AppLocalizations.of(context)!.processedRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "whitelisted", + icon: Icons.verified_user_rounded, + label: AppLocalizations.of(context)!.processedWhitelistRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "blocked", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blocked, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "blocked_safebrowsing", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blockedSafeBrowsingRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "blocked_parental", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blockedParentalRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "safe_search", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blockedSafeSearchRow, + onChanged: updateSelectedResultStatus + ), + ], + ), + ), ), - _Item( - selectedResultStatus: selectedResultStatus, - id: "filtered", - icon: Icons.shield_rounded, - label: AppLocalizations.of(context)!.filtered, - onChanged: updateSelectedResultStatus - ), - _Item( - selectedResultStatus: selectedResultStatus, - id: "processed", - icon: Icons.verified_user_rounded, - label: AppLocalizations.of(context)!.processedRow, - onChanged: updateSelectedResultStatus - ), - _Item( - selectedResultStatus: selectedResultStatus, - id: "whitelisted", - icon: Icons.verified_user_rounded, - label: AppLocalizations.of(context)!.processedWhitelistRow, - onChanged: updateSelectedResultStatus - ), - _Item( - selectedResultStatus: selectedResultStatus, - id: "blocked", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blocked, - onChanged: updateSelectedResultStatus - ), - _Item( - selectedResultStatus: selectedResultStatus, - id: "blocked_safebrowsing", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blockedSafeBrowsingRow, - onChanged: updateSelectedResultStatus - ), - _Item( - selectedResultStatus: selectedResultStatus, - id: "blocked_parental", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blockedParentalRow, - onChanged: updateSelectedResultStatus - ), - _Item( - selectedResultStatus: selectedResultStatus, - id: "safe_search", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blockedSafeSearchRow, - onChanged: updateSelectedResultStatus + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: onApply, + child: Text(AppLocalizations.of(context)!.apply) + ) + ], + ), ), + if (Platform.isIOS) const SizedBox(height: 16) ], ); } diff --git a/lib/screens/logs/filters/logs_filters_modal.dart b/lib/screens/logs/filters/logs_filters_modal.dart index b506cff..8e1cdbe 100644 --- a/lib/screens/logs/filters/logs_filters_modal.dart +++ b/lib/screens/logs/filters/logs_filters_modal.dart @@ -15,17 +15,30 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; -class LogsFiltersModal extends StatelessWidget { +class LogsFiltersModal extends StatefulWidget { final bool dialog; const LogsFiltersModal({ - super.key, + Key? key, required this.dialog - }); + }) : super(key: key); + + @override + State createState() => _LogsFiltersModalState(); +} + +class _LogsFiltersModalState extends State { + TextEditingController searchController = TextEditingController(); + + @override + void initState() { + searchController.text = Provider.of(context, listen: false).searchText ?? ''; + super.initState(); + } @override Widget build(BuildContext context) { - if (dialog == true) { + if (widget.dialog == true) { return Padding( padding: MediaQuery.of(context).viewInsets, child: Dialog( @@ -33,7 +46,10 @@ class LogsFiltersModal extends StatelessWidget { constraints: const BoxConstraints( maxWidth: 500 ), - child: const _FiltersList() + child: _FiltersList( + searchController: searchController, + onClearSearch: () => setState(() => searchController.text = "") + ) ) ), ); @@ -49,8 +65,9 @@ class LogsFiltersModal extends StatelessWidget { topRight: Radius.circular(28) ) ), - child: const SafeArea( - child: _FiltersList(), + child: _FiltersList( + searchController: searchController, + onClearSearch: () => setState(() => searchController.text = "") ) ), ); @@ -59,7 +76,14 @@ class LogsFiltersModal extends StatelessWidget { } class _FiltersList extends StatelessWidget { - const _FiltersList(); + final TextEditingController searchController; + final void Function() onClearSearch; + + const _FiltersList({ + Key? key, + required this.searchController, + required this.onClearSearch, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -99,8 +123,7 @@ class _FiltersList extends StatelessWidget { dialog: false, ), isScrollControlled: true, - backgroundColor: Colors.transparent, - useSafeArea: true + backgroundColor: Colors.transparent ); } } @@ -125,7 +148,6 @@ class _FiltersList extends StatelessWidget { dialog: false, ), isScrollControlled: true, - useSafeArea: true, backgroundColor: Colors.transparent ); } @@ -169,11 +191,40 @@ class _FiltersList extends StatelessWidget { ), ], ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + children: [ + Expanded( + child: TextFormField( + controller: searchController, + onChanged: logsProvider.setSearchText, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.search, + suffixIcon: IconButton( + onPressed: () { + onClearSearch(); + logsProvider.setSearchText(null); + }, + icon: const Icon(Icons.clear) + ), + ), + ), + ) + ], + ), + ), Container(height: 16), CustomListTile( title: AppLocalizations.of(context)!.client, - subtitle: logsProvider.selectedClients.isNotEmpty - ? "${logsProvider.selectedClients.length} ${AppLocalizations.of(context)!.clientsSelected}" + subtitle: logsProvider.selectedClients != null + ? "${logsProvider.selectedClients!.length} ${AppLocalizations.of(context)!.clientsSelected}" : AppLocalizations.of(context)!.all, onTap: clientsProvider.loadStatus == LoadStatus.loaded ? openSelectClients @@ -209,13 +260,13 @@ class _FiltersList extends StatelessWidget { FilterChip( selected: logsProvider.selectedResultStatus == "all", label: Text(AppLocalizations.of(context)!.all), - onSelected: (_) => logsProvider.setSelectedResultStatus(value: "all") + onSelected: (_) => logsProvider.setSelectedResultStatus("all") ), FilterChip( selected: logsProvider.selectedResultStatus == "processed" || logsProvider.selectedResultStatus == "whitelisted", label: Text(AppLocalizations.of(context)!.allowed), - onSelected: (_) => logsProvider.setSelectedResultStatus(value: "processed") + onSelected: (_) => logsProvider.setSelectedResultStatus("processed") ), FilterChip( selected: logsProvider.selectedResultStatus == "blocked" || @@ -223,7 +274,7 @@ class _FiltersList extends StatelessWidget { logsProvider.selectedResultStatus == "blocked_parental" || logsProvider.selectedResultStatus == "safe_search", label: Text(AppLocalizations.of(context)!.blocked), - onSelected: (_) => logsProvider.setSelectedResultStatus(value: "blocked") + onSelected: (_) => logsProvider.setSelectedResultStatus("blocked") ), ], ), @@ -246,6 +297,7 @@ class _FiltersList extends StatelessWidget { children: [ TextButton( onPressed: () { + searchController.text = ""; logsProvider.requestResetFilters(); }, child: Text(AppLocalizations.of(context)!.resetFilters) diff --git a/lib/screens/logs/live/live_logs_list.dart b/lib/screens/logs/live/live_logs_list.dart deleted file mode 100644 index 4bdf02f..0000000 --- a/lib/screens/logs/live/live_logs_list.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/logs/log_tile.dart'; -import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart'; - -import 'package:adguard_home_manager/models/logs.dart'; -import 'package:adguard_home_manager/providers/live_logs_provider.dart'; - -class LiveLogsList extends StatelessWidget { - final Log? selectedLog; - final void Function(Log) onLogSelected; - final bool twoColumns; - - const LiveLogsList({ - super.key, - required this.selectedLog, - required this.onLogSelected, - required this.twoColumns, - }); - - @override - Widget build(BuildContext context) { - final liveLogsProvider = Provider.of(context); - - return NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - title: Text(AppLocalizations.of(context)!.liveLogs), - ) - ) - ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (liveLogsProvider.logs.isEmpty) SliverFillRemaining( - child: Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: Text( - AppLocalizations.of(context)!.hereWillAppearRealtimeLogs, - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 22 - ), - ), - ), - ), - ), - if (liveLogsProvider.logs.isNotEmpty) SliverPadding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewPadding.bottom), - sliver: SliverList.builder( - itemCount: liveLogsProvider.logs.length, - itemBuilder: (context, index) => LogTile( - log: liveLogsProvider.logs[index], - length: liveLogsProvider.logs.length, - index: index, - onLogTap: (log) { - if (!twoColumns) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false, - twoColumns: twoColumns, - ) - ) - ); - } - onLogSelected(log); - }, - isLogSelected: selectedLog == liveLogsProvider.logs[index], - twoColumns: twoColumns - ), - ), - ) - ], - ), - ) - ) - ); - } -} \ No newline at end of file diff --git a/lib/screens/logs/live/live_logs_screen.dart b/lib/screens/logs/live/live_logs_screen.dart deleted file mode 100644 index 5c35e1a..0000000 --- a/lib/screens/logs/live/live_logs_screen.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart'; -import 'package:adguard_home_manager/screens/logs/live/live_logs_list.dart'; - -import 'package:adguard_home_manager/models/logs.dart'; -import 'package:adguard_home_manager/providers/live_logs_provider.dart'; - -class LiveLogsScreen extends StatefulWidget { - const LiveLogsScreen({super.key}); - - @override - State createState() => _LiveLogsScreenState(); -} - -class _LiveLogsScreenState extends State { - Log? _selectedLog; - - @override - void initState() { - Provider.of(context, listen: false).startFetchLogs(); - - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: LayoutBuilder( - builder: (context, constraints) { - if (constraints.maxWidth > 800) { - return Material( - color: Colors.transparent, - child: Row( - children: [ - Expanded( - flex: 2, - child: LiveLogsList( - twoColumns: true, - selectedLog: _selectedLog, - onLogSelected: (log) => setState(() => _selectedLog = log), - ) - ), - Expanded( - flex: 3, - child: _selectedLog != null - ? LogDetailsScreen( - log: _selectedLog!, - dialog: false, - twoColumns: true, - ) - : const SizedBox() - ) - ], - ), - ); - } - else { - return LiveLogsList( - twoColumns: false, - selectedLog: _selectedLog, - onLogSelected: (log) => setState(() => _selectedLog = log), - ); - } - }, - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index dc33048..af130e2 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -1,18 +1,15 @@ +// ignore_for_file: use_build_context_synchronously + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; -import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; import 'package:adguard_home_manager/widgets/options_menu.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/providers/filtering_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/copy_clipboard.dart'; -import 'package:adguard_home_manager/models/clients.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/functions/get_filtered_status.dart'; @@ -43,8 +40,6 @@ class LogTile extends StatelessWidget { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); final statusProvider = Provider.of(context); - final clientsProvider = Provider.of(context); - final filteringProvider = Provider.of(context); Widget logStatusWidget({ required IconData icon, @@ -96,8 +91,6 @@ class LogTile extends StatelessWidget { } void blockUnblock({required String domain, required String newStatus}) async { - if (!context.mounted) return; - final ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingUserFilters); @@ -110,14 +103,14 @@ class LogTile extends StatelessWidget { if (!context.mounted) return; if (rules == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.userFilteringRulesUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated, color: Colors.red @@ -125,123 +118,6 @@ class LogTile extends StatelessWidget { } } - void confirmAddClient(Client client) async { - if (!context.mounted) return; - - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.addingClient); - - final result = await clientsProvider.addClient(client); - - processModal.close(); - - if (!context.mounted) return; - - if (result == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.clientAddedSuccessfully, - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.clientNotAdded, - color: Colors.red - ); - } - } - - void blockUnblockRuleClient() async { - if (!context.mounted) return; - - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.addingRule); - - final rule = isDomainBlocked(log.reason) == true - ? "@@||${log.question.name}^\$client='${log.client}'" - : "||${log.question.name}^\$client='${log.client}'"; - - final result = await filteringProvider.addCustomRule(rule); - - processModal.close(); - - if (!context.mounted) return; - if (result == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: isDomainBlocked(log.reason) == true - ? AppLocalizations.of(context)!.domainUnblockedThisClient(log.question.name!) - : AppLocalizations.of(context)!.domainBlockedThisClient(log.question.name!), - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.ruleNotAdded, - color: Colors.red - ); - } - } - - void allowDisallowClient() async { - ProcessModal processModal = ProcessModal(); - processModal.open( - log.clientInfo!.disallowed == true - ? AppLocalizations.of(context)!.allowingClient - : AppLocalizations.of(context)!.disallowingClient - ); - - final result = await clientsProvider.addClientList( - log.client, - log.clientInfo!.disallowed == true - ? AccessSettingsList.allowed - : AccessSettingsList.disallowed - ); - - processModal.close(); - - if (!context.mounted) return; - if (result.successful == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.clientAddedSuccessfully, - color: Colors.green - ); - } - else if (result.successful == false && result.content == 'client_another_list') { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.clientAnotherList, - color: Colors.red - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.changesNotSaved, - color: Colors.red - ); - } - } - - void openAddClient() { - Future.delayed( - const Duration(milliseconds: 0), - () { - if (!context.mounted) return; - openClientFormModal( - context: context, - width: MediaQuery.of(context).size.width, - onConfirm: confirmAddClient, - initialData: ClientInitialData(name: "Client ${log.client}", ip: log.client) - ); - } - ); - } - final domainBlocked = isDomainBlocked(log.reason); if (twoColumns && !(useAlwaysNormalTile == true)) { @@ -252,7 +128,7 @@ class LogTile extends StatelessWidget { child: OptionsMenu( onTap: (_) => onLogTap(log), borderRadius: BorderRadius.circular(28), - options: (_) => [ + options: [ if (log.question.name != null) MenuOption( title: domainBlocked == true ? AppLocalizations.of(context)!.unblockDomain @@ -260,38 +136,15 @@ class LogTile extends StatelessWidget { icon: domainBlocked == true ? Icons.check_rounded : Icons.block_rounded, - action: () => blockUnblock( + action: (_) => blockUnblock( domain: log.question.name!, newStatus: domainBlocked == true ? 'unblock' : 'block' ) ), - if (filteringProvider.filtering != null) MenuOption( - title: domainBlocked == true - ? AppLocalizations.of(context)!.unblockThisClientOnly - : AppLocalizations.of(context)!.blockThisClientOnly, - icon: domainBlocked == true - ? Icons.check_rounded - : Icons.block_rounded, - action: blockUnblockRuleClient - ), - if (log.clientInfo?.name == "") MenuOption( - title: AppLocalizations.of(context)!.addPersistentClient, - icon: Icons.add_rounded, - action: openAddClient - ), MenuOption( - title: log.clientInfo!.disallowed == true - ? AppLocalizations.of(context)!.allowThisClient - : AppLocalizations.of(context)!.disallowThisClient, - icon: log.clientInfo!.disallowed == true - ? Icons.check_rounded - : Icons.block_rounded, - action: allowDisallowClient - ), - if (log.question.name != null) MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyToClipboard(value: log.question.name!, successMessage: AppLocalizations.of(context)!.copiedClipboard) + action: (v) => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) ) ], child: Container( @@ -453,7 +306,7 @@ class LogTile extends StatelessWidget { color: Colors.transparent, child: OptionsMenu( onTap: (_) => onLogTap(log), - options: (_) => [ + options: [ if (log.question.name != null) MenuOption( title: domainBlocked == true ? AppLocalizations.of(context)!.unblockDomain @@ -461,38 +314,15 @@ class LogTile extends StatelessWidget { icon: domainBlocked == true ? Icons.check_rounded : Icons.block_rounded, - action: () => blockUnblock( + action: (_) => blockUnblock( domain: log.question.name!, newStatus: domainBlocked == true ? 'unblock' : 'block' ) ), - if (filteringProvider.filtering != null) MenuOption( - title: domainBlocked == true - ? AppLocalizations.of(context)!.unblockThisClientOnly - : AppLocalizations.of(context)!.blockThisClientOnly, - icon: domainBlocked == true - ? Icons.check_rounded - : Icons.block_rounded, - action: blockUnblockRuleClient - ), - if (log.clientInfo?.name == "") MenuOption( - title: AppLocalizations.of(context)!.addPersistentClient, - icon: Icons.add_rounded, - action: openAddClient - ), - MenuOption( - title: log.clientInfo!.disallowed == true - ? AppLocalizations.of(context)!.allowThisClient - : AppLocalizations.of(context)!.disallowThisClient, - icon: log.clientInfo!.disallowed == true - ? Icons.check_rounded - : Icons.block_rounded, - action: allowDisallowClient - ), if (log.question.name != null) MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: log.question.name!, successMessage: AppLocalizations.of(context)!.copiedClipboard ) diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index d008297..757c949 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -1,16 +1,14 @@ // ignore_for_file: use_build_context_synchronously import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:adguard_home_manager/screens/logs/logs_list.dart'; import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart'; import 'package:adguard_home_manager/models/logs.dart'; -import 'package:adguard_home_manager/providers/filtering_provider.dart'; class Logs extends StatefulWidget { - const Logs({super.key}); + const Logs({Key? key}) : super(key: key); @override State createState() => _LogsState(); @@ -19,17 +17,11 @@ class Logs extends StatefulWidget { class _LogsState extends State { Log? _selectedLog; - @override - void initState() { - Provider.of(context, listen: false).fetchFilters(); - super.initState(); - } - @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { - if (constraints.maxWidth > 800) { + if (constraints.maxWidth > 1000) { return Material( color: Colors.transparent, child: Row( @@ -48,7 +40,6 @@ class _LogsState extends State { ? LogDetailsScreen( log: _selectedLog!, dialog: false, - twoColumns: true, ) : const SizedBox() ) diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index 4281af0..ac0596a 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -22,18 +22,17 @@ class LogsListWidget extends StatefulWidget { final void Function(Log) onLogSelected; const LogsListWidget({ - super.key, + Key? key, required this.twoColumns, required this.selectedLog, required this.onLogSelected, - }); + }) : super(key: key); @override State createState() => _LogsListWidgetState(); } class _LogsListWidgetState extends State { - final _scaffoldMessengerKey = GlobalKey(); bool showDivider = true; void fetchFilteringRules() async { @@ -42,7 +41,7 @@ class _LogsListWidgetState extends State { final result = await statusProvider.getFilteringRules(); if (mounted && result == false) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.couldntGetFilteringStatus, color: Colors.red @@ -56,7 +55,7 @@ class _LogsListWidgetState extends State { final result = await clientsProvider.fetchClients(); if (mounted && result == false) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.couldntGetFilteringStatus, color: Colors.red @@ -94,198 +93,194 @@ class _LogsListWidgetState extends State { Widget build(BuildContext context) { final logsProvider = Provider.of(context); - return ScaffoldMessenger( - key: widget.twoColumns ? _scaffoldMessengerKey : null, - child: Scaffold( - body: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: LogsListAppBar( - innerBoxIsScrolled: innerBoxIsScrolled, - showDivider: showDivider, - ) + return Scaffold( + body: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: LogsListAppBar( + innerBoxIsScrolled: innerBoxIsScrolled, + showDivider: showDivider, ) - ], - body: Builder( - builder: (context) { - switch (logsProvider.loadStatus) { - case LoadStatus.loading: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverFillRemaining( - child: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingLogs, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ) + ) + ], + body: Builder( + builder: (context) { + switch (logsProvider.loadStatus) { + case LoadStatus.loading: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + child: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingLogs, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), ) - ], - ), - ) - ); - - case LoadStatus.loaded: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => RefreshIndicator( - onRefresh: () async { - await logsProvider.fetchLogs(inOffset: 0); - }, - displacement: 95, - child: NotificationListener( - onNotification: scrollListener, - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder( - itemCount: logsProvider.isLoadingMore - ? logsProvider.logsData!.data.length + 1 - : logsProvider.logsData!.data.length, - itemBuilder: (context, index) { - if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: Center( - child: CircularProgressIndicator(), - ), - ); - } - else if (logsProvider.logsData!.data[index].question.name != null) { - return LogTile( - log: logsProvider.logsData!.data[index], - index: index, - length: logsProvider.logsData!.data.length, - isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index], - onLogTap: (log) { - if (!widget.twoColumns) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false, - twoColumns: widget.twoColumns, - ) - ) - ); - } - widget.onLogSelected(log); - }, - twoColumns: widget.twoColumns, - ); - } - else { - return null; - } + ) + ], + ), + ) + ); + + case LoadStatus.loaded: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => RefreshIndicator( + onRefresh: () async { + await logsProvider.fetchLogs(inOffset: 0); + }, + displacement: 95, + child: NotificationListener( + onNotification: scrollListener, + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder( + itemCount: logsProvider.isLoadingMore + ? logsProvider.logsData!.data.length + 1 + : logsProvider.logsData!.data.length, + itemBuilder: (context, index) { + if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: CircularProgressIndicator(), + ), + ); } - ), - if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.noLogsDisplay, + else if (logsProvider.logsData!.data[index].question.name != null) { + return LogTile( + log: logsProvider.logsData!.data[index], + index: index, + length: logsProvider.logsData!.data.length, + isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index], + onLogTap: (log) { + if (!widget.twoColumns) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LogDetailsScreen( + log: log, + dialog: false, + ) + ) + ); + } + widget.onLogSelected(log); + }, + twoColumns: widget.twoColumns, + ); + } + else { + return null; + } + } + ), + if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context)!.noLogsDisplay, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + if (logsProvider.logsOlderThan != null) Padding( + padding: const EdgeInsets.only( + top: 30, + left: 20, + right: 20 + ), + child: Text( + AppLocalizations.of(context)!.noLogsThatOld, + textAlign: TextAlign.center, style: TextStyle( - fontSize: 24, + fontSize: 16, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), - if (logsProvider.logsOlderThan != null) Padding( - padding: const EdgeInsets.only( - top: 30, - left: 20, - right: 20 - ), - child: Text( - AppLocalizations.of(context)!.noLogsThatOld, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ] - ), - ), - ) - ], - ), - ), - ), - ) - ); - - case LoadStatus.error: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverFillRemaining( - child: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.logsNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ) - ], + ] + ), ), ) - ) - ], + ], + ), ), - ) - ); - - default: - return const SizedBox(); - } - }, - ) - ), + ), + ) + ); + + case LoadStatus.error: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + child: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.logsNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ) + ) + ], + ), + ) + ); + + default: + return const SizedBox(); + } + }, + ) ), ); } diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart index 21e03b1..f467d26 100644 --- a/lib/screens/logs/logs_list_appbar.dart +++ b/lib/screens/logs/logs_list_appbar.dart @@ -6,19 +6,19 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/logs/live/live_logs_screen.dart'; import 'package:adguard_home_manager/screens/logs/filters/logs_filters_modal.dart'; -import 'package:adguard_home_manager/widgets/floating_search_bar.dart'; +import 'package:adguard_home_manager/screens/logs/configuration/logs_config_modal.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:adguard_home_manager/providers/live_logs_provider.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; -final GlobalKey _searchButtonKey = GlobalKey(); - class LogsListAppBar extends StatelessWidget { final bool innerBoxIsScrolled; final bool showDivider; @@ -32,9 +32,61 @@ class LogsListAppBar extends StatelessWidget { @override Widget build(BuildContext context) { final logsProvider = Provider.of(context); + final statusProvider = Provider.of(context); + final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; + void updateConfig(Map data) async { + ProcessModal processModal = ProcessModal(); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = await serversProvider.apiClient2!.updateQueryLogParameters(data: data); + + processModal.close(); + + if (result.successful == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigUpdated, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigNotUpdated, + color: Colors.red + ); + } + } + + void clearQueries() async { + ProcessModal processModal = ProcessModal(); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = await serversProvider.apiClient2!.clearLogs(); + + processModal.close(); + + if (result.successful == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsCleared, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsNotCleared, + color: Colors.red + ); + } + } + + void openFilersModal() { if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { showDialog( @@ -58,53 +110,6 @@ class LogsListAppBar extends StatelessWidget { } } - void showSearchDialog() { - showDialog( - context: context, - builder: (context) => FloatingSearchBar( - existingSearchValue: logsProvider.appliedFilters.searchText ?? "", - searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?, - onSearchCompleted: (v) { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, - searchText: v != "" ? v : null, - clients: logsProvider.appliedFilters.clients - ) - ); - logsProvider.filterLogs(); - }, - onSearchFieldUpdated: (v) { - if (v == "") { - logsProvider.setSearchText(null); - return; - } - logsProvider.setSearchText(v); - }, - onSearchFieldCleared: () { - logsProvider.setSearchText(null); - }, - ), - ); - } - - void openLiveLogsScreen() { - if (!context.mounted) return; - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => MultiProvider( - providers: [ - ChangeNotifierProxyProvider( - create: (context) => LiveLogsProvider(), - update: (context, servers, logs) => logs!..update(servers), - ), - ], - child: const LiveLogsScreen() - ) - ) - ); - } - final Map translatedString = { "all": AppLocalizations.of(context)!.all, "filtered": AppLocalizations.of(context)!.filtered, @@ -123,45 +128,58 @@ class LogsListAppBar extends StatelessWidget { forceElevated: innerBoxIsScrolled, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text(AppLocalizations.of(context)!.logs), + expandedHeight: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null + ? 170 : null, actions: [ if (!(Platform.isAndroid || Platform.isIOS)) IconButton( onPressed: () => logsProvider.fetchLogs(inOffset: 0), icon: const Icon(Icons.refresh_rounded), tooltip: AppLocalizations.of(context)!.refresh, ), - if (logsProvider.loadStatus == LoadStatus.loaded) IconButton( - key: _searchButtonKey, - onPressed: showSearchDialog, - icon: const Icon(Icons.search_rounded), - tooltip: AppLocalizations.of(context)!.search, - ), - if (logsProvider.loadStatus == LoadStatus.loaded) PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - onTap: openFilersModal, - child: Row( - children: [ - const Icon(Icons.filter_list_rounded), - const SizedBox(width: 10), - Text(AppLocalizations.of(context)!.filters) - ], + logsProvider.loadStatus == LoadStatus.loaded + ? IconButton( + onPressed: openFilersModal, + icon: const Icon(Icons.filter_list_rounded), + tooltip: AppLocalizations.of(context)!.filters, + ) + : const SizedBox(), + if (statusProvider.serverStatus != null) IconButton( + tooltip: AppLocalizations.of(context)!.settings, + onPressed: () => { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => LogsConfigModal( + context: context, + onConfirm: updateConfig, + onClear: clearQueries, + dialog: true, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + barrierDismissible: false ) - ), - PopupMenuItem( - onTap: openLiveLogsScreen, - child: Row( - children: [ - const Icon(Icons.stream_rounded), - const SizedBox(width: 10), - Text(AppLocalizations.of(context)!.liveLogs) - ], + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => LogsConfigModal( + context: context, + onConfirm: updateConfig, + onClear: clearQueries, + dialog: false, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true ) - ), - ], + } + }, + icon: const Icon(Icons.settings) ), - const SizedBox(width: 8), + const SizedBox(width: 5), ], - bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients.isNotEmpty + bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null ? PreferredSize( preferredSize: const Size(double.maxFinite, 70), child: Container( @@ -238,7 +256,7 @@ class LogsListAppBar extends StatelessWidget { clients: logsProvider.appliedFilters.clients ) ); - logsProvider.setSelectedResultStatus(value: 'all'); + logsProvider.setSelectedResultStatus('all'); logsProvider.fetchLogs( inOffset: 0, responseStatus: 'all' @@ -246,7 +264,7 @@ class LogsListAppBar extends StatelessWidget { }, ), ], - if (logsProvider.appliedFilters.clients.isNotEmpty) ...[ + if (logsProvider.appliedFilters.clients != null) ...[ const SizedBox(width: 15), Chip( avatar: const Icon( @@ -255,9 +273,9 @@ class LogsListAppBar extends StatelessWidget { label: Row( children: [ Text( - logsProvider.appliedFilters.clients.length == 1 - ? logsProvider.appliedFilters.clients[0] - : "${logsProvider.appliedFilters.clients.length} ${AppLocalizations.of(context)!.clients}", + logsProvider.appliedFilters.clients!.length == 1 + ? logsProvider.appliedFilters.clients![0] + : "${logsProvider.appliedFilters.clients!.length} ${AppLocalizations.of(context)!.clients}", ), ], ), @@ -270,7 +288,7 @@ class LogsListAppBar extends StatelessWidget { AppliedFiters( selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, searchText: logsProvider.appliedFilters.searchText, - clients: [] + clients: null ) ); logsProvider.setSelectedClients(null); @@ -289,5 +307,4 @@ class LogsListAppBar extends StatelessWidget { : null, ); } -} - +} \ No newline at end of file diff --git a/lib/screens/servers/servers.dart b/lib/screens/servers/servers.dart index 25bca06..180af77 100644 --- a/lib/screens/servers/servers.dart +++ b/lib/screens/servers/servers.dart @@ -16,9 +16,9 @@ class Servers extends StatefulWidget { final double? breakingWidth; const Servers({ - super.key, + Key? key, this.breakingWidth - }); + }) : super(key: key); @override State createState() => _ServersState(); @@ -77,31 +77,29 @@ class _ServersState extends State { title: Text(AppLocalizations.of(context)!.servers), centerTitle: false, ), - body: SafeArea( - child: Stack( - children: [ - ServersList( - context: context, - controllers: expandableControllerList, - onChange: expandOrContract, - scrollController: scrollController, - breakingWidth: widget.breakingWidth ?? 700, + body: Stack( + children: [ + ServersList( + context: context, + controllers: expandableControllerList, + onChange: expandOrContract, + scrollController: scrollController, + breakingWidth: widget.breakingWidth ?? 700, + ), + AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: isVisible ? + appConfigProvider.showingSnackbar + ? 70 : (Platform.isIOS ? 40 : 20) + : -70, + right: 20, + child: FloatingActionButton( + onPressed: openAddServerModal, + child: const Icon(Icons.add), ), - AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: isVisible ? - appConfigProvider.showingSnackbar - ? 70 : (Platform.isIOS ? 40 : 20) - : -70, - right: 20, - child: FloatingActionButton( - onPressed: openAddServerModal, - child: const Icon(Icons.add), - ), - ), - ], - ), + ), + ], ), ); } diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index 9c1e986..d5673f7 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -49,7 +49,6 @@ class _AccessSettingsState extends State with TickerProviderStat handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: SliverSafeArea( top: false, - bottom: false, sliver: SliverAppBar( title: Text(AppLocalizations.of(context)!.accessSettings), pinned: true, @@ -57,7 +56,10 @@ class _AccessSettingsState extends State with TickerProviderStat centerTitle: false, forceElevated: innerBoxIsScrolled, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - bottom: _Tabs(tabController: _tabController) + bottom: PreferredSize( + preferredSize: const Size(double.maxFinite, 50), + child: _Tabs(tabController: _tabController) + ) ), ), ) @@ -90,7 +92,7 @@ class _AccessSettingsState extends State with TickerProviderStat } } -class _Tabs extends StatelessWidget implements PreferredSizeWidget { +class _Tabs extends StatelessWidget { final TabController tabController; const _Tabs({ @@ -135,9 +137,6 @@ class _Tabs extends StatelessWidget implements PreferredSizeWidget { ] ); } - - @override - Size get preferredSize => const Size.fromHeight(kToolbarHeight); } class _TabsView extends StatelessWidget { diff --git a/lib/screens/settings/access_settings/add_client_modal.dart b/lib/screens/settings/access_settings/add_client_modal.dart index bb0385a..014babd 100644 --- a/lib/screens/settings/access_settings/add_client_modal.dart +++ b/lib/screens/settings/access_settings/add_client_modal.dart @@ -44,11 +44,9 @@ class AddClientModal extends StatelessWidget { topRight: Radius.circular(28) ) ), - child: SafeArea( - child: _Content( - type: type, - onConfirm: onConfirm, - ), + child: _Content( + type: type, + onConfirm: onConfirm, ) ), ); diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index bf2c4d4..0b83a4d 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -71,7 +71,7 @@ class _ClientsListState extends State { Future refetchClients() async { final result = await clientsProvider.fetchClients(); if (result == false && mounted) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientsNotLoaded, color: Colors.red @@ -104,21 +104,21 @@ class _ClientsListState extends State { processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientRemovedSuccessfully, color: Colors.green ); } else if (result.successful == false && result.content == 'client_another_list') { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientAnotherList, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed ? AppLocalizations.of(context)!.clientNotRemoved @@ -137,21 +137,21 @@ class _ClientsListState extends State { processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientAddedSuccessfully, color: Colors.green ); } else if (result.successful == false && result.content == 'client_another_list') { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientAnotherList, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed ? AppLocalizations.of(context)!.clientNotRemoved diff --git a/lib/screens/settings/advanced_setings.dart b/lib/screens/settings/advanced_setings.dart index 6026082..39a93e3 100644 --- a/lib/screens/settings/advanced_setings.dart +++ b/lib/screens/settings/advanced_setings.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class AdvancedSettings extends StatelessWidget { - const AdvancedSettings({super.key}); + const AdvancedSettings({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -26,14 +26,14 @@ class AdvancedSettings extends StatelessWidget { final result = await function(newStatus); if (!context.mounted) return; if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.settingsUpdatedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.cannotUpdateSettings, color: Colors.red @@ -46,33 +46,31 @@ class AdvancedSettings extends StatelessWidget { title: Text(AppLocalizations.of(context)!.advancedSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), - body: SafeArea( - child: ListView( - children: [ - CustomListTile( - icon: Icons.lock, - title: AppLocalizations.of(context)!.dontCheckCertificate, - subtitle: AppLocalizations.of(context)!.dontCheckCertificateDescription, - trailing: Switch( - value: appConfigProvider.overrideSslCheck, - onChanged: (value) => updateSettings( - newStatus: value, - function: appConfigProvider.setOverrideSslCheck - ), - ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.overrideSslCheck, + body: ListView( + children: [ + CustomListTile( + icon: Icons.lock, + title: AppLocalizations.of(context)!.dontCheckCertificate, + subtitle: AppLocalizations.of(context)!.dontCheckCertificateDescription, + trailing: Switch( + value: appConfigProvider.overrideSslCheck, + onChanged: (value) => updateSettings( + newStatus: value, function: appConfigProvider.setOverrideSslCheck ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 20, - right: 10 - ) ), - ], - ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.overrideSslCheck, + function: appConfigProvider.setOverrideSslCheck + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 20, + right: 10 + ) + ), + ], ) ); } diff --git a/lib/screens/settings/app_logs/app_log_details_modal.dart b/lib/screens/settings/app_logs/app_log_details_modal.dart new file mode 100644 index 0000000..26d07f2 --- /dev/null +++ b/lib/screens/settings/app_logs/app_log_details_modal.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/models/app_log.dart'; + +class AppLogDetailsModal extends StatefulWidget { + final AppLog log; + + const AppLogDetailsModal({ + Key? key, + required this.log + }) : super(key: key); + + @override + State createState() => _AppLogDetailsModalState(); +} + +class _AppLogDetailsModalState extends State { + String valueToShow = 'message'; + + String generateBody() { + switch (valueToShow) { + case 'message': + return widget.log.message; + + case 'statusCode': + return widget.log.statusCode != null + ? widget.log.statusCode.toString() + : "[NO STAUS CODE]"; + + case 'body': + return widget.log.resBody != null + ? widget.log.resBody.toString() + : "[NO RESPONSE BODY]"; + + default: + return ''; + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Column( + children: [ + Icon( + Icons.description_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.logDetails, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + scrollable: true, + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 20), + Row( + children: [ + Material( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + bottomLeft: Radius.circular(15) + ), + color: Colors.transparent, + child: InkWell( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + bottomLeft: Radius.circular(15) + ), + onTap: () => setState(() => valueToShow = 'message'), + child: AnimatedContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + bottomLeft: Radius.circular(15) + ), + border: Border.all( + color: Theme.of(context).colorScheme.primary + ), + color: valueToShow == 'message' + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.primary.withOpacity(0.05) + ), + child: Text( + "Message", + style: TextStyle( + color: valueToShow == 'message' + ? Colors.white + : null + ), + ), + ), + ), + ), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => setState(() => valueToShow = 'statusCode'), + child: AnimatedContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Theme.of(context).colorScheme.primary + ), + bottom: BorderSide( + color: Theme.of(context).colorScheme.primary + ), + ), + color: valueToShow == 'statusCode' + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.primary.withOpacity(0.05) + ), + child: Text( + "Status code", + style: TextStyle( + color: valueToShow == 'statusCode' + ? Colors.white + : null + ), + ), + ), + ), + ), + Material( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(15), + bottomRight: Radius.circular(15) + ), + color: Colors.transparent, + child: InkWell( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(15), + bottomRight: Radius.circular(15) + ), + onTap: () => setState(() => valueToShow = 'body'), + child: AnimatedContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(15), + bottomRight: Radius.circular(15) + ), + border: Border.all( + color: Theme.of(context).colorScheme.primary + ), + color: valueToShow == 'body' + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.primary.withOpacity(0.05) + ), + child: Text( + "Body", + style: TextStyle( + color: valueToShow == 'body' + ? Colors.white + : null + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 20), + Text(generateBody()) + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text("Close") + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/app_logs/app_logs.dart b/lib/screens/settings/app_logs/app_logs.dart new file mode 100644 index 0000000..19a8502 --- /dev/null +++ b/lib/screens/settings/app_logs/app_logs.dart @@ -0,0 +1,81 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:convert'; + +import 'package:adguard_home_manager/functions/copy_clipboard.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/settings/app_logs/app_log_details_modal.dart'; + +import 'package:adguard_home_manager/providers/app_config_provider.dart'; + +class AppLogs extends StatelessWidget { + const AppLogs({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(context); + + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.logs), + actions: [ + IconButton( + onPressed: appConfigProvider.logs.isNotEmpty + ? () => copyToClipboard( + value: jsonEncode(appConfigProvider.logs.map((log) => log.toMap()).toList()), + successMessage: AppLocalizations.of(context)!.logsCopiedClipboard + ) + : null, + icon: const Icon(Icons.share), + tooltip: AppLocalizations.of(context)!.copyLogsClipboard, + ), + const SizedBox(width: 10), + ], + ), + body: appConfigProvider.logs.isNotEmpty + ? ListView.builder( + padding: const EdgeInsets.only(top: 0), + itemCount: appConfigProvider.logs.length, + itemBuilder: (context, index) => ListTile( + title: Text( + appConfigProvider.logs[index].message, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Theme.of(context).colorScheme.onSurface + ), + ), + subtitle: Text( + appConfigProvider.logs[index].dateTime.toString(), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).listTileTheme.textColor + ), + ), + trailing: Text(appConfigProvider.logs[index].type), + onTap: () => { + showDialog( + context: context, + builder: (context) => AppLogDetailsModal( + log: appConfigProvider.logs[index] + ) + ) + }, + ) + ) + : Center( + child: Text( + AppLocalizations.of(context)!.noSavedLogs, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/customization/color_item.dart b/lib/screens/settings/customization/color_item.dart index 00873d0..0d6ea03 100644 --- a/lib/screens/settings/customization/color_item.dart +++ b/lib/screens/settings/customization/color_item.dart @@ -1,37 +1,29 @@ import 'package:flutter/material.dart'; class ColorItem extends StatelessWidget { - final int index; - final int total; final Color color; final int numericValue; final int? selectedValue; final void Function(int) onChanged; const ColorItem({ - super.key, - required this.index, - required this.total, + Key? key, required this.color, required this.numericValue, required this.selectedValue, required this.onChanged - }); + }) : super(key: key); @override Widget build(BuildContext context) { return Padding( - padding: index == 0 - ? const EdgeInsets.only(top: 10, right: 10, bottom: 10) - : index == total-1 - ? const EdgeInsets.only(top: 10, bottom: 10, left: 10) - : const EdgeInsets.all(10), + padding: const EdgeInsets.all(10), child: Material( borderRadius: BorderRadius.circular(50), child: InkWell( onTap: () => onChanged(numericValue), borderRadius: BorderRadius.circular(50), - overlayColor: const WidgetStatePropertyAll(Colors.grey), + overlayColor: const MaterialStatePropertyAll(Colors.grey), child: Container( width: 50, height: 50, diff --git a/lib/screens/settings/customization/customization.dart b/lib/screens/settings/customization/customization.dart index 38f93a3..6ddd5e4 100644 --- a/lib/screens/settings/customization/customization.dart +++ b/lib/screens/settings/customization/customization.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -16,7 +14,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/constants/colors.dart'; class Customization extends StatelessWidget { - const Customization({super.key}); + const Customization({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -32,9 +30,9 @@ class CustomizationWidget extends StatefulWidget { final AppConfigProvider appConfigProvider; const CustomizationWidget({ - super.key, + Key? key, required this.appConfigProvider, - }); + }) : super(key: key); @override State createState() => _CustomizationWidgetState(); @@ -46,8 +44,6 @@ class _CustomizationWidgetState extends State { int selectedColor = 0; bool useThemeColorInsteadGreenRed = false; - final _colorsScrollController = ScrollController(); - @override void initState() { selectedTheme = widget.appConfigProvider.selectedThemeNumber; @@ -69,149 +65,152 @@ class _CustomizationWidgetState extends State { centerTitle: false, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), - body: SafeArea( - child: ListView( - children: [ - SectionLabel( - label: AppLocalizations.of(context)!.theme, - padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 5), - ), - Column( - children: [ - CustomSwitchListTile( - value: selectedTheme == 0 ? true : false, - onChanged: (value) { - selectedTheme = value == true ? 0 : 1; - appConfigProvider.setSelectedTheme(value == true ? 0 : 1); - }, - title: AppLocalizations.of(context)!.systemDefined, - ), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ThemeModeButton( - icon: Icons.light_mode, - value: 1, - selected: selectedTheme, - label: AppLocalizations.of(context)!.light, - onChanged: (value) { - selectedTheme = value; - appConfigProvider.setSelectedTheme(value); - }, - disabled: selectedTheme == 0 ? true : false, - ), - ThemeModeButton( - icon: Icons.dark_mode, - value: 2, - selected: selectedTheme, - label: AppLocalizations.of(context)!.dark, - onChanged: (value) { - selectedTheme = value; - appConfigProvider.setSelectedTheme(value); - }, - disabled: selectedTheme == 0 ? true : false, - ), - ], - ), - ], - ), - SectionLabel( - label: AppLocalizations.of(context)!.color, - padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5), - ), - if (appConfigProvider.supportsDynamicTheme) CustomSwitchListTile( - value: dynamicColor, - onChanged: (value) { - setState(() => dynamicColor = value); - appConfigProvider.setUseDynamicColor(value); - }, - title: AppLocalizations.of(context)!.useDynamicTheme, - ), - if (!(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31)) const SizedBox(height: 20), - if ( - appConfigProvider.supportsDynamicTheme == false || - (appConfigProvider.supportsDynamicTheme == true && dynamicColor == false) - ) Padding( - padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16), - child: Scrollbar( - controller: _colorsScrollController, - thumbVisibility: Platform.isMacOS || Platform.isLinux || Platform.isWindows, - interactive: Platform.isMacOS || Platform.isLinux || Platform.isWindows, - thickness: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 8 : 0, - child: Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width, - height: 70, - child: ListView.builder( - controller: _colorsScrollController, - scrollDirection: Axis.horizontal, - itemCount: colors.length, - padding: const EdgeInsets.all(0), - itemBuilder: (context, index) { - if (index == 0) { - return Row( - children: [ - ColorItem( - index: index, - total: colors.length, - color: colors[index], - numericValue: index, - selectedValue: selectedColor, - onChanged: (value) { - setState(() => selectedColor = value); - appConfigProvider.setStaticColor(value); - } - ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - width: 1, - height: 60, - decoration: BoxDecoration( - color: Colors.grey, - borderRadius: BorderRadius.circular(1) - ), - ) - ], - ); - } - else { - return ColorItem( - index: index, - total: colors.length, - color: colors[index], - numericValue: index, - selectedValue: selectedColor, - onChanged: (value) { - setState(() => selectedColor = value); - appConfigProvider.setStaticColor(value); - } - ); - } - }, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Text( - colorTranslation(context, selectedColor), - style: TextStyle( - color: Theme.of(context).listTileTheme.iconColor, - fontSize: 16 - ), - ), - ) - ], + body: ListView( + children: [ + SectionLabel( + label: AppLocalizations.of(context)!.theme, + padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 5), + ), + Column( + children: [ + CustomSwitchListTile( + value: selectedTheme == 0 ? true : false, + onChanged: (value) { + selectedTheme = value == true ? 0 : 1; + appConfigProvider.setSelectedTheme(value == true ? 0 : 1); + }, + title: AppLocalizations.of(context)!.systemDefined, + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ThemeModeButton( + icon: Icons.light_mode, + value: 1, + selected: selectedTheme, + label: AppLocalizations.of(context)!.light, + onChanged: (value) { + selectedTheme = value; + appConfigProvider.setSelectedTheme(value); + }, + disabled: selectedTheme == 0 ? true : false, ), - ), + ThemeModeButton( + icon: Icons.dark_mode, + value: 2, + selected: selectedTheme, + label: AppLocalizations.of(context)!.dark, + onChanged: (value) { + selectedTheme = value; + appConfigProvider.setSelectedTheme(value); + }, + disabled: selectedTheme == 0 ? true : false, + ), + ], + ), + ], + ), + SectionLabel( + label: AppLocalizations.of(context)!.color, + padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5), + ), + if (appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31) CustomSwitchListTile( + value: dynamicColor, + onChanged: (value) { + setState(() => dynamicColor = value); + appConfigProvider.setUseDynamicColor(value); + }, + title: AppLocalizations.of(context)!.useDynamicTheme, + ), + if (!(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31)) const SizedBox(height: 20), + if (dynamicColor == false) ...[ + SizedBox( + width: MediaQuery.of(context).size.width, + height: 70, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: colors.length, + itemBuilder: (context, index) { + if (index == 0) { + return Row( + children: [ + const SizedBox(width: 15), + ColorItem( + color: colors[index], + numericValue: index, + selectedValue: selectedColor, + onChanged: (value) { + setState(() => selectedColor = value); + appConfigProvider.setStaticColor(value); + } + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + width: 1, + height: 60, + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(1) + ), + ) + ], + ); + } + else if (index == colors.length-1) { + return Row( + children: [ + ColorItem( + color: colors[index], + numericValue: index, + selectedValue: selectedColor, + onChanged: (value) { + setState(() => selectedColor = value); + appConfigProvider.setStaticColor(value); + } + ), + const SizedBox(width: 15) + ], + ); + } + else { + return ColorItem( + color: colors[index], + numericValue: index, + selectedValue: selectedColor, + onChanged: (value) { + setState(() => selectedColor = value); + appConfigProvider.setStaticColor(value); + } + ); + } + }, ), ), + Padding( + padding: const EdgeInsets.only( + left: 25, + top: 10 + ), + child: Text( + colorTranslation(context, selectedColor), + style: TextStyle( + color: Theme.of(context).listTileTheme.iconColor, + fontSize: 16 + ), + ), + ) ], - ), + CustomSwitchListTile( + value: useThemeColorInsteadGreenRed, + onChanged: (value) { + setState(() => useThemeColorInsteadGreenRed = value); + appConfigProvider.setUseThemeColorForStatus(value); + }, + title: AppLocalizations.of(context)!.useThemeColorStatus, + subtitle: AppLocalizations.of(context)!.useThemeColorStatusDescription, + ) + ], ), ); } diff --git a/lib/screens/settings/customization/theme_mode_button.dart b/lib/screens/settings/customization/theme_mode_button.dart index ec949c0..df3f8d1 100644 --- a/lib/screens/settings/customization/theme_mode_button.dart +++ b/lib/screens/settings/customization/theme_mode_button.dart @@ -9,14 +9,14 @@ class ThemeModeButton extends StatelessWidget { final bool? disabled; const ThemeModeButton({ - super.key, + Key? key, required this.icon, required this.value, required this.selected, required this.label, required this.onChanged, this.disabled - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -32,19 +32,19 @@ class ThemeModeButton extends StatelessWidget { ? () => onChanged(value) : null, style: ButtonStyle( - elevation: WidgetStateProperty.all(0), - shape: WidgetStateProperty.all( + elevation: MaterialStateProperty.all(0), + shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ) ), - backgroundColor: WidgetStateProperty.all( + backgroundColor: MaterialStateProperty.all( value == selected ? disabled == null || disabled == false ? Theme.of(context).colorScheme.primary : greyBackgroundColor : disabled == null || disabled == false - ? Theme.of(context).colorScheme.surfaceTint.withOpacity(0.1) + ? Theme.of(context).colorScheme.surfaceVariant : greyBackgroundColor, ) ), diff --git a/lib/screens/settings/dhcp/add_static_lease_modal.dart b/lib/screens/settings/dhcp/add_static_lease_modal.dart index d40c506..fd222d3 100644 --- a/lib/screens/settings/dhcp/add_static_lease_modal.dart +++ b/lib/screens/settings/dhcp/add_static_lease_modal.dart @@ -1,10 +1,9 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/models/dhcp.dart'; -class AddStaticLeaseModal extends StatelessWidget { +class AddStaticLeaseModal extends StatefulWidget { final void Function(Lease) onConfirm; final bool dialog; @@ -15,49 +14,10 @@ class AddStaticLeaseModal extends StatelessWidget { }); @override - Widget build(BuildContext context) { - if (dialog == true) { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400 - ), - child: _Content(onConfirm: onConfirm) - ), - ); - } - else { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) - ), - child: SafeArea( - child: _Content(onConfirm: onConfirm) - ) - ), - ); - } - } + State createState() => _AddStaticLeaseModalState(); } -class _Content extends StatefulWidget { - final void Function(Lease) onConfirm; - - const _Content({ - required this.onConfirm - }); - - @override - State<_Content> createState() => __ContentState(); -} - -class __ContentState extends State<_Content> { +class _AddStaticLeaseModalState extends State { final TextEditingController macController = TextEditingController(); String? macError; final TextEditingController ipController = TextEditingController(); @@ -68,7 +28,8 @@ class __ContentState extends State<_Content> { bool validData = false; void validateMac(String value) { - if (Regexps.macAddress.hasMatch(value)) { + final RegExp macRegex = RegExp(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$'); + if (macRegex.hasMatch(value)) { setState(() => macError = null); } else { @@ -78,7 +39,8 @@ class __ContentState extends State<_Content> { } void validateIp(String value) { - if (Regexps.ipv4Address.hasMatch(value) == true) { + RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); + if (ipAddress.hasMatch(value) == true) { setState(() => ipError = null); } else { @@ -105,147 +67,175 @@ class __ContentState extends State<_Content> { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.add, - size: 24, - color: Theme.of(context).listTileTheme.iconColor + Widget content() { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.add, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.addStaticLease, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.addStaticLease, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), ), - ), - ], + ], + ), ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 24, right: 24, bottom: 12 - ), - child: TextFormField( - controller: macController, - onChanged: validateMac, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.smartphone_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: macError, - labelText: AppLocalizations.of(context)!.macAddress, + ], ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: TextFormField( - controller: ipController, - onChanged: validateIp, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) + Padding( + padding: const EdgeInsets.only( + left: 24, right: 24, bottom: 12 + ), + child: TextFormField( + controller: macController, + onChanged: validateMac, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.smartphone_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: macError, + labelText: AppLocalizations.of(context)!.macAddress, ), - errorText: ipError, - labelText: AppLocalizations.of(context)!.ipAddress, ), ), - ), - Padding( - padding: const EdgeInsets.only( - left: 24, right: 24, top: 12 - ), - child: TextFormField( - controller: hostNameController, - onChanged: (value) { - if (value != '') { - setState(() => hostNameError = null); - } - else { - setState(() => hostNameError = AppLocalizations.of(context)!.hostNameError); - } - validateData(); - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.badge_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: TextFormField( + controller: ipController, + onChanged: validateIp, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipError, + labelText: AppLocalizations.of(context)!.ipAddress, ), - errorText: hostNameError, - labelText: AppLocalizations.of(context)!.hostName, + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 24, right: 24, top: 12 + ), + child: TextFormField( + controller: hostNameController, + onChanged: (value) { + if (value != '') { + setState(() => hostNameError = null); + } + else { + setState(() => hostNameError = AppLocalizations.of(context)!.hostNameError); + } + validateData(); + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.badge_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: hostNameError, + labelText: AppLocalizations.of(context)!.hostName, + ), + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel), + ), + const SizedBox(width: 20), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm( + Lease( + mac: macController.text, + hostname: hostNameController.text, + ip: ipController.text + ) + ); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).colorScheme.primary + : Colors.grey ), ), ), ], ), + ) + ], + ); + } + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 ), + child: content(), ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel), - ), - const SizedBox(width: 20), - TextButton( - onPressed: validData == true - ? () { - Navigator.pop(context); - widget.onConfirm( - Lease( - mac: macController.text, - hostname: hostNameController.text, - ip: ipController.text - ) - ); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: validData == true - ? Theme.of(context).colorScheme.primary - : Colors.grey - ), - ), - ), - ], + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ) ), - ) - ], - ); + child: content() + ), + ); + } } } \ No newline at end of file diff --git a/lib/screens/settings/dhcp/delete_static_lease_modal.dart b/lib/screens/settings/dhcp/delete_static_lease_modal.dart index f797e89..77ad241 100644 --- a/lib/screens/settings/dhcp/delete_static_lease_modal.dart +++ b/lib/screens/settings/dhcp/delete_static_lease_modal.dart @@ -5,9 +5,9 @@ class DeleteStaticLeaseModal extends StatelessWidget { final void Function() onConfirm; const DeleteStaticLeaseModal({ - super.key, + Key? key, required this.onConfirm - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index 76867ef..88f1dad 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -2,21 +2,17 @@ import 'dart:io'; -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/settings/settings.dart'; -import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_not_available.dart'; -import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; -import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_main_button.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; +import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/dhcp_provider.dart'; @@ -26,7 +22,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class DhcpScreen extends StatefulWidget { - const DhcpScreen({super.key}); + const DhcpScreen({Key? key}) : super(key: key); @override State createState() => _DhcpScreenState(); @@ -59,25 +55,24 @@ class _DhcpScreenState extends State { void loadDhcpStatus() async { final result = await Provider.of(context, listen: false).loadDhcpStatus(); - if (!mounted || result == false) return; - - final dhcpProvider = Provider.of(context, listen: false); - if (dhcpProvider.dhcp == null) return; - - setState(() { - if (dhcpProvider.dhcp!.dhcpStatus!.interfaceName != null && dhcpProvider.dhcp!.dhcpStatus!.interfaceName != '') { - try {selectedInterface = dhcpProvider.dhcp!.networkInterfaces.firstWhere((iface) => iface.name == dhcpProvider.dhcp!.dhcpStatus!.interfaceName);} catch (_) {} - enabled = dhcpProvider.dhcp!.dhcpStatus!.enabled; - if (dhcpProvider.dhcp!.dhcpStatus!.v4 != null) { - ipv4StartRangeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.rangeStart; - ipv4EndRangeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.rangeEnd ?? ''; - ipv4SubnetMaskController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.subnetMask ?? ''; - ipv4GatewayController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.gatewayIp ?? ''; - ipv4LeaseTimeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.leaseDuration.toString(); - } + if (mounted && result == true) { + final dhcpProvider = Provider.of(context, listen: false); + if (dhcpProvider.dhcp != null) { + setState(() { + if (dhcpProvider.dhcp!.dhcpStatus.interfaceName != null && dhcpProvider.dhcp!.dhcpStatus.interfaceName != '') { + try {selectedInterface = dhcpProvider.dhcp!.networkInterfaces.firstWhere((iface) => iface.name == dhcpProvider.dhcp!.dhcpStatus.interfaceName);} catch (_) {} + enabled = dhcpProvider.dhcp!.dhcpStatus.enabled; + if (dhcpProvider.dhcp!.dhcpStatus.v4 != null) { + ipv4StartRangeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.rangeStart; + ipv4EndRangeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.rangeEnd ?? ''; + ipv4SubnetMaskController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.subnetMask ?? ''; + ipv4GatewayController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.gatewayIp ?? ''; + ipv4LeaseTimeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.leaseDuration.toString(); + } + } + }); } - }); - + } checkDataValid(); } @@ -101,8 +96,8 @@ class _DhcpScreenState extends State { break; } } - - if (Regexps.ipv4Address.hasMatch(value)) { + final regex = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$'); + if (regex.hasMatch(value)) { setValue(null); } else { @@ -127,8 +122,8 @@ class _DhcpScreenState extends State { break; } } - - if (Regexps.ipv6Address.hasMatch(value)) { + final regex = RegExp(r'^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$'); + if (regex.hasMatch(value)) { setValue(null); } else { @@ -184,15 +179,13 @@ class _DhcpScreenState extends State { @override void initState() { - final statusProvider = Provider.of(context, listen: false); - if (mounted && statusProvider.serverStatus?.dhcpAvailable == true) loadDhcpStatus(); + if (mounted) loadDhcpStatus(); super.initState(); } @override Widget build(BuildContext context) { final serversProvider = Provider.of(context); - final statusProvider = Provider.of(context); final dhcpProvider = Provider.of(context); final appConfigProvider = Provider.of(context); @@ -222,14 +215,14 @@ class _DhcpScreenState extends State { if (!mounted) return; processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.settingsSaved, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.settingsNotSaved, color: Colors.red @@ -246,14 +239,14 @@ class _DhcpScreenState extends State { processModal.close(); if (result.successful == true) { clearAll(); - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.configRestored, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.configNotRestored, color: Colors.red @@ -273,18 +266,18 @@ class _DhcpScreenState extends State { if (result.successful == true) { DhcpModel data = dhcpProvider.dhcp!; - data.dhcpStatus!.staticLeases = []; - data.dhcpStatus!.leases = []; + data.dhcpStatus.staticLeases = []; + data.dhcpStatus.leases = []; dhcpProvider.setDhcpData(data); - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.leasesRestored, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.leasesNotRestored, color: Colors.red @@ -357,10 +350,6 @@ class _DhcpScreenState extends State { }); } - if (statusProvider.serverStatus?.dhcpAvailable != true) { - return const DhcpNotAvailable(); - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dhcpSettings), @@ -411,376 +400,503 @@ class _DhcpScreenState extends State { const SizedBox(width: 10) ] : null, ), - body: SafeArea( - child: Builder( - builder: (context) { - switch (dhcpProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + body: Builder( + builder: (context) { + switch (dhcpProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingDhcp, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + if (selectedInterface != null) { + return SingleChildScrollView( + child: Wrap( children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingDhcp, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 16, + right: 16 ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (selectedInterface != null) { - return SingleChildScrollView( - child: Wrap( - children: [ - DhcpMainButton( - selectedInterface: selectedInterface, - enabled: enabled, - setEnabled: (v) => setState(() => enabled = v) + child: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: selectedInterface != null + ? () => setState(() => enabled = !enabled) + : null, + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.enableDhcpServer, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + if (selectedInterface != null) ...[ + Text( + selectedInterface!.name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).listTileTheme.textColor + ), + ) + ] + ], + ), + Switch( + value: enabled, + onChanged: selectedInterface != null + ? (value) => setState(() => enabled = value) + : null, + ), + ], + ), + ), + ), ), - if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv4settings, - padding: const EdgeInsets.only( - top: 24, left: 16, right: 16, bottom: 8 - ) - ), - _DhcpField( - icon: Icons.skip_previous_rounded, - label: AppLocalizations.of(context)!.startOfRange, - controller: ipv4StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv4StartRangeError - ), - _DhcpField( - icon: Icons.skip_next_rounded, - label: AppLocalizations.of(context)!.endOfRange, - controller: ipv4EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv4EndRangeError - ), - _DhcpField( - icon: Icons.hub_rounded, - label: AppLocalizations.of(context)!.subnetMask, - controller: ipv4SubnetMaskController, - onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), - error: ipv4SubnetMaskError - ), - _DhcpField( - icon: Icons.router_rounded, - label: AppLocalizations.of(context)!.gateway, - controller: ipv4GatewayController, - onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), - error: ipv4GatewayError - ), - _DhcpField( - icon: Icons.timer, - label: AppLocalizations.of(context)!.leaseTime, - controller: ipv4LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv4LeaseTimeError = null); - } - else { - setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - error: ipv4LeaseTimeError - ), - ], - if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv6settings, - padding: const EdgeInsets.all(16) - ), - _DhcpField( - icon: Icons.skip_next_rounded, - label: AppLocalizations.of(context)!.startOfRange, - controller: ipv6StartRangeController, - onChanged: (value) => validateIpV6(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv6StartRangeError - ), - _DhcpField( - icon: Icons.skip_previous_rounded, - label: AppLocalizations.of(context)!.endOfRange, - controller: ipv6EndRangeController, - onChanged: (value) => validateIpV6(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv6EndRangeError - ), - _DhcpField( - icon: Icons.timer, - label: AppLocalizations.of(context)!.leaseTime, - controller: ipv6LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv6LeaseTimeError = null); - } - else { - setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - error: ipv6LeaseTimeError - ) - ], - const SizedBox(height: 20), + ), + if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ SectionLabel( - label: AppLocalizations.of(context)!.dhcpLeases, - padding: const EdgeInsets.all(16), + label: AppLocalizations.of(context)!.ipv4settings, + padding: const EdgeInsets.only( + top: 24, left: 16, right: 16, bottom: 8 + ) ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.leases, - staticLeases: false, + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) ) - ) - ); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpLeases, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], + ), + errorText: ipv4StartRangeError, + labelText: AppLocalizations.of(context)!.startOfRange, ), + keyboardType: TextInputType.number, ), ), ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, - staticLeases: true, + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_next_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) ) - ) - ); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpStatic, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], + ), + errorText: ipv4EndRangeError, + labelText: AppLocalizations.of(context)!.endOfRange, ), + keyboardType: TextInputType.number, ), ), ), - if (width > 900) Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - Navigator.of(settingsNavigatorKey.currentContext!).push( - MaterialPageRoute(builder: (ctx) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.leases, - staticLeases: false, - )) - ); + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4SubnetMaskController, + onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.hub_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4SubnetMaskError, + labelText: AppLocalizations.of(context)!.subnetMask, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4GatewayController, + onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.router_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4GatewayError, + labelText: AppLocalizations.of(context)!.gateway, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv4LeaseTimeError = null); } else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.leases, - staticLeases: false, - ) - ) - ); + setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); } }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpLeases), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4LeaseTimeError, + labelText: AppLocalizations.of(context)!.leaseTime, + ), + keyboardType: TextInputType.number, ), - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - Navigator.of(settingsNavigatorKey.currentContext!).push( - MaterialPageRoute(builder: (ctx) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, - staticLeases: true, - )) - ); + ), + ), + ], + if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ + SectionLabel( + label: AppLocalizations.of(context)!.ipv6settings, + padding: const EdgeInsets.all(16) + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_next_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6StartRangeError, + labelText: AppLocalizations.of(context)!.startOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6EndRangeError, + labelText: AppLocalizations.of(context)!.endOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv6LeaseTimeError = null); } else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, - staticLeases: true, - ) - ) - ); + setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); } - }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpStatic), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) - ), - ], - ), - const SizedBox(height: 10) - ], - ), - ); - } - else { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.neededSelectInterface, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) ), + errorText: ipv6LeaseTimeError, + labelText: AppLocalizations.of(context)!.leaseTime, ), + keyboardType: TextInputType.number, ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: selectInterface, - child: Text(AppLocalizations.of(context)!.selectInterface) + ), + ), + ], + const SizedBox(height: 20), + SectionLabel( + label: AppLocalizations.of(context)!.dhcpLeases, + padding: const EdgeInsets.all(16), + ), + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + ) + ); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpLeases, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], ), - ], + ), ), ), - ], - ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dhcpSettingsNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + ) + ); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpStatic, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], + ), + ), ), - ) + ), + if (width > 900) Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + ); + } + else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + ) + ); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpLeases), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + ); + } + else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + ) + ); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpStatic), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ], + ), + const SizedBox(height: 10) ], ), ); - - default: - return const SizedBox(); - } - }, - ), + } + else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.neededSelectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) + ), + ), + ), + const SizedBox(height: 30), + ElevatedButton( + onPressed: selectInterface, + child: Text(AppLocalizations.of(context)!.selectInterface) + ), + ], + ), + ), + ], + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dhcpSettingsNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, ) ); } -} - -class _DhcpField extends StatelessWidget { - final IconData icon; - final String label; - final TextEditingController controller; - final void Function(String) onChanged; - final String? error; - - const _DhcpField({ - required this.icon, - required this.label, - required this.controller, - required this.onChanged, - required this.error, - }); - - @override - Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width; - - return FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: controller, - onChanged: onChanged, - decoration: InputDecoration( - prefixIcon: Icon(icon), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: error, - labelText: label, - ), - keyboardType: TextInputType.number, - ), - ), - ); - } } \ No newline at end of file diff --git a/lib/screens/settings/dhcp/dhcp_leases.dart b/lib/screens/settings/dhcp/dhcp_leases.dart index 7b90d13..91bd1cf 100644 --- a/lib/screens/settings/dhcp/dhcp_leases.dart +++ b/lib/screens/settings/dhcp/dhcp_leases.dart @@ -22,10 +22,10 @@ class DhcpLeases extends StatelessWidget { final bool staticLeases; const DhcpLeases({ - super.key, + Key? key, required this.items, required this.staticLeases, - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -43,14 +43,14 @@ class DhcpLeases extends StatelessWidget { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.staticLeaseDeleted, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.staticLeaseNotDeleted, color: Colors.red @@ -67,28 +67,28 @@ class DhcpLeases extends StatelessWidget { processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.staticLeaseCreated, color: Colors.green ); } else if (result.successful == false && result.content == "already_exists") { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.staticLeaseExists, color: Colors.red ); } else if (result.successful == false && result.content == "server_not_configured") { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.serverNotConfigured, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.staticLeaseNotCreated, color: Colors.red @@ -130,34 +130,32 @@ class DhcpLeases extends StatelessWidget { ), ), body: items.isNotEmpty - ? SafeArea( - child: ListView.builder( - padding: const EdgeInsets.only(top: 0), - itemCount: items.length, - itemBuilder: (context, index) => ListTile( - isThreeLine: true, - title: Text(items[index].ip), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(items[index].mac), - Text(items[index].hostname), - ], - ), - trailing: staticLeases == true - ? IconButton( - onPressed: () { - showModal( - context: context, - builder: (context) => DeleteStaticLeaseModal( - onConfirm: () => deleteLease(items[index]) - ) - ); - }, - icon: const Icon(Icons.delete) - ) - : null, + ? ListView.builder( + padding: const EdgeInsets.only(top: 0), + itemCount: items.length, + itemBuilder: (context, index) => ListTile( + isThreeLine: true, + title: Text(items[index].ip), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(items[index].mac), + Text(items[index].hostname), + ], ), + trailing: staticLeases == true + ? IconButton( + onPressed: () { + showModal( + context: context, + builder: (context) => DeleteStaticLeaseModal( + onConfirm: () => deleteLease(items[index]) + ) + ); + }, + icon: const Icon(Icons.delete) + ) + : null, ), ) : Center( diff --git a/lib/screens/settings/dhcp/dhcp_main_button.dart b/lib/screens/settings/dhcp/dhcp_main_button.dart deleted file mode 100644 index 3e46a14..0000000 --- a/lib/screens/settings/dhcp/dhcp_main_button.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/models/dhcp.dart'; - -class DhcpMainButton extends StatelessWidget { - final NetworkInterface? selectedInterface; - final bool enabled; - final void Function(bool) setEnabled; - - const DhcpMainButton({ - super.key, - required this.selectedInterface, - required this.enabled, - required this.setEnabled, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only( - top: 10, - left: 16, - right: 16 - ), - child: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: selectedInterface != null - ? () => setEnabled(!enabled) - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.enableDhcpServer, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - if (selectedInterface != null) ...[ - Text( - selectedInterface!.name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).listTileTheme.textColor - ), - ) - ] - ], - ), - Switch( - value: enabled, - onChanged: selectedInterface != null - ? (value) => setEnabled(value) - : null, - ), - ], - ), - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/dhcp/dhcp_not_available.dart b/lib/screens/settings/dhcp/dhcp_not_available.dart deleted file mode 100644 index ceb3f6b..0000000 --- a/lib/screens/settings/dhcp/dhcp_not_available.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/functions/desktop_mode.dart'; - -class DhcpNotAvailable extends StatelessWidget { - const DhcpNotAvailable({super.key}); - - @override - Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width; - - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.dhcpSettings), - centerTitle: false, - surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - AppLocalizations.of(context)!.dhcpNotAvailable, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.w400, - fontSize: 24 - ), - ), - const SizedBox(height: 20), - Text( - AppLocalizations.of(context)!.osServerInstalledIncompatible, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/dhcp/select_interface_modal.dart b/lib/screens/settings/dhcp/select_interface_modal.dart index 40291e9..e2dc26d 100644 --- a/lib/screens/settings/dhcp/select_interface_modal.dart +++ b/lib/screens/settings/dhcp/select_interface_modal.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_interface_item.dart'; -import 'package:adguard_home_manager/widgets/list_bottom_sheet.dart'; import 'package:adguard_home_manager/models/dhcp.dart'; @@ -90,20 +89,69 @@ class SelectInterfaceModal extends StatelessWidget { ); } else { - return ListBottomSheet( - icon: Icons.settings_ethernet_rounded, - title: AppLocalizations.of(context)!.selectInterface, - children: [ - ListView.builder( - primary: false, - shrinkWrap: true, - itemCount: interfaces.length, - itemBuilder: (context, index) => DhcpInterfaceItem( - networkInterface: interfaces[index], - onSelect: onSelect - ) - ), - ] + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 1, + builder: (context, controller) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + ), + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(16), + width: 36, + height: 4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.grey + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Icon( + Icons.settings_ethernet_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.selectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + Expanded( + child: ListView.builder( + controller: controller, + itemCount: interfaces.length, + itemBuilder: (context, index) => DhcpInterfaceItem( + networkInterface: interfaces[index], + onSelect: onSelect + ) + ) + ), + const SizedBox(height: 16) + ], + ), + ); + }, + ), ); } } diff --git a/lib/screens/settings/dns/bootstrap_dns.dart b/lib/screens/settings/dns/bootstrap_dns.dart index dba929a..2ae3f4f 100644 --- a/lib/screens/settings/dns/bootstrap_dns.dart +++ b/lib/screens/settings/dns/bootstrap_dns.dart @@ -1,6 +1,5 @@ // ignore_for_file: use_build_context_synchronously -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -24,7 +23,8 @@ class _BootstrapDnsScreenState extends State { bool validValues = false; void validateIp(Map field, String value) { - if (Regexps.ipv4Address.hasMatch(value) == true || Regexps.ipv6Address.hasMatch(value) == true) { + RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)'); + if (ipAddress.hasMatch(value) == true) { setState(() => field['error'] = null); } else { @@ -80,21 +80,21 @@ class _BootstrapDnsScreenState extends State { processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } else if (result.successful == false && result.statusCode == 400) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigNotSaved, color: Colors.red @@ -117,107 +117,105 @@ class _BootstrapDnsScreenState extends State { const SizedBox(width: 10) ], ), - body: SafeArea( - child: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - Card( - margin: const EdgeInsets.only( - left: 16, right: 16, bottom: 20 - ), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 20), - Flexible( - child: Text( - AppLocalizations.of(context)!.bootstrapDnsServersInfo, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ) - ], - ), - ), + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + Card( + margin: const EdgeInsets.only( + left: 16, right: 16, bottom: 20 ), - const SizedBox(height: 10), - if (bootstrapControllers.isEmpty) Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - AppLocalizations.of(context)!.noBootstrapDns, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 16 - ), - ), - ), - ), - const SizedBox(height: 20), - ], - ), - ...bootstrapControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 16, right: 6, bottom: 20 - ), + child: Padding( + padding: const EdgeInsets.all(20), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: TextFormField( - controller: c['controller'], - onChanged: (value) => validateIp(c, value), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: c['error'], - labelText: AppLocalizations.of(context)!.dnsServer, - ) - ), + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, ), - const SizedBox(width: 8), - IconButton( - onPressed: () { - setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList()); - checkValidValues(); - }, - icon: const Icon(Icons.remove_circle_outline) + const SizedBox(width: 20), + Flexible( + child: Text( + AppLocalizations.of(context)!.bootstrapDnsServersInfo, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) ) ], ), - )), - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, + ), + ), + const SizedBox(height: 10), + if (bootstrapControllers.isEmpty) Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Center( + child: Text( + AppLocalizations.of(context)!.noBootstrapDns, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16 + ), + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ...bootstrapControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 16, right: 6, bottom: 20 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ElevatedButton.icon( + Expanded( + child: TextFormField( + controller: c['controller'], + onChanged: (value) => validateIp(c, value), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: c['error'], + labelText: AppLocalizations.of(context)!.dnsServer, + ) + ), + ), + const SizedBox(width: 8), + IconButton( onPressed: () { - setState(() => bootstrapControllers.add({ - 'controller': TextEditingController(), - 'error': null - })); + setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList()); checkValidValues(); }, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.addItem) - ), + icon: const Icon(Icons.remove_circle_outline) + ) ], ), - const SizedBox(height: 20) - ], - ), + )).toList(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() => bootstrapControllers.add({ + 'controller': TextEditingController(), + 'error': null + })); + checkValidValues(); + }, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), + const SizedBox(height: 20) + ], ), ); } diff --git a/lib/screens/settings/dns/cache_config.dart b/lib/screens/settings/dns/cache_config.dart index e2edb4d..7f6f63a 100644 --- a/lib/screens/settings/dns/cache_config.dart +++ b/lib/screens/settings/dns/cache_config.dart @@ -86,21 +86,21 @@ class _CacheConfigDnsScreenState extends State { processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } else if (result.successful== false && result.statusCode == 400) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigNotSaved, color: Colors.red @@ -139,14 +139,14 @@ class _CacheConfigDnsScreenState extends State { void clearCache() async { final result = await clearDnsCache(context, serversProvider.selectedServer!); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsCacheCleared, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsCacheNotCleared, color: Colors.red @@ -169,83 +169,81 @@ class _CacheConfigDnsScreenState extends State { const SizedBox(width: 10) ], ), - body: SafeArea( - child: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - numericField( - controller: cacheSizeController, - label: AppLocalizations.of(context)!.cacheSize, - helper: AppLocalizations.of(context)!.inBytes, - error: cacheSizeError, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => cacheSizeError = null); - } - else { - setState(() => cacheSizeError = AppLocalizations.of(context)!.valueNotNumber); - } - checkValidData(); + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + numericField( + controller: cacheSizeController, + label: AppLocalizations.of(context)!.cacheSize, + helper: AppLocalizations.of(context)!.inBytes, + error: cacheSizeError, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => cacheSizeError = null); } - ), - const SizedBox(height: 30), - numericField( - controller: overrideMinTtlController, - label: AppLocalizations.of(context)!.overrideMinimumTtl, - helper: AppLocalizations.of(context)!.overrideMinimumTtlDescription, - error: overrideMinTtlError, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => overrideMinTtlError = null); - } - else { - setState(() => overrideMinTtlError = AppLocalizations.of(context)!.valueNotNumber); - } - checkValidData(); + else { + setState(() => cacheSizeError = AppLocalizations.of(context)!.valueNotNumber); } - ), - const SizedBox(height: 30), - numericField( - controller: overrideMaxTtlController, - label: AppLocalizations.of(context)!.overrideMaximumTtl, - helper: AppLocalizations.of(context)!.overrideMaximumTtlDescription, - error: overrideMaxTtlError, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => overrideMaxTtlError = null); - } - else { - setState(() => overrideMaxTtlError = AppLocalizations.of(context)!.valueNotNumber); - } - checkValidData(); + checkValidData(); + } + ), + const SizedBox(height: 30), + numericField( + controller: overrideMinTtlController, + label: AppLocalizations.of(context)!.overrideMinimumTtl, + helper: AppLocalizations.of(context)!.overrideMinimumTtlDescription, + error: overrideMinTtlError, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => overrideMinTtlError = null); } - ), - const SizedBox(height: 10), - CustomSwitchListTile( - value: optimisticCache, - onChanged: (value) => setState(() => optimisticCache = value), - title: AppLocalizations.of(context)!.optimisticCaching, - subtitle: AppLocalizations.of(context)!.optimisticCachingDescription, - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton.icon( - onPressed: () => showDialog( - context: context, - builder: (context) => ClearDnsCacheDialog( - onConfirm: clearCache - ) - ), - icon: const Icon(Icons.delete_rounded), - label: Text(AppLocalizations.of(context)!.clearDnsCache), - ), - ], - ), - const SizedBox(height: 16) - ], - ), + else { + setState(() => overrideMinTtlError = AppLocalizations.of(context)!.valueNotNumber); + } + checkValidData(); + } + ), + const SizedBox(height: 30), + numericField( + controller: overrideMaxTtlController, + label: AppLocalizations.of(context)!.overrideMaximumTtl, + helper: AppLocalizations.of(context)!.overrideMaximumTtlDescription, + error: overrideMaxTtlError, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => overrideMaxTtlError = null); + } + else { + setState(() => overrideMaxTtlError = AppLocalizations.of(context)!.valueNotNumber); + } + checkValidData(); + } + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: optimisticCache, + onChanged: (value) => setState(() => optimisticCache = value), + title: AppLocalizations.of(context)!.optimisticCaching, + subtitle: AppLocalizations.of(context)!.optimisticCachingDescription, + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton.icon( + onPressed: () => showDialog( + context: context, + builder: (context) => ClearDnsCacheDialog( + onConfirm: clearCache + ) + ), + icon: const Icon(Icons.delete_rounded), + label: Text(AppLocalizations.of(context)!.clearDnsCache), + ), + ], + ), + const SizedBox(height: 16) + ], ), ); } diff --git a/lib/screens/settings/dns/comment_modal.dart b/lib/screens/settings/dns/comment_modal.dart index 92d4c0f..a9dd60d 100644 --- a/lib/screens/settings/dns/comment_modal.dart +++ b/lib/screens/settings/dns/comment_modal.dart @@ -1,70 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class CommentModal extends StatelessWidget { +class CommentModal extends StatefulWidget { final String? comment; final void Function(String) onConfirm; final bool dialog; const CommentModal({ - super.key, + Key? key, this.comment, required this.onConfirm, required this.dialog - }); + }) : super(key: key); @override - Widget build(BuildContext context) { - if (dialog == true) { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400 - ), - child: _Content( - comment: comment, - onConfirm: onConfirm, - ) - ), - ); - } - else { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ), - color: Theme.of(context).dialogBackgroundColor - ), - child: SafeArea( - child: _Content( - comment: comment, - onConfirm: onConfirm, - ), - ) - ), - ); - } - } + State createState() => _CommentModalState(); } -class _Content extends StatefulWidget { - final String? comment; - final void Function(String) onConfirm; - - const _Content({ - required this.comment, - required this.onConfirm - }); - - @override - State<_Content> createState() => __ContentState(); -} - -class __ContentState extends State<_Content> { +class _CommentModalState extends State { final TextEditingController commentController = TextEditingController(); bool validData = false; @@ -79,99 +32,127 @@ class __ContentState extends State<_Content> { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.comment_rounded, - size: 24, - color: Theme.of(context).colorScheme.secondary, + Widget content() { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.comment_rounded, + size: 24, + color: Theme.of(context).colorScheme.secondary, + ), ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.comment, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.comment, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), ), - ), - const SizedBox(height: 16), - ], - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: commentController, - onChanged: (value) { - if (value != '') { - setState(() => validData = true); - } - else { - setState(() => validData = false); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.comment_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) + const SizedBox(height: 16), + ], ), - labelText: AppLocalizations.of(context)!.comment, - helperText: AppLocalizations.of(context)!.commentsDescription, - helperMaxLines: 3 - ) + ], ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: commentController, + onChanged: (value) { + if (value != '') { + setState(() => validData = true); + } + else { + setState(() => validData = false); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.comment_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.comment, + helperText: AppLocalizations.of(context)!.commentsDescription, + helperMaxLines: 3 + ) + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 20), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm("# ${commentController.text}"); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ) ), ], ), + ) + ], + ); + } + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 ), + child: content() ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 20), - TextButton( - onPressed: validData == true - ? () { - Navigator.pop(context); - widget.onConfirm("# ${commentController.text}"); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: validData == true - ? Theme.of(context).colorScheme.primary - : Colors.grey - ), - ) - ), - ], + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ), + color: Theme.of(context).dialogBackgroundColor ), - ) - ], - ); + child: content() + ), + ); + } } } \ No newline at end of file diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 564fff2..02ec5d6 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -1,12 +1,10 @@ // ignore_for_file: use_build_context_synchronously import 'package:flutter/material.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/settings/settings.dart'; -import 'package:adguard_home_manager/screens/settings/dns/fallback_dns.dart'; -import 'package:adguard_home_manager/screens/settings/dns/test_upstream_dns_modal.dart'; import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart'; import 'package:adguard_home_manager/screens/settings/dns/cache_config.dart'; import 'package:adguard_home_manager/screens/settings/dns/dns_server_settings.dart'; @@ -27,9 +25,9 @@ class DnsSettings extends StatefulWidget { final bool splitView; const DnsSettings({ - super.key, + Key? key, required this.splitView, - }); + }) : super(key: key); @override State createState() => _DnsSettingsState(); @@ -52,7 +50,7 @@ class _DnsSettingsState extends State { void navigate(Widget w) { if (widget.splitView) { - Navigator.of(settingsNavigatorKey.currentContext!).push(MaterialPageRoute(builder: (ctx) => w)); + SplitView.of(context).push(w); } else { Navigator.of(context).push( @@ -66,14 +64,14 @@ class _DnsSettingsState extends State { void clearCache() async { final result = await clearDnsCache(context, serversProvider.selectedServer!); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsCacheCleared, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsCacheNotCleared, color: Colors.red @@ -86,14 +84,6 @@ class _DnsSettingsState extends State { title: Text(AppLocalizations.of(context)!.dnsSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ - IconButton( - onPressed: () => showDialog( - context: context, - builder: (ctx) => const TestUpstreamDnsModal() - ), - icon: const Icon(Icons.upload_rounded), - tooltip: AppLocalizations.of(context)!.testUpstreamDnsServers, - ), PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( @@ -128,103 +118,95 @@ class _DnsSettingsState extends State { const SizedBox(width: 10) ], ), - body: SafeArea( - child: Builder( - builder: (context) { - switch (dnsProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingDnsConfig, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case LoadStatus.loaded: - return ListView( + body: Builder( + builder: (context) { + switch (dnsProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - CustomListTile( - title: AppLocalizations.of(context)!.upstreamDns, - subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, - onTap: () => navigate(const UpstreamDnsScreen()), - icon: Icons.upload_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.bootstrapDns, - subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, - onTap: () => navigate(const BootstrapDnsScreen()), - icon: Icons.dns_rounded, - ), - if (dnsProvider.dnsInfo!.fallbackDns != null) CustomListTile( - title: AppLocalizations.of(context)!.fallbackDnsServers, - subtitle: AppLocalizations.of(context)!.fallbackDnsServersDescription, - onTap: () => navigate(const FallbackDnsScreen()), - icon: Icons.alt_route_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.privateReverseDnsServers, - subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, - onTap: () => navigate(const PrivateReverseDnsServersScreen()), - icon: Icons.person_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsServerSettings, - subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, - onTap: () => navigate(const DnsServerSettingsScreen()), - icon: Icons.settings, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsCacheConfig, - subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, - onTap: () => navigate(const CacheConfigDnsScreen()), - icon: Icons.storage_rounded, - ), - ], - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingDnsConfig, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dnsConfigNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], + ) + ], + ) + ); + + case LoadStatus.loaded: + return ListView( + children: [ + CustomListTile( + title: AppLocalizations.of(context)!.upstreamDns, + subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, + onTap: () => navigate(const UpstreamDnsScreen()), + icon: Icons.upload_rounded, ), - ); - - default: - return const SizedBox(); - } - }, - ), + CustomListTile( + title: AppLocalizations.of(context)!.bootstrapDns, + subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, + onTap: () => navigate(const BootstrapDnsScreen()), + icon: Icons.dns_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.privateReverseDnsServers, + subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, + onTap: () => navigate(const PrivateReverseDnsServersScreen()), + icon: Icons.person_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsServerSettings, + subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, + onTap: () => navigate(const DnsServerSettingsScreen()), + icon: Icons.settings, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsCacheConfig, + subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, + onTap: () => navigate(const CacheConfigDnsScreen()), + icon: Icons.storage_rounded, + ), + ], + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dnsConfigNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, ) ); } diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index fc74d88..777821f 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -1,12 +1,9 @@ // ignore_for_file: use_build_context_synchronously -import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/settings/dns/rate_limit_allowlist_modal.dart'; -import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; @@ -25,50 +22,30 @@ class DnsServerSettingsScreen extends StatefulWidget { } class _DnsServerSettingsScreenState extends State { - final _limitRequestsController = TextEditingController(); - final _ipv4PrefixSubnetController = TextEditingController(); - String? _ipv4PrefixSubnetError; - final _ipv6PrefixSubnetController = TextEditingController(); - String? _ipv6PrefixSubnetError; - List _rateLimitAllowlist = []; - String? _limitRequestsError; - final _expandableCustomEdns = ExpandableController(); - final _expandableEdnsIp = ExpandableController(); - bool _enableEdns = false; - bool _useCustomIpEdns = false; - final _customIpEdnsController = TextEditingController(); - String? _ednsIpError; - bool _enableDnssec = false; - bool _disableIpv6Resolving = false; + final TextEditingController limitRequestsController = TextEditingController(); + String? limitRequestsError; + bool enableEdns = false; + bool enableDnssec = false; + bool disableIpv6Resolving = false; String blockingMode = "default"; - final _ipv4controller = TextEditingController(); - String? _ipv4error; - final _ipv6controller = TextEditingController(); + final TextEditingController ipv4controller = TextEditingController(); + String? ipv4error; + final TextEditingController ipv6controller = TextEditingController(); String? ipv6error; - final _ttlController = TextEditingController(); - String? _ttlError; + bool isDataValid = false; void validateIpv4(String value) { RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$'); if (ipAddress.hasMatch(value) == true) { - setState(() => _ipv4error = null); + setState(() => ipv4error = null); } else { - setState(() => _ipv4error = AppLocalizations.of(context)!.invalidIp); - } - } - - void validateEdns(String value) { - RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); - if (ipAddress.hasMatch(value) == true) { - setState(() => _ednsIpError = null); - } - else { - setState(() => _ednsIpError = AppLocalizations.of(context)!.ipNotValid); + setState(() => ipv4error = AppLocalizations.of(context)!.invalidIp); } + validateData(); } void validateIpv6(String value) { @@ -79,50 +56,28 @@ class _DnsServerSettingsScreenState extends State { else { setState(() => ipv6error = AppLocalizations.of(context)!.invalidIp); } + validateData(); } - bool validateData() { + void validateData() { if ( - (_limitRequestsController.text == "" || (_limitRequestsController.text != "" && _limitRequestsError == null)) && + limitRequestsController.text != '' && + limitRequestsError == null && ( blockingMode != 'custom_ip' || ( blockingMode == 'custom_ip' && - _ipv4controller.text != '' && - _ipv4error == null && - _ipv6controller.text != '' && + ipv4controller.text != '' && + ipv4error == null && + ipv6controller.text != '' && ipv6error == null ) - ) == true && - _ednsIpError == null && - _ttlController.text != "" && _ttlError == null && - (_ipv4PrefixSubnetController.text == "" || (_ipv4PrefixSubnetController.text != "" && _ipv4PrefixSubnetError == null)) && - (_ipv6PrefixSubnetController.text == "" || (_ipv6PrefixSubnetController.text != "" && _ipv6PrefixSubnetError == null)) + ) == true ) { - return true; + setState(() => isDataValid = true); } else { - return false; - } - } - - String? validateTtl(String value) { - if (value == "") return AppLocalizations.of(context)!.valueNotNumber; - if (int.tryParse(value) != null) { - return null; - } - else { - return AppLocalizations.of(context)!.valueNotNumber; - } - } - - String? validateNumber(String value) { - if (value == "") return null; - if (int.tryParse(value) != null) { - return null; - } - else { - return AppLocalizations.of(context)!.valueNotNumber; + setState(() => isDataValid = false); } } @@ -130,27 +85,14 @@ class _DnsServerSettingsScreenState extends State { void initState() { final dnsProvider = Provider.of(context, listen: false); - _limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString(); - _enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled; - _useCustomIpEdns = dnsProvider.dnsInfo!.ednsCsUseCustom ?? false; - _customIpEdnsController.text = dnsProvider.dnsInfo!.ednsCsCustomIp ?? ""; - if (dnsProvider.dnsInfo!.ednsCsEnabled == true) _expandableCustomEdns.toggle(); - if (dnsProvider.dnsInfo!.ednsCsUseCustom == true) _expandableEdnsIp.toggle(); - _enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled; - _disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6; + limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString(); + enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled; + enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled; + disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6; blockingMode = dnsProvider.dnsInfo!.blockingMode; - _ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4; - _ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6; - _ttlController.text = dnsProvider.dnsInfo!.blockedResponseTtl != null - ? dnsProvider.dnsInfo!.blockedResponseTtl.toString() - : ""; - _ipv4PrefixSubnetController.text = dnsProvider.dnsInfo!.ratelimitSubnetLenIpv4 != null - ? dnsProvider.dnsInfo!.ratelimitSubnetLenIpv4.toString() - : ""; - _ipv6PrefixSubnetController.text = dnsProvider.dnsInfo!.ratelimitSubnetLenIpv6 != null - ? dnsProvider.dnsInfo!.ratelimitSubnetLenIpv6.toString() - : ""; - _rateLimitAllowlist = dnsProvider.dnsInfo!.ratelimitWhitelist ?? []; + ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4; + ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6; + isDataValid = true; super.initState(); } @@ -165,39 +107,33 @@ class _DnsServerSettingsScreenState extends State { processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await dnsProvider.saveDnsServerConfig({ - "ratelimit": int.tryParse(_limitRequestsController.text), - "edns_cs_enabled": _enableEdns, - "edns_cs_use_custom": _useCustomIpEdns, - "edns_cs_custom_ip": _customIpEdnsController.text, - "dnssec_enabled": _enableDnssec, - "disable_ipv6": _disableIpv6Resolving, + "ratelimit": int.parse(limitRequestsController.text), + "edns_cs_enabled": enableEdns, + "dnssec_enabled": enableDnssec, + "disable_ipv6": disableIpv6Resolving, "blocking_mode": blockingMode, - "blocking_ipv4": _ipv4controller.text, - "blocking_ipv6": _ipv6controller.text, - "blocked_response_ttl": int.tryParse(_ttlController.text), - "ratelimit_subnet_len_ipv4": int.tryParse(_ipv4PrefixSubnetController.text), - "ratelimit_subnet_len_ipv6": int.tryParse(_ipv6PrefixSubnetController.text), - "ratelimit_whitelist": _rateLimitAllowlist + "blocking_ipv4": ipv4controller.text, + "blocking_ipv6": ipv6controller.text }); processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } else if (result.successful == false && result.statusCode == 400) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigNotSaved, color: Colors.red @@ -207,24 +143,22 @@ class _DnsServerSettingsScreenState extends State { void updateBlockingMode(String mode) { if (mode != 'custom_ip') { - _ipv4controller.text = ''; - _ipv4error = null; - _ipv6controller.text = ''; + ipv4controller.text = ''; + ipv4error = null; + ipv6controller.text = ''; ipv6error = null; } setState(() => blockingMode = mode); validateData(); } - final dataValid = validateData(); - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsServerSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( - onPressed: dataValid == true + onPressed: isDataValid == true ? () => saveData() : null, icon: const Icon(Icons.save_rounded), @@ -233,290 +167,141 @@ class _DnsServerSettingsScreenState extends State { const SizedBox(width: 10) ], ), - body: SafeArea( - child: ListView( - padding: const EdgeInsets.only(top: 0), - children: [ - SectionLabel( - label: AppLocalizations.of(context)!.rateLimit, - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24, top: 8), - ), - Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24, top: 4), - child: TextFormField( - controller: _limitRequestsController, - onChanged: (v) => setState(() => _limitRequestsError = validateNumber(v)), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.looks_one_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.limitRequestsSecond, - errorText: _limitRequestsError - ), - keyboardType: TextInputType.number, - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24), - child: TextFormField( - controller: _ipv4PrefixSubnetController, - onChanged: (v) => setState(() => _ipv4PrefixSubnetError = validateNumber(v)), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.subnetPrefixLengthIpv4, - errorText: _ipv4PrefixSubnetError - ), - keyboardType: TextInputType.number, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: TextFormField( - controller: _ipv6PrefixSubnetController, - onChanged: (v) => setState(() => _ipv6PrefixSubnetError = validateNumber(v)), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.subnetPrefixLengthIpv6, - errorText: _ipv6PrefixSubnetError - ), - keyboardType: TextInputType.number, - ), - ), - const SizedBox(height: 16), - CustomListTile( - title: AppLocalizations.of(context)!.rateLimitAllowlist, - subtitle: AppLocalizations.of(context)!.rateLimitAllowlistDescription, - onTap: () => showDialog( - context: context, - builder: (context) => RateLimitAllowlistModal( - values: _rateLimitAllowlist, - onConfirm: (ips) => setState(() => _rateLimitAllowlist = ips) - ), - ), - trailing: _rateLimitAllowlist.isNotEmpty ? Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(40) - ), - child: Text( - _rateLimitAllowlist.length.toString(), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onPrimaryContainer - ), - ), - ) : null, - ), - SectionLabel(label: AppLocalizations.of(context)!.dnsOptions), - CustomSwitchListTile( - value: _enableEdns, - onChanged: (value) => setState(() { - _enableEdns = value; - _expandableCustomEdns.toggle(); - if (value == false) { - _useCustomIpEdns = false; - if (_expandableEdnsIp.expanded == true) _expandableEdnsIp.toggle(); - _customIpEdnsController.text = ""; - _ednsIpError = null; + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: TextFormField( + controller: limitRequestsController, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => limitRequestsError = null); + } + else { + setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber); } validateData(); - }), - title: AppLocalizations.of(context)!.enableEdns, - subtitle: AppLocalizations.of(context)!.enableEdnsDescription, - ), - ExpandableNotifier( - controller: _expandableCustomEdns, - child: Expandable( - collapsed: const SizedBox(), - expanded: Column( - children: [ - CustomSwitchListTile( - padding: const EdgeInsets.only( - left: 50, - top: 12, - bottom: 12, - right: 16 - ), - value: _useCustomIpEdns, - onChanged: (value) => setState(() { - _useCustomIpEdns = value; - _expandableEdnsIp.toggle(); - if (_useCustomIpEdns == false) { - _customIpEdnsController.text = ""; - _ednsIpError = null; - } - validateData(); - }), - title: AppLocalizations.of(context)!.useCustomIpEdns, - subtitle: AppLocalizations.of(context)!.useCustomIpEdnsDescription, - ), - ExpandableNotifier( - controller: _expandableEdnsIp, - child: Expandable( - collapsed: const SizedBox(), - expanded: Padding( - padding: const EdgeInsets.only( - top: 16, - bottom: 16, - right: 16, - left: 70 - ), - child: TextFormField( - controller: _customIpEdnsController, - onChanged: validateEdns, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: _ednsIpError, - labelText: AppLocalizations.of(context)!.ipAddress, - ), - ), - ), - ) - ), - ], - ), - ) - ), - CustomSwitchListTile( - value: _enableDnssec, - onChanged: (value) => setState(() => _enableDnssec = value), - title: AppLocalizations.of(context)!.enableDnssec, - subtitle: AppLocalizations.of(context)!.enableDnssecDescription, - ), - CustomSwitchListTile( - value: _disableIpv6Resolving, - onChanged: (value) => setState(() => _disableIpv6Resolving = value), - title: AppLocalizations.of(context)!.disableResolvingIpv6, - subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description, - ), - SectionLabel(label: AppLocalizations.of(context)!.blockingMode), - CustomRadioListTile( - groupValue: blockingMode, - value: "default", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.defaultMode, - subtitle: AppLocalizations.of(context)!.defaultDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "refused", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: "REFUSED", - subtitle: AppLocalizations.of(context)!.refusedDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "nxdomain", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: "NXDOMAIN", - subtitle: AppLocalizations.of(context)!.nxdomainDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "null_ip", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.nullIp, - subtitle: AppLocalizations.of(context)!.nullIpDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "custom_ip", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.customIp, - subtitle: AppLocalizations.of(context)!.customIpDescription, - onChanged: updateBlockingMode, - ), - const SizedBox(height: 10), - if (blockingMode == 'custom_ip') ...[ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: _ipv4controller, - onChanged: validateIpv4, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: _ipv4error, - helperText: AppLocalizations.of(context)!.blockingIpv4Description, - helperMaxLines: 10, - labelText: AppLocalizations.of(context)!.blockingIpv4, - ), - keyboardType: TextInputType.number, + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.looks_one_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) ), + labelText: AppLocalizations.of(context)!.limitRequestsSecond, + errorText: limitRequestsError ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: _ipv6controller, - onChanged: validateIpv6, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6error, - helperText: AppLocalizations.of(context)!.blockingIpv6Description, - helperMaxLines: 10, - labelText: AppLocalizations.of(context)!.blockingIpv6, - ), - keyboardType: TextInputType.number, - ), - ), - const SizedBox(height: 30), - ], + keyboardType: TextInputType.number, + ), + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: enableEdns, + onChanged: (value) => setState(() => enableEdns = value), + title: AppLocalizations.of(context)!.enableEdns, + subtitle: AppLocalizations.of(context)!.enableEdnsDescription, + ), + CustomSwitchListTile( + value: enableDnssec, + onChanged: (value) => setState(() => enableDnssec = value), + title: AppLocalizations.of(context)!.enableDnssec, + subtitle: AppLocalizations.of(context)!.enableDnssecDescription, + ), + CustomSwitchListTile( + value: disableIpv6Resolving, + onChanged: (value) => setState(() => disableIpv6Resolving = value), + title: AppLocalizations.of(context)!.disableResolvingIpv6, + subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description, + ), + SectionLabel(label: AppLocalizations.of(context)!.blockingMode), + CustomRadioListTile( + groupValue: blockingMode, + value: "default", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.defaultMode, + subtitle: AppLocalizations.of(context)!.defaultDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "refused", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: "REFUSED", + subtitle: AppLocalizations.of(context)!.refusedDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "nxdomain", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: "NXDOMAIN", + subtitle: AppLocalizations.of(context)!.nxdomainDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "null_ip", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.nullIp, + subtitle: AppLocalizations.of(context)!.nullIpDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "custom_ip", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.customIp, + subtitle: AppLocalizations.of(context)!.customIpDescription, + onChanged: updateBlockingMode, + ), + const SizedBox(height: 10), + if (blockingMode == 'custom_ip') ...[ Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.symmetric(horizontal: 24), child: TextFormField( - controller: _ttlController, - onChanged: (v) => setState(() => _ttlError = validateTtl(v)), + controller: ipv4controller, + onChanged: validateIpv4, decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer_rounded), + prefixIcon: const Icon(Icons.link_rounded), border: const OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10) ) ), - errorText: _ttlError, - labelText: AppLocalizations.of(context)!.blockedResponseTtl, - helperText: AppLocalizations.of(context)!.blockedResponseTtlDescription, - helperMaxLines: 2, + errorText: ipv4error, + helperText: AppLocalizations.of(context)!.blockingIpv4Description, + helperMaxLines: 10, + labelText: AppLocalizations.of(context)!.blockingIpv4, ), keyboardType: TextInputType.number, ), ), - ], - ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: ipv6controller, + onChanged: validateIpv6, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6error, + helperText: AppLocalizations.of(context)!.blockingIpv6Description, + helperMaxLines: 10, + labelText: AppLocalizations.of(context)!.blockingIpv6, + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(height: 30) + ] + ], ), ); } diff --git a/lib/screens/settings/dns/fallback_dns.dart b/lib/screens/settings/dns/fallback_dns.dart deleted file mode 100644 index 6c8d8ce..0000000 --- a/lib/screens/settings/dns/fallback_dns.dart +++ /dev/null @@ -1,334 +0,0 @@ -import 'dart:io'; - -import 'package:adguard_home_manager/constants/regexps.dart'; -import 'package:adguard_home_manager/screens/settings/dns/comment_modal.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/providers/dns_provider.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; - -class FallbackDnsScreen extends StatefulWidget { - const FallbackDnsScreen({super.key}); - - @override - State createState() => _FallbackDnsScreenState(); -} - -class _FallbackDnsScreenState extends State { - List> fallbackControllers = []; - - bool validValues = false; - - void validateIp(Map field, String value) { - if (Regexps.ipv4Address.hasMatch(value) == true || Regexps.ipv6Address.hasMatch(value) || Regexps.url.hasMatch(value) == true) { - setState(() => field['error'] = null); - } - else { - setState(() => field['error'] = AppLocalizations.of(context)!.invalidIpOrUrl); - } - checkValidValues(); - } - - void checkValidValues() { - if (fallbackControllers.every((element) => element['error'] == null)) { - setState(() => validValues = true); - } - else { - setState(() => validValues = false); - } - } - - @override - void initState() { - final dnsProvider = Provider.of(context, listen: false); - - for (var item in dnsProvider.dnsInfo!.fallbackDns!) { - if (item.contains("#")) { - fallbackControllers.add({ - 'comment': item - }); - } - else { - final controller = TextEditingController(); - controller.text = item; - fallbackControllers.add({ - 'controller': controller, - 'error': null, - 'isComment': item.contains("#") - }); - } - } - validValues = true; - super.initState(); - } - - @override - Widget build(BuildContext context) { - final dnsProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - void saveData() async { - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.savingConfig); - - final result = await dnsProvider.saveFallbackDnsConfig({ - "fallback_dns": fallbackControllers.map( - (e) => e['controller'] != null - ? e['controller'].text - : e['comment'] - ).toList(), - }); - - processModal.close(); - - if (!context.mounted) return; - if (result.successful == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.dnsConfigSaved, - color: Colors.green - ); - } - else if (result.successful == false && result.statusCode == 400) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.someValueNotValid, - color: Colors.red - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.dnsConfigNotSaved, - color: Colors.red - ); - } - } - - void openAddCommentModal() { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => CommentModal( - onConfirm: (value) { - setState(() { - fallbackControllers.add({ - 'comment': value - }); - }); - }, - dialog: true, - ), - ); - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => CommentModal( - onConfirm: (value) { - setState(() { - fallbackControllers.add({ - 'comment': value - }); - }); - }, - dialog: false, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true, - isDismissible: true - ); - } - } - - - void openEditCommentModal(Map item, int position) { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => CommentModal( - comment: item['comment'], - onConfirm: (value) { - setState(() => fallbackControllers[position] = { 'comment': value }); - }, - dialog: true, - ), - ); - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => CommentModal( - comment: item['comment'], - onConfirm: (value) { - setState(() => fallbackControllers[position] = { 'comment': value }); - }, - dialog: false, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true, - isDismissible: true - ); - } - } - - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.fallbackDnsServers), - surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - actions: [ - IconButton( - onPressed: validValues == true - ? () => saveData() - : null, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 10) - ], - ), - body: SafeArea( - child: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - Card( - margin: const EdgeInsets.only( - left: 16, right: 16, bottom: 20 - ), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 20), - Flexible( - child: Text( - AppLocalizations.of(context)!.fallbackDnsServersInfo, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ) - ], - ), - ), - ), - const SizedBox(height: 10), - if (fallbackControllers.isEmpty) Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - AppLocalizations.of(context)!.noFallbackDnsAdded, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 16 - ), - ), - ), - ), - const SizedBox(height: 20), - ], - ), - ...fallbackControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 16, right: 6, bottom: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (c['controller'] != null) Expanded( - child: TextFormField( - controller: c['controller'], - onChanged: (value) => validateIp(c, value), - decoration: InputDecoration( - prefixIcon: Icon( - c['isComment'] == true - ? Icons.comment_rounded - : Icons.dns_rounded - ), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: c['error'], - labelText: c['isComment'] == true - ? AppLocalizations.of(context)!.comment - : AppLocalizations.of(context)!.dnsServer, - ) - ), - ), - const SizedBox(width: 8), - if (c['comment'] != null) Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - c['comment'], - style: TextStyle( - fontSize: 16, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - IconButton( - onPressed: () => openEditCommentModal(c, fallbackControllers.indexOf(c)), - icon: const Icon(Icons.edit), - tooltip: AppLocalizations.of(context)!.edit, - ) - ], - ), - ), - IconButton( - onPressed: () { - setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList()); - checkValidValues(); - }, - icon: const Icon(Icons.remove_circle_outline), - tooltip: AppLocalizations.of(context)!.remove, - ) - ], - ), - )), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton.icon( - onPressed: openAddCommentModal, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.comment) - ), - ElevatedButton.icon( - onPressed: () { - setState(() => fallbackControllers.add({ - 'controller': TextEditingController(), - 'error': null - })); - checkValidValues(); - }, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.address) - ), - ], - ), - const SizedBox(height: 20) - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index 1ea4cc3..f91c593 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -13,7 +13,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class PrivateReverseDnsServersScreen extends StatefulWidget { - const PrivateReverseDnsServersScreen({super.key}); + const PrivateReverseDnsServersScreen({Key? key}) : super(key: key); @override State createState() => _PrivateReverseDnsServersScreenState(); @@ -112,21 +112,21 @@ class _PrivateReverseDnsServersScreenState extends State item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}", + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16 ), ), ), - if (editReverseResolvers == false) ...[ - Padding( - padding: const EdgeInsets.all(20), + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 20), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() => editReverseResolvers = true); + checkDataValid(); + }, + icon: const Icon(Icons.edit), + label: Text(AppLocalizations.of(context)!.edit) + ), + ], + ), + ) + ], + if (editReverseResolvers == true) ...[ + const SizedBox(height: 20), + ...reverseResolversControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 16, right: 6, bottom: 20 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextFormField( + controller: c['controller'], + onChanged: (value) => validateAddress(c, value), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: c['error'], + labelText: AppLocalizations.of(context)!.serverAddress, + ) + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: () { + setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList()); + checkDataValid(); + }, + icon: const Icon(Icons.remove_circle_outline) + ) + ], + ), + )), + if (reverseResolversControllers.isEmpty) Padding( + padding: const EdgeInsets.only( + left: 20, right: 20, bottom: 20 + ), + child: Center( child: Text( - "${AppLocalizations.of(context)!.reverseDnsDefault}:\n\n${defaultReverseResolvers.map((item) => item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}", + AppLocalizations.of(context)!.noServerAddressesAdded, textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, @@ -190,110 +257,41 @@ class _PrivateReverseDnsServersScreenState extends State editReverseResolvers = true); - checkDataValid(); - }, - icon: const Icon(Icons.edit), - label: Text(AppLocalizations.of(context)!.edit) - ), - ], - ), - ) - ], - if (editReverseResolvers == true) ...[ - const SizedBox(height: 20), - ...reverseResolversControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 16, right: 6, bottom: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: TextFormField( - controller: c['controller'], - onChanged: (value) => validateAddress(c, value), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: c['error'], - labelText: AppLocalizations.of(context)!.serverAddress, - ) - ), - ), - const SizedBox(width: 8), - IconButton( - onPressed: () { - setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList()); - checkDataValid(); - }, - icon: const Icon(Icons.remove_circle_outline) - ) - ], - ), - )), - if (reverseResolversControllers.isEmpty) Padding( - padding: const EdgeInsets.only( - left: 20, right: 20, bottom: 20 - ), - child: Center( - child: Text( - AppLocalizations.of(context)!.noServerAddressesAdded, - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 16 - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton.icon( - onPressed: () { - setState(() => reverseResolversControllers.add({ - 'controller': TextEditingController(), - 'error': null - })); - checkDataValid(); - }, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.addItem) - ), - ], - ), - ), - ], - CustomSwitchListTile( - value: usePrivateReverseDnsResolvers, - onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value), - title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers, - subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription ), - CustomSwitchListTile( - value: enableReverseResolve, - onChanged: (value) => setState(() => enableReverseResolve = value), - title: AppLocalizations.of(context)!.enableReverseResolving, - subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() => reverseResolversControllers.add({ + 'controller': TextEditingController(), + 'error': null + })); + checkDataValid(); + }, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), ), ], - ), + CustomSwitchListTile( + value: usePrivateReverseDnsResolvers, + onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value), + title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers, + subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription + ), + CustomSwitchListTile( + value: enableReverseResolve, + onChanged: (value) => setState(() => enableReverseResolve = value), + title: AppLocalizations.of(context)!.enableReverseResolving, + subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription + ), + ], ), ); } diff --git a/lib/screens/settings/dns/rate_limit_allowlist_modal.dart b/lib/screens/settings/dns/rate_limit_allowlist_modal.dart deleted file mode 100644 index 2c2fed8..0000000 --- a/lib/screens/settings/dns/rate_limit_allowlist_modal.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; -import 'package:flutter/material.dart'; -import 'package:uuid/uuid.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -class _IpListItemController { - final String id; - final TextEditingController controller; - bool error; - - _IpListItemController({ - required this.id, - required this.controller, - required this.error - }); -} - -class RateLimitAllowlistModal extends StatefulWidget { - final List values; - final void Function(List) onConfirm; - - const RateLimitAllowlistModal({ - super.key, - required this.values, - required this.onConfirm, - }); - - @override - State createState() => _RateLimitAllowlistModalState(); -} - -class _RateLimitAllowlistModalState extends State { - final Uuid uuid = const Uuid(); - List<_IpListItemController> _controllersList = []; - - void _validateIp(String value, _IpListItemController item) { - if (Regexps.ipv4Address.hasMatch(value)) { - setState(() => _controllersList = _controllersList.map((e) { - if (e.id == item.id) { - return _IpListItemController( - id: e.id, - controller: e.controller, - error: false - ); - } - return e; - }).toList()); - } - else { - setState(() => _controllersList = _controllersList.map((e) { - if (e.id == item.id) { - return _IpListItemController( - id: e.id, - controller: e.controller, - error: true - ); - } - return e; - }).toList()); - } - } - - @override - void initState() { - _controllersList = widget.values.map((e) => _IpListItemController( - id: uuid.v4(), - controller: TextEditingController(text: e), - error: false - )).toList(); - - super.initState(); - } - - @override - Widget build(BuildContext context) { - final validData = _controllersList.where((e) => e.error == true).isEmpty; - - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 500), - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Center( - child: Icon( - Icons.check_circle_rounded, - size: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.rateLimitAllowlist, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 24, - ), - ), - const SizedBox(height: 16), - ..._controllersList.map((item) => Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Row( - children: [ - Expanded( - child: TextFormField( - controller: item.controller, - onChanged: (v) => _validateIp(v, item), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.ipAddress, - errorText: item.error == true - ? AppLocalizations.of(context)!.invalidIp - : null - ), - keyboardType: TextInputType.number, - ), - ), - const SizedBox(width: 16), - IconButton( - onPressed: () => setState( - () => _controllersList = _controllersList.where((c) => c.id != item.id).toList() - ), - icon: const Icon(Icons.remove_circle_outline_rounded), - tooltip: AppLocalizations.of(context)!.remove, - ) - ], - ), - )), - Center( - child: ElevatedButton.icon( - onPressed: () => setState(() => _controllersList.add( - _IpListItemController( - id: uuid.v4(), - controller: TextEditingController(), - error: false - ), - )), - icon: const Icon(Icons.add_rounded), - label: Text(AppLocalizations.of(context)!.addItem), - ), - ) - ], - ), - ) - ), - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 8), - TextButton( - onPressed: validData == true - ? () { - widget.onConfirm( - _controllersList.map((e) => e.controller.text).toList() - ); - Navigator.pop(context); - } - : null, - child: Text(AppLocalizations.of(context)!.confirm) - ), - ], - ) - ], - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/dns/test_upstream_dns_modal.dart b/lib/screens/settings/dns/test_upstream_dns_modal.dart deleted file mode 100644 index 7bff710..0000000 --- a/lib/screens/settings/dns/test_upstream_dns_modal.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/dns_provider.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; - -class _Item { - final String url; - final bool value; - - const _Item({ - required this.url, - required this.value - }); -} - -class TestUpstreamDnsModal extends StatefulWidget { - const TestUpstreamDnsModal({super.key}); - - @override - State createState() => _TestUpstreamDnsModalState(); -} - -class _TestUpstreamDnsModalState extends State { - LoadStatus loadStatus = LoadStatus.loading; - List<_Item>? values; - - void checkDns() async { - final dnsProvider = Provider.of(context, listen: false); - if (dnsProvider.dnsInfo == null) { - setState(() => loadStatus = LoadStatus.error); - return; - } - final result = await Provider.of(context, listen: false).apiClient2!.testUpstreamDns( - body: { - "bootstrap_dns": dnsProvider.dnsInfo!.bootstrapDns, - "fallback_dns": [], - "private_upstream": dnsProvider.dnsInfo!.defaultLocalPtrUpstreams, - "upstream_dns": dnsProvider.dnsInfo!.upstreamDns - } - ); - if (!mounted) return; - if (result.successful == true) { - setState(() { - values = List<_Item>.from( - (result.content as Map).entries.map((e) => _Item( - url: e.key, - value: e.value == "OK" ? true : false - )) - ); - loadStatus = LoadStatus.loaded; - }); - } - else { - setState(() => loadStatus = LoadStatus.error); - } - } - - @override - void initState() { - checkDns(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: Column( - children: [ - Icon( - Icons.upload_rounded, - size: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.testUpstreamDnsServers, - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - content: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: Builder( - builder: (context) { - switch (loadStatus) { - case LoadStatus.loading: - return const Padding( - padding: EdgeInsets.symmetric(vertical: 16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CircularProgressIndicator() - ], - ), - ); - - case LoadStatus.loaded: - return SingleChildScrollView( - child: Wrap( - children: values!.map((v) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - v.url, - overflow: TextOverflow.ellipsis, - ), - ), - ...[ - const SizedBox(width: 8), - if (v.value == true) const Icon( - Icons.check_circle_rounded, - color: Colors.green, - size: 16, - ), - if (v.value == false) const Icon( - Icons.cancel_rounded, - color: Colors.red, - ) - ] - ], - ), - )).toList(), - ), - ); - - case LoadStatus.error: - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.error_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 30, - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.errorTestUpstreamDns, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ) - ], - ); - - default: - return const SizedBox(); - } - }, - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close) - ), - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/dns/upstream_dns.dart b/lib/screens/settings/dns/upstream_dns.dart index 1ae8c5e..5dd2acb 100644 --- a/lib/screens/settings/dns/upstream_dns.dart +++ b/lib/screens/settings/dns/upstream_dns.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use_build_context_synchronously + import 'dart:io'; import 'package:flutter/material.dart'; @@ -28,9 +30,6 @@ class _UpstreamDnsScreenState extends State { bool validValues = false; - final upstreamTimeoutController = TextEditingController(); - String? upstreamTimeoutError = null; - checkValidValues() { if ( dnsServers.isNotEmpty && @@ -48,7 +47,7 @@ class _UpstreamDnsScreenState extends State { final dnsProvider = Provider.of(context, listen: false); for (var item in dnsProvider.dnsInfo!.upstreamDns) { - if (item.contains("#")) { + if (item == '#') { dnsServers.add({ 'comment': item }); @@ -62,7 +61,6 @@ class _UpstreamDnsScreenState extends State { } } upstreamMode = dnsProvider.dnsInfo!.upstreamMode ?? ""; - upstreamTimeoutController.text = dnsProvider.dnsInfo!.upstreamTimeout != null ? dnsProvider.dnsInfo!.upstreamTimeout.toString() : ""; validValues = true; super.initState(); } @@ -74,23 +72,6 @@ class _UpstreamDnsScreenState extends State { final width = MediaQuery.of(context).size.width; - void validateTimeout(String value) { - if (value != '' && int.tryParse(value) != null && int.parse(value) > 0) { - setState(() { - upstreamTimeoutError = null; - validValues = true; - }); - } - else { - setState(() { - upstreamTimeoutError = value == '' - ? AppLocalizations.of(context)!.fieldCannotBeEmpty - : AppLocalizations.of(context)!.invalidValue; - validValues = false; - }); - } - } - void openAddCommentModal() { if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { showDialog( @@ -165,29 +146,27 @@ class _UpstreamDnsScreenState extends State { final result = await dnsProvider.saveUpstreamDnsConfig({ "upstream_dns": dnsServers.map((e) => e['controller'] != null ? e['controller'].text : e['comment']).toList(), - "upstream_mode": upstreamMode, - "upstream_timeout": int.tryParse(upstreamTimeoutController.text) + "upstream_mode": upstreamMode }); processModal.close(); - if (!context.mounted) return; if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } else if (result.successful == false && result.statusCode == 400) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, color: Colors.red ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigNotSaved, color: Colors.red @@ -207,155 +186,132 @@ class _UpstreamDnsScreenState extends State { icon: const Icon(Icons.save_rounded), tooltip: AppLocalizations.of(context)!.save, ), - const SizedBox(width: 8) + const SizedBox(width: 10) ], ), - body: SafeArea( - child: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - if (dnsServers.isEmpty) Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - AppLocalizations.of(context)!.noUpstreamDns, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 16 - ), + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + if (dnsServers.isEmpty) Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Center( + child: Text( + AppLocalizations.of(context)!.noUpstreamDns, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16 ), ), ), - const SizedBox(height: 20), - ], - ), - ...dnsServers.map((item) => Padding( - padding: const EdgeInsets.only( - left: 16, right: 6, bottom: 24 ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (item['controller'] != null) Expanded( - child: TextFormField( - controller: item['controller'], - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.dnsServer, - ) - ), - ), - const SizedBox(width: 8), - if (item['comment'] != null) Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - item['comment'], - style: TextStyle( - fontSize: 16, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - IconButton( - onPressed: () => openEditCommentModal(item, dnsServers.indexOf(item)), - icon: const Icon(Icons.edit), - tooltip: AppLocalizations.of(context)!.edit, + const SizedBox(height: 20), + ], + ), + ...dnsServers.map((item) => Padding( + padding: const EdgeInsets.only( + left: 16, right: 6, bottom: 24 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (item['controller'] != null) Expanded( + child: TextFormField( + controller: item['controller'], + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) ) - ], - ), - ), - IconButton( - onPressed: () { - setState(() => dnsServers = dnsServers.where((i) => i != item).toList()); - checkValidValues(); - }, - icon: const Icon(Icons.remove_circle_outline), - tooltip: AppLocalizations.of(context)!.remove, - ), - const SizedBox(width: 4), - ], - ), - )), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton.icon( - onPressed: openAddCommentModal, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.comment) - ), - ElevatedButton.icon( - onPressed: () { - setState(() => dnsServers.add({ - 'controller': TextEditingController() - })); - checkValidValues(); - }, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.address) - ), - ], - ), - const SizedBox(height: 16), - SectionLabel(label: AppLocalizations.of(context)!.dnsMode), - CustomRadioListTile( - groupValue: upstreamMode, - value: "", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.loadBalancing, - subtitle: AppLocalizations.of(context)!.loadBalancingDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - CustomRadioListTile( - groupValue: upstreamMode, - value: "parallel", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.parallelRequests, - subtitle: AppLocalizations.of(context)!.parallelRequestsDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - CustomRadioListTile( - groupValue: upstreamMode, - value: "fastest_addr", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.fastestIpAddress, - subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - const SizedBox(height: 16), - SectionLabel(label: AppLocalizations.of(context)!.others), - Padding( - padding: const EdgeInsets.all(16), - child: TextFormField( - controller: upstreamTimeoutController, - onChanged: validateTimeout, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) + ), + labelText: AppLocalizations.of(context)!.dnsServer, ) ), - labelText: AppLocalizations.of(context)!.upstreamTimeout, - helperText: AppLocalizations.of(context)!.upstreamTimeoutHelper, - helperMaxLines: 2, - errorText: upstreamTimeoutError - ) - ), + ), + const SizedBox(width: 8), + if (item['comment'] != null) Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + item['comment'], + style: TextStyle( + fontSize: 16, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + IconButton( + onPressed: () => openEditCommentModal(item, dnsServers.indexOf(item)), + icon: const Icon(Icons.edit), + tooltip: AppLocalizations.of(context)!.edit, + ) + ], + ), + ), + IconButton( + onPressed: () { + setState(() => dnsServers = dnsServers.where((i) => i != item).toList()); + checkValidValues(); + }, + icon: const Icon(Icons.remove_circle_outline), + tooltip: AppLocalizations.of(context)!.remove, + ), + const SizedBox(width: 4), + ], ), - ], - ), + )).toList(), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: openAddCommentModal, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.comment) + ), + ElevatedButton.icon( + onPressed: () { + setState(() => dnsServers.add({ + 'controller': TextEditingController() + })); + checkValidValues(); + }, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.address) + ), + ], + ), + const SizedBox(height: 16), + SectionLabel(label: AppLocalizations.of(context)!.dnsMode), + CustomRadioListTile( + groupValue: upstreamMode, + value: "", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.loadBalancing, + subtitle: AppLocalizations.of(context)!.loadBalancingDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + CustomRadioListTile( + groupValue: upstreamMode, + value: "parallel", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.parallelRequests, + subtitle: AppLocalizations.of(context)!.parallelRequestsDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + CustomRadioListTile( + groupValue: upstreamMode, + value: "fastest_addr", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.fastestIpAddress, + subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + ], ), ); } diff --git a/lib/screens/settings/dns_rewrites/delete_dns_rewrite.dart b/lib/screens/settings/dns_rewrites/delete_dns_rewrite.dart index be6b7b4..fb2c705 100644 --- a/lib/screens/settings/dns_rewrites/delete_dns_rewrite.dart +++ b/lib/screens/settings/dns_rewrites/delete_dns_rewrite.dart @@ -5,9 +5,9 @@ class DeleteDnsRewrite extends StatelessWidget { final void Function() onConfirm; const DeleteDnsRewrite({ - super.key, + Key? key, required this.onConfirm - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart index 42524fa..fcd62d7 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -47,12 +46,10 @@ class DnsRewriteModal extends StatelessWidget { ), color: Theme.of(context).dialogBackgroundColor, ), - child: SafeArea( - child: _Content( - onConfirm: onConfirm, - onDelete: onDelete, - rule: rule, - ), + child: _Content( + onConfirm: onConfirm, + onDelete: onDelete, + rule: rule, ) ), ); @@ -83,7 +80,8 @@ class _ContentState extends State<_Content> { bool validData = false; void validateDomain(String value) { - if (Regexps.wildcardDomain.hasMatch(value)) { + final domainRegex = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$'); + if (domainRegex.hasMatch(value)) { setState(() => domainError = null); } else { diff --git a/lib/screens/settings/dns_rewrites/dns_rewrites.dart b/lib/screens/settings/dns_rewrites/dns_rewrites.dart index ad38c44..8b5ee0d 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrites.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrites.dart @@ -67,14 +67,14 @@ class _DnsRewritesScreenState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsRewriteRuleDeleted, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsRewriteRuleNotDeleted, color: Colors.red @@ -91,14 +91,14 @@ class _DnsRewritesScreenState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsRewriteRuleAdded, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsRewriteRuleNotAdded, color: Colors.red @@ -115,14 +115,14 @@ class _DnsRewritesScreenState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsRewriteRuleUpdated, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsRewriteRuleNotUpdated, color: Colors.red @@ -136,241 +136,239 @@ class _DnsRewritesScreenState extends State { surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), - body: SafeArea( - child: Stack( - children: [ - Builder( - builder: (context) { - switch (rewriteRulesProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingRewriteRules, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { - return RefreshIndicator( - onRefresh: () async { - final result = await rewriteRulesProvider.fetchRules(); - if (result == false) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, - color: Colors.red - ); - } - }, - child: ListView.builder( - controller: scrollController, - padding: const EdgeInsets.only(top: 0), - itemCount: rewriteRulesProvider.rewriteRules!.length, - itemBuilder: (context, index) => Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: InkWell( - onTap: () => { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: true, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - ) - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: false, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - backgroundColor: Colors.transparent, - isScrollControlled: true, - ) - } - }, - borderRadius: BorderRadius.circular(10), - child: Padding( - padding: const EdgeInsets.only( - left: 16, top: 16, bottom: 16, right: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.domain}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].domain, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - const SizedBox(height: 3), - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.answer}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].answer, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ], - ), - Icon( - Icons.keyboard_arrow_right_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ) - ], - ), - ), - ), - ) - ), - ); - } - else { - return Center( - child: Text( - AppLocalizations.of(context)!.noRewriteRules, + body: Stack( + children: [ + Builder( + builder: (context) { + switch (rewriteRulesProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingRewriteRules, + textAlign: TextAlign.center, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ), - ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.rewriteRulesNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ), + ); + + case LoadStatus.loaded: + if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { + return RefreshIndicator( + onRefresh: () async { + final result = await rewriteRulesProvider.fetchRules(); + if (result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, + color: Colors.red + ); + } + }, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 0), + itemCount: rewriteRulesProvider.rewriteRules!.length, + itemBuilder: (context, index) => Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: InkWell( + onTap: () => { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: true, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + ) + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: false, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + backgroundColor: Colors.transparent, + isScrollControlled: true, + ) + } + }, + borderRadius: BorderRadius.circular(10), + child: Padding( + padding: const EdgeInsets.only( + left: 16, top: 16, bottom: 16, right: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.domain}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].domain, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + const SizedBox(height: 3), + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.answer}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].answer, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ], + ), + Icon( + Icons.keyboard_arrow_right_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ), ), - ) - ], + ), + ) ), ); - - default: - return const SizedBox(); - } - }, - ), - AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: isVisible ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: FloatingActionButton( - onPressed: () => { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: addDnsRewrite, - dialog: true, - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noRewriteRules, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ) - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => DnsRewriteModal( - onConfirm: addDnsRewrite, - dialog: false, - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), + ), + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ) - } - }, - child: const Icon(Icons.add), - ), - ) - ], - ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.rewriteRulesNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ), + AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: isVisible ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: FloatingActionButton( + onPressed: () => { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => DnsRewriteModal( + onConfirm: addDnsRewrite, + dialog: true, + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + ) + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => DnsRewriteModal( + onConfirm: addDnsRewrite, + dialog: false, + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ) + } + }, + child: const Icon(Icons.add), + ), + ) + ], ), ); } diff --git a/lib/screens/settings/dns_rewrites/rule_modal.dart b/lib/screens/settings/dns_rewrites/rule_modal.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/screens/settings/dns_rewrites/server_version_needed.dart b/lib/screens/settings/dns_rewrites/server_version_needed.dart index 68ea426..88888c6 100644 --- a/lib/screens/settings/dns_rewrites/server_version_needed.dart +++ b/lib/screens/settings/dns_rewrites/server_version_needed.dart @@ -4,7 +4,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ServerVersionNeeded extends StatelessWidget { final String version; - // ignore: use_super_parameters const ServerVersionNeeded({ Key? key, required this.version diff --git a/lib/screens/settings/encryption/config_error_modal.dart b/lib/screens/settings/encryption/config_error_modal.dart index 4fdcec3..1e2cfd0 100644 --- a/lib/screens/settings/encryption/config_error_modal.dart +++ b/lib/screens/settings/encryption/config_error_modal.dart @@ -5,9 +5,9 @@ class EncryptionErrorModal extends StatelessWidget { final String error; const EncryptionErrorModal({ - super.key, + Key? key, required this.error, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/encryption/custom_text_field.dart b/lib/screens/settings/encryption/custom_text_field.dart index 1290104..6a1cac8 100644 --- a/lib/screens/settings/encryption/custom_text_field.dart +++ b/lib/screens/settings/encryption/custom_text_field.dart @@ -12,7 +12,7 @@ class EncryptionTextField extends StatelessWidget { final String? helperText; const EncryptionTextField({ - super.key, + Key? key, required this.enabled, required this.controller, required this.icon, @@ -22,7 +22,7 @@ class EncryptionTextField extends StatelessWidget { this.keyboardType, this.multiline, this.helperText, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index 764f303..f47a63d 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -4,11 +4,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; +import 'package:adguard_home_manager/screens/settings/encryption/config_error_modal.dart'; import 'package:adguard_home_manager/screens/settings/encryption/status.dart'; -import 'package:adguard_home_manager/screens/settings/encryption/reset_settings_modal.dart'; import 'package:adguard_home_manager/screens/settings/encryption/custom_text_field.dart'; import 'package:adguard_home_manager/screens/settings/encryption/master_switch.dart'; import 'package:adguard_home_manager/screens/settings/encryption/encryption_functions.dart'; @@ -36,8 +35,6 @@ class _EncryptionSettingsState extends State { bool enabled = false; - bool? _plainDns; - final TextEditingController domainNameController = TextEditingController(); String? domainError; @@ -73,7 +70,7 @@ class _EncryptionSettingsState extends State { bool localValidationValid = false; String? validDataError; - bool _dataValidApi = true; + int certKeyValidApi = 0; EncryptionValidation? certKeyValid; String? encryptionResultMessage; @@ -85,10 +82,10 @@ class _EncryptionSettingsState extends State { final result = await Provider.of(context, listen: false).apiClient2!.getEncryptionSettings(); if (!mounted) return; + + final data = result.content as EncryptionData; if (result.successful == true) { - final data = result.content as EncryptionData; - await checkValidDataApi(data: data.toJson()); if (!mounted) return; @@ -115,7 +112,6 @@ class _EncryptionSettingsState extends State { privateKeyPathController.text = data.privateKeyPath; } usePreviouslySavedKey = data.privateKeySaved; - _plainDns = data.servePlainDns; loadStatus = LoadStatus.loaded; }); } @@ -125,6 +121,8 @@ class _EncryptionSettingsState extends State { } Future checkValidDataApi({Map? data}) async { + setState(() => certKeyValidApi = 0); + final result = await Provider.of(context, listen: false).apiClient2!.checkEncryptionSettings( data: data ?? { "enabled": enabled, @@ -148,11 +146,11 @@ class _EncryptionSettingsState extends State { final object = data.encryptionValidation!; setState(() { if (object.warningValidation != null && object.warningValidation != '') { - _dataValidApi = false; + certKeyValidApi = 2; validDataError = object.warningValidation; } else { - _dataValidApi = true; + certKeyValidApi = 1; validDataError = null; } certKeyValid = object; @@ -161,7 +159,7 @@ class _EncryptionSettingsState extends State { else { setState(() { encryptionResultMessage = data.message; - _dataValidApi = false; + certKeyValidApi = 2; }); } } @@ -226,21 +224,20 @@ class _EncryptionSettingsState extends State { "private_key_saved": usePreviouslySavedKey, "certificate_path": certificatePathController.text, "private_key_path": privateKeyPathController.text, - "serve_plain_dns": _plainDns } ); processModal.close(); if (result.successful == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.encryptionConfigSaved, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.encryptionConfigNotSaved, color: Colors.red @@ -257,46 +254,6 @@ class _EncryptionSettingsState extends State { } } - void resetSettings() async { - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.resettingConfig); - - final result = await serversProvider.apiClient2!.saveEncryptionSettings( - data: { - "enabled": false, - "server_name": "", - "force_https": false, - "port_https": 443, - "port_dns_over_tls": 853, - "port_dns_over_quic": 853, - "certificate_chain": "", - "private_key": "", - "private_key_saved": false, - "certificate_path": "", - "private_key_path": "", - "serve_plain_dns": true - } - ); - if (!mounted) return; - - processModal.close(); - - if (result.successful == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.configurationResetSuccessfully, - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.configurationResetError, - color: Colors.red - ); - } - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.encryptionSettings), @@ -304,12 +261,17 @@ class _EncryptionSettingsState extends State { centerTitle: false, actions: [ IconButton( - onPressed: () => showDialog( - context: context, - builder: (ctx) => ResetSettingsModal(onConfirm: resetSettings) - ), - icon: const Icon(Icons.restore_rounded), - tooltip: AppLocalizations.of(context)!.resetSettings, + onPressed: certKeyValidApi == 2 && (validDataError != null || encryptionResultMessage != null) + ? () => { + showDialog( + context: context, + builder: (context) => EncryptionErrorModal( + error: validDataError ?? encryptionResultMessage ?? AppLocalizations.of(context)!.unknownError + ) + ) + } : null, + icon: generateStatus(context, appConfigProvider, localValidationValid, certKeyValidApi, formEdited), + tooltip: generateStatusString(context, localValidationValid, certKeyValidApi) ), IconButton( onPressed: localValidationValid ? @@ -321,408 +283,376 @@ class _EncryptionSettingsState extends State { const SizedBox(width: 10), ], ), - body: SafeArea( - child: Builder( - builder: (context) { - switch (loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingEncryptionSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case LoadStatus.loaded: - return ListView( + body: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (_dataValidApi == false && (validDataError != null || encryptionResultMessage != null)) Card( - margin: const EdgeInsets.all(16), - color: Colors.red.withOpacity(0.2), - elevation: 0, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.error_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Expanded( - child: Text(validDataError ?? encryptionResultMessage ?? AppLocalizations.of(context)!.unknownError) - ) - ], - ), + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingEncryptionSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ), - EncryptionMasterSwitch( - value: enabled, - onChange: (value) { - setState(() => enabled = value); - onEditValidate(); - } - ), - if (_plainDns != null) ...[ - const SizedBox(height: 8), - CustomCheckboxListTile( - value: _plainDns!, - onChanged: (v) => setState(() => _plainDns = v), - title: AppLocalizations.of(context)!.enablePlainDns, - subtitle: AppLocalizations.of(context)!.enablePlainDnsDescription, - disabled: enabled == false, - ), - ], - SectionLabel( - label: AppLocalizations.of(context)!.serverConfiguration, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - EncryptionTextField( - enabled: enabled, - controller: domainNameController, - icon: Icons.link_rounded, - onChanged: (value) { - setState(() => domainError = validateDomain(context, value)); - onEditValidate(); - }, - errorText: domainError, - label: AppLocalizations.of(context)!.domainName, - helperText: AppLocalizations.of(context)!.domainNameDescription, - ), - const SizedBox(height: 10), - CustomSwitchListTile( - value: redirectHttps, - onChanged: (value) { - setState(() => redirectHttps = value); - onEditValidate(); - }, - title: AppLocalizations.of(context)!.redirectHttps, - disabled: !enabled, - ), - const SizedBox(height: 10), - Wrap( - children: [ - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: httpsPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => httpsPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: httpsPortError, - label: AppLocalizations.of(context)!.httpsPort, - keyboardType: TextInputType.number, - ), - ), - Padding( - padding: width <= 900 - ? const EdgeInsets.symmetric(vertical: 24) - : const EdgeInsets.all(0), - child: FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: tlsPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => tlsPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: tlsPortError, - label: AppLocalizations.of(context)!.tlsPort, - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: dnsOverQuicPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => dnsOverQuicPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: dnsOverQuicPortError, - label: AppLocalizations.of(context)!.dnsOverQuicPort, - keyboardType: TextInputType.number, - ), - ), - ], - ), - SectionLabel( - label: AppLocalizations.of(context)!.certificates, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 20), - Flexible( - child: Text( - AppLocalizations.of(context)!.certificatesDescription, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - ) - ) - ], - ), - ), - ), - const SizedBox(height: 20), - RadioListTile( - value: 0, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.certificateFilePath, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - RadioListTile( - value: 1, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pasteCertificateContent, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - const SizedBox(height: 10), - if (certificateOption == 0) EncryptionTextField( - enabled: enabled, - controller: certificatePathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificatePathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificatePath, - errorText: certificatePathError, - ), - if (certificateOption == 1) EncryptionTextField( - enabled: enabled, - controller: certificateContentController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificateContentError = validateCertificate(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificateContent, - errorText: certificateContentError, - multiline: true, - keyboardType: TextInputType.multiline, - ), - if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ - const SizedBox(height: 20), - if (certKeyValid!.validChain != null) ...[ - Status( - valid: certKeyValid!.validChain ?? false, - label: certKeyValid!.validChain == true - ? AppLocalizations.of(context)!.validCertificateChain - : AppLocalizations.of(context)!.invalidCertificateChain, - ), - const SizedBox(height: 10), - ], - if (certKeyValid!.subject != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid?.subject}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!.issuer != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid?.issuer}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!.notAfter != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid?.notAfter}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!.dnsNames != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid?.dnsNames?.join(', ')}" - ), - const SizedBox(height: 10), - ], - ], - SectionLabel( - label: AppLocalizations.of(context)!.privateKey, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - RadioListTile( - value: 0, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.privateKeyFile, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - RadioListTile( - value: 1, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pastePrivateKey, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - if (privateKeyOption == 0) const SizedBox(height: 10), - if (privateKeyOption == 1) ...[ - CustomSwitchListTile( - value: usePreviouslySavedKey, - onChanged: (value) => setState(() => usePreviouslySavedKey = value), - title: AppLocalizations.of(context)!.usePreviousKey, - ), - const SizedBox(height: 10) - ], - if (privateKeyOption == 0) EncryptionTextField( - enabled: enabled, - controller: privateKeyPathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => privateKeyPathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.privateKeyPath, - errorText: privateKeyPathError, - ), - if (privateKeyOption == 1) EncryptionTextField( - enabled: enabled == true - ? !usePreviouslySavedKey - : false, - controller: pastePrivateKeyController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.pastePrivateKey, - errorText: pastePrivateKeyError, - keyboardType: TextInputType.multiline, - multiline: true, - ), - const SizedBox(height: 20), - if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ - if (certKeyValid!.validKey != null) ...[ - Status( - valid: certKeyValid!.validKey ?? false, - label: certKeyValid!.validKey == true - ? AppLocalizations.of(context)!.validPrivateKey - : AppLocalizations.of(context)!.invalidPrivateKey, - ), - const SizedBox(height: 10) - ], - if (certKeyValid!.validPair != null && certKeyValid!.validPair == false) ...[ - Status( - valid: false, - label: AppLocalizations.of(context)!.keysNotMatch, - ), - const SizedBox(height: 10) - ], - if (certKeyValid!.keyType != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!.keyType}" - ), - const SizedBox(height: 10), - ], - const SizedBox(height: 10) - ] + ) ], - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + ) + ); + + case LoadStatus.loaded: + return ListView( + children: [ + EncryptionMasterSwitch( + value: enabled, + onChange: (value) { + setState(() => enabled = value); + onEditValidate(); + } + ), + SectionLabel( + label: AppLocalizations.of(context)!.serverConfiguration, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + EncryptionTextField( + enabled: enabled, + controller: domainNameController, + icon: Icons.link_rounded, + onChanged: (value) { + setState(() => domainError = validateDomain(context, value)); + onEditValidate(); + }, + errorText: domainError, + label: AppLocalizations.of(context)!.domainName, + helperText: AppLocalizations.of(context)!.domainNameDescription, + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: redirectHttps, + onChanged: (value) { + setState(() => redirectHttps = value); + onEditValidate(); + }, + title: AppLocalizations.of(context)!.redirectHttps, + disabled: !enabled, + ), + const SizedBox(height: 10), + Wrap( children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.encryptionSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, + FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: httpsPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => httpsPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: httpsPortError, + label: AppLocalizations.of(context)!.httpsPort, + keyboardType: TextInputType.number, ), - ) + ), + Padding( + padding: width <= 900 + ? const EdgeInsets.symmetric(vertical: 24) + : const EdgeInsets.all(0), + child: FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: tlsPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => tlsPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: tlsPortError, + label: AppLocalizations.of(context)!.tlsPort, + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: dnsOverQuicPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => dnsOverQuicPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: dnsOverQuicPortError, + label: AppLocalizations.of(context)!.dnsOverQuicPort, + keyboardType: TextInputType.number, + ), + ), ], ), - ); - - default: - return const SizedBox(); - } - }, - ), + SectionLabel( + label: AppLocalizations.of(context)!.certificates, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + Card( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 20), + Flexible( + child: Text( + AppLocalizations.of(context)!.certificatesDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ) + ) + ], + ), + ), + ), + const SizedBox(height: 20), + RadioListTile( + value: 0, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.certificateFilePath, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + RadioListTile( + value: 1, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pasteCertificateContent, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + const SizedBox(height: 10), + if (certificateOption == 0) EncryptionTextField( + enabled: enabled, + controller: certificatePathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificatePathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificatePath, + errorText: certificatePathError, + ), + if (certificateOption == 1) EncryptionTextField( + enabled: enabled, + controller: certificateContentController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificateContentError = validateCertificate(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificateContent, + errorText: certificateContentError, + multiline: true, + keyboardType: TextInputType.multiline, + ), + if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ + const SizedBox(height: 20), + if (certKeyValid!.validChain != null) ...[ + Status( + valid: certKeyValid!.validChain ?? false, + label: certKeyValid!.validChain == true + ? AppLocalizations.of(context)!.validCertificateChain + : AppLocalizations.of(context)!.invalidCertificateChain, + ), + const SizedBox(height: 10), + ], + if (certKeyValid!.subject != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid?.subject}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!.issuer != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid?.issuer}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!.notAfter != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid?.notAfter}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!.dnsNames != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid?.dnsNames?.join(', ')}" + ), + const SizedBox(height: 10), + ], + ], + SectionLabel( + label: AppLocalizations.of(context)!.privateKey, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + RadioListTile( + value: 0, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.privateKeyFile, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + RadioListTile( + value: 1, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pastePrivateKey, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + if (privateKeyOption == 0) const SizedBox(height: 10), + if (privateKeyOption == 1) ...[ + CustomSwitchListTile( + value: usePreviouslySavedKey, + onChanged: (value) => setState(() => usePreviouslySavedKey = value), + title: AppLocalizations.of(context)!.usePreviousKey, + ), + const SizedBox(height: 10) + ], + if (privateKeyOption == 0) EncryptionTextField( + enabled: enabled, + controller: privateKeyPathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => privateKeyPathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.privateKeyPath, + errorText: privateKeyPathError, + ), + if (privateKeyOption == 1) EncryptionTextField( + enabled: enabled == true + ? !usePreviouslySavedKey + : false, + controller: pastePrivateKeyController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.pastePrivateKey, + errorText: pastePrivateKeyError, + keyboardType: TextInputType.multiline, + multiline: true, + ), + const SizedBox(height: 20), + if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ + if (certKeyValid!.validKey != null) ...[ + Status( + valid: certKeyValid!.validKey ?? false, + label: certKeyValid!.validKey == true + ? AppLocalizations.of(context)!.validPrivateKey + : AppLocalizations.of(context)!.invalidPrivateKey, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!.validPair != null && certKeyValid!.validPair == false) ...[ + Status( + valid: false, + label: AppLocalizations.of(context)!.keysNotMatch, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!.keyType != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!.keyType}" + ), + const SizedBox(height: 10), + ], + const SizedBox(height: 10) + ] + ], + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.encryptionSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, ) ); } diff --git a/lib/screens/settings/encryption/encryption_functions.dart b/lib/screens/settings/encryption/encryption_functions.dart index c7700e4..c9c0c4b 100644 --- a/lib/screens/settings/encryption/encryption_functions.dart +++ b/lib/screens/settings/encryption/encryption_functions.dart @@ -1,11 +1,11 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; String? validateDomain(BuildContext context, String domain) { - if (Regexps.domain.hasMatch(domain)) { + RegExp regExp = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$'); + if (regExp.hasMatch(domain)) { return null; } else { @@ -23,7 +23,8 @@ String? validatePort(BuildContext context, String value) { } String? validateCertificate(BuildContext context, String cert) { - if (Regexps.certificate.hasMatch(cert.replaceAll('\n', ''))) { + final regExp = RegExp(r'(-{3,}(\bBEGIN CERTIFICATE\b))|(-{3,}-{3,}(\END CERTIFICATE\b)-{3,})', multiLine: true); + if (regExp.hasMatch(cert.replaceAll('\n', ''))) { return null; } else { @@ -32,7 +33,8 @@ String? validateCertificate(BuildContext context, String cert) { } String? validatePrivateKey(BuildContext context, String cert) { - if (Regexps.privateKey.hasMatch(cert.replaceAll('\n', ''))) { + final regExp = RegExp(r'(-{3,}(\bBEGIN\b).*(PRIVATE KEY\b))|(-{3,}-{3,}(\bEND\b).*(PRIVATE KEY\b)-{3,})', multiLine: true); + if (regExp.hasMatch(cert.replaceAll('\n', ''))) { return null; } else { @@ -41,7 +43,8 @@ String? validatePrivateKey(BuildContext context, String cert) { } String? validatePath(BuildContext context, String cert) { - if (Regexps.path.hasMatch(cert)) { + final regExp = RegExp(r'^(\/{0,1}(?!\/))[A-Za-z0-9\/\-_]+(\.([a-zA-Z]+))?$'); + if (regExp.hasMatch(cert)) { return null; } else { diff --git a/lib/screens/settings/encryption/error_message.dart b/lib/screens/settings/encryption/error_message.dart index 01dc221..be132d8 100644 --- a/lib/screens/settings/encryption/error_message.dart +++ b/lib/screens/settings/encryption/error_message.dart @@ -5,9 +5,9 @@ class ErrorMessageEncryption extends StatelessWidget { final String errorMessage; const ErrorMessageEncryption({ - super.key, + Key? key, required this.errorMessage, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/encryption/master_switch.dart b/lib/screens/settings/encryption/master_switch.dart index 474c0f9..f13c4fa 100644 --- a/lib/screens/settings/encryption/master_switch.dart +++ b/lib/screens/settings/encryption/master_switch.dart @@ -6,10 +6,10 @@ class EncryptionMasterSwitch extends StatelessWidget { final void Function(bool) onChange; const EncryptionMasterSwitch({ - super.key, + Key? key, required this.value, required this.onChange - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/encryption/reset_settings_modal.dart b/lib/screens/settings/encryption/reset_settings_modal.dart deleted file mode 100644 index 4429162..0000000 --- a/lib/screens/settings/encryption/reset_settings_modal.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -class ResetSettingsModal extends StatelessWidget { - final void Function() onConfirm; - - const ResetSettingsModal({ - super.key, - required this.onConfirm, - }); - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: Column( - children: [ - Icon( - Icons.restore_rounded, - size: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.resetSettings, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - content: Text( - AppLocalizations.of(context)!.resetEncryptionSettingsDescription, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - TextButton( - onPressed: () { - onConfirm(); - Navigator.pop(context); - }, - child: Text(AppLocalizations.of(context)!.confirm) - ), - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/encryption/status.dart b/lib/screens/settings/encryption/status.dart index 0c11f5a..eb344ae 100644 --- a/lib/screens/settings/encryption/status.dart +++ b/lib/screens/settings/encryption/status.dart @@ -8,10 +8,10 @@ class Status extends StatelessWidget { final String label; const Status({ - super.key, + Key? key, required this.valid, required this.label - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index f32446d..4625136 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -1,24 +1,26 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:store_checker/store_checker.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/settings/settings.dart'; -import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/top_items_list_settings.dart'; +import 'package:adguard_home_manager/screens/settings/general_settings/reorderable_top_items_home.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; +import 'package:adguard_home_manager/functions/check_app_updates.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/functions/open_url.dart'; +import 'package:adguard_home_manager/functions/app_update_download_link.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class GeneralSettings extends StatefulWidget { - final bool splitView; - - const GeneralSettings({ - super.key, - required this.splitView, - }); + const GeneralSettings({Key? key}) : super(key: key); @override State createState() => _GeneralSettingsState(); @@ -40,16 +42,15 @@ class _GeneralSettingsState extends State { required Future Function(bool) function }) async { final result = await function(newStatus); - if (!context.mounted) return; if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.settingsUpdatedSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.cannotUpdateSettings, color: Colors.red @@ -57,208 +58,231 @@ class _GeneralSettingsState extends State { } } - // Future checkUpdatesAvailable() async { - // setState(() => appUpdatesStatus = AppUpdatesStatus.checking); + Future checkUpdatesAvailable() async { + setState(() => appUpdatesStatus = AppUpdatesStatus.checking); - // final res = await checkAppUpdates( - // currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber, - // setUpdateAvailable: appConfigProvider.setAppUpdatesAvailable, - // installationSource: appConfigProvider.installationSource, - // isBeta: appConfigProvider.getAppInfo!.version.contains('beta'), - // ); + final res = await checkAppUpdates( + currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber, + setUpdateAvailable: appConfigProvider.setAppUpdatesAvailable, + installationSource: appConfigProvider.installationSource, + isBeta: appConfigProvider.getAppInfo!.version.contains('beta'), + ); - // if (!mounted) return; - // if (res != null) { - // setState(() => appUpdatesStatus = AppUpdatesStatus.available); - // } - // else { - // setState(() => appUpdatesStatus = AppUpdatesStatus.recheck); - // } - // } + if (!mounted) return; + if (res != null) { + setState(() => appUpdatesStatus = AppUpdatesStatus.available); + } + else { + setState(() => appUpdatesStatus = AppUpdatesStatus.recheck); + } + } - // Widget generateAppUpdateStatus() { - // if (appUpdatesStatus == AppUpdatesStatus.available) { - // return IconButton( - // onPressed: appConfigProvider.appUpdatesAvailable != null - // ? () async { - // final link = getAppUpdateDownloadLink(appConfigProvider.appUpdatesAvailable!); - // if (link != null) { - // openUrl(link); - // } - // } - // : null, - // icon: const Icon(Icons.download_rounded), - // tooltip: AppLocalizations.of(context)!.downloadUpdate, - // ); - // } - // else if (appUpdatesStatus == AppUpdatesStatus.checking) { - // return const Padding( - // padding: EdgeInsets.only(right: 16), - // child: SizedBox( - // width: 24, - // height: 24, - // child: CircularProgressIndicator( - // strokeWidth: 3, - // ) - // ), - // ); - // } - // else { - // return IconButton( - // onPressed: checkUpdatesAvailable, - // icon: const Icon(Icons.refresh_rounded), - // tooltip: AppLocalizations.of(context)!.checkUpdates, - // ); - // } - // } + Widget generateAppUpdateStatus() { + if (appUpdatesStatus == AppUpdatesStatus.available) { + return IconButton( + onPressed: appConfigProvider.appUpdatesAvailable != null + ? () async { + final link = getAppUpdateDownloadLink(appConfigProvider.appUpdatesAvailable!); + if (link != null) { + openUrl(link); + } + } + : null, + icon: const Icon(Icons.download_rounded), + tooltip: AppLocalizations.of(context)!.downloadUpdate, + ); + } + else if (appUpdatesStatus == AppUpdatesStatus.checking) { + return const Padding( + padding: EdgeInsets.only(right: 16), + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 3, + ) + ), + ); + } + else { + return IconButton( + onPressed: checkUpdatesAvailable, + icon: const Icon(Icons.refresh_rounded), + tooltip: AppLocalizations.of(context)!.checkUpdates, + ); + } + } return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.generalSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), - body: SafeArea( - child: ListView( - children: [ - SectionLabel(label: AppLocalizations.of(context)!.home), - CustomListTile( - icon: Icons.exposure_zero_rounded, - title: AppLocalizations.of(context)!.hideZeroValues, - subtitle: AppLocalizations.of(context)!.hideZeroValuesDescription, - trailing: Switch( - value: appConfigProvider.hideZeroValues, - onChanged: (value) => updateSettings( - newStatus: value, - function: appConfigProvider.setHideZeroValues - ), - ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.hideZeroValues, + body: ListView( + children: [ + SectionLabel(label: AppLocalizations.of(context)!.home), + CustomListTile( + icon: Icons.exposure_zero_rounded, + title: AppLocalizations.of(context)!.hideZeroValues, + subtitle: AppLocalizations.of(context)!.hideZeroValuesDescription, + trailing: Switch( + value: appConfigProvider.hideZeroValues, + onChanged: (value) => updateSettings( + newStatus: value, function: appConfigProvider.setHideZeroValues ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) ), - CustomListTile( - icon: Icons.show_chart_rounded, - title: AppLocalizations.of(context)!.combinedChart, - subtitle: AppLocalizations.of(context)!.combinedChartDescription, - trailing: Switch( - value: appConfigProvider.combinedChartHome, - onChanged: (value) => updateSettings( - newStatus: value, - function: appConfigProvider.setCombinedChartHome - ), - ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.combinedChartHome, + onTap: () => updateSettings( + newStatus: !appConfigProvider.hideZeroValues, + function: appConfigProvider.setHideZeroValues + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) + ), + CustomListTile( + icon: Icons.show_chart_rounded, + title: AppLocalizations.of(context)!.combinedChart, + subtitle: AppLocalizations.of(context)!.combinedChartDescription, + trailing: Switch( + value: appConfigProvider.combinedChartHome, + onChanged: (value) => updateSettings( + newStatus: value, function: appConfigProvider.setCombinedChartHome ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) ), - CustomListTile( - icon: Icons.remove_red_eye_rounded, - title: AppLocalizations.of(context)!.hideServerAddress, - subtitle: AppLocalizations.of(context)!.hideServerAddressDescription, - trailing: Switch( - value: appConfigProvider.hideServerAddress, - onChanged: (value) => updateSettings( - newStatus: value, - function: appConfigProvider.setHideServerAddress - ), - ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.hideServerAddress, + onTap: () => updateSettings( + newStatus: !appConfigProvider.combinedChartHome, + function: appConfigProvider.setCombinedChartHome + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) + ), + CustomListTile( + icon: Icons.remove_red_eye_rounded, + title: AppLocalizations.of(context)!.hideServerAddress, + subtitle: AppLocalizations.of(context)!.hideServerAddressDescription, + trailing: Switch( + value: appConfigProvider.hideServerAddress, + onChanged: (value) => updateSettings( + newStatus: value, function: appConfigProvider.setHideServerAddress ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.hideServerAddress, + function: appConfigProvider.setHideServerAddress + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) + ), + CustomListTile( + icon: Icons.reorder_rounded, + title: AppLocalizations.of(context)!.topItemsOrder, + subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const ReorderableTopItemsHome() ) - ), - CustomListTile( - icon: Icons.reorder_rounded, - title: AppLocalizations.of(context)!.topItemsOrder, - subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, - onTap: () => widget.splitView == true - ? Navigator.of(settingsNavigatorKey.currentContext!).push( - MaterialPageRoute(builder: (ctx) => const TopItemsListSettings()) - ) - : Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const TopItemsListSettings() - ) - ) - ), - SectionLabel(label: AppLocalizations.of(context)!.logs), - CustomListTile( - icon: Icons.timer_rounded, - title: AppLocalizations.of(context)!.timeLogs, - subtitle: AppLocalizations.of(context)!.timeLogsDescription, - trailing: Switch( - value: appConfigProvider.showTimeLogs, - onChanged: (value) => updateSettings( - newStatus: value, - function: appConfigProvider.setshowTimeLogs - ), + ) + ), + CustomListTile( + icon: Icons.donut_large_rounded, + title: AppLocalizations.of(context)!.showTopItemsChart, + subtitle: AppLocalizations.of(context)!.showTopItemsChartDescription, + trailing: Switch( + value: appConfigProvider.showTopItemsChart, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setShowTopItemsChart ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.showTimeLogs, + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.showTopItemsChart, + function: appConfigProvider.setShowTopItemsChart + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) + ), + SectionLabel(label: AppLocalizations.of(context)!.logs), + CustomListTile( + icon: Icons.timer_rounded, + title: AppLocalizations.of(context)!.timeLogs, + subtitle: AppLocalizations.of(context)!.timeLogsDescription, + trailing: Switch( + value: appConfigProvider.showTimeLogs, + onChanged: (value) => updateSettings( + newStatus: value, function: appConfigProvider.setshowTimeLogs ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) ), - CustomListTile( - icon: Icons.more, - title: AppLocalizations.of(context)!.ipLogs, - subtitle: AppLocalizations.of(context)!.ipLogsDescription, - trailing: Switch( - value: appConfigProvider.showIpLogs, - onChanged: (value) => updateSettings( - newStatus: value, - function: appConfigProvider.setShowIpLogs - ), - ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.showIpLogs, + onTap: () => updateSettings( + newStatus: !appConfigProvider.showTimeLogs, + function: appConfigProvider.setshowTimeLogs + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) + ), + CustomListTile( + icon: Icons.more, + title: AppLocalizations.of(context)!.ipLogs, + subtitle: AppLocalizations.of(context)!.ipLogsDescription, + trailing: Switch( + value: appConfigProvider.showIpLogs, + onChanged: (value) => updateSettings( + newStatus: value, function: appConfigProvider.setShowIpLogs ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) ), - // if (!(Platform.isAndroid || Platform.isIOS) || (Platform.isAndroid && (appConfigProvider.installationSource == InstallationAppReferrer.androidManually ))) ...[ - // SectionLabel(label: AppLocalizations.of(context)!.application), - // CustomListTile( - // icon: Icons.system_update_rounded, - // title: AppLocalizations.of(context)!.appUpdates, - // subtitle: appConfigProvider.appUpdatesAvailable != null - // ? AppLocalizations.of(context)!.updateAvailable - // : AppLocalizations.of(context)!.usingLatestVersion, - // trailing: generateAppUpdateStatus() - // ) - // ] - ], - ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.showIpLogs, + function: appConfigProvider.setShowIpLogs + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) + ), + if ( + !(Platform.isAndroid || Platform.isIOS) || + (Platform.isAndroid && ( + appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE || + appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER || + appConfigProvider.installationSource == Source.UNKNOWN + )) + ) ...[ + SectionLabel(label: AppLocalizations.of(context)!.application), + CustomListTile( + icon: Icons.system_update_rounded, + title: AppLocalizations.of(context)!.appUpdates, + subtitle: appConfigProvider.appUpdatesAvailable != null + ? AppLocalizations.of(context)!.updateAvailable + : AppLocalizations.of(context)!.usingLatestVersion, + trailing: generateAppUpdateStatus() + ) + ] + ], ) ); } diff --git a/lib/screens/settings/general_settings/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/reorderable_top_items_home.dart new file mode 100644 index 0000000..2b7bfe1 --- /dev/null +++ b/lib/screens/settings/general_settings/reorderable_top_items_home.dart @@ -0,0 +1,320 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as reorderable_list_library; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; + +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; + +class _ItemData { + final HomeTopItems title; + final Key key; + + const _ItemData({ + required this.title, + required this.key + }); +} + +enum DraggingMode { + iOS, + android, +} + +class ReorderableTopItemsHome extends StatefulWidget { + const ReorderableTopItemsHome({super.key}); + + @override + State createState() => _ReorderableTopItemsHomeState(); +} + +class _ReorderableTopItemsHomeState extends State { + List homeTopItemsList = []; + List persistHomeTopItemsList = []; + List<_ItemData> renderItems = []; + + int _indexOfKey(Key key) { + return renderItems.indexWhere((_ItemData d) => d.key == key); + } + + bool _reorderCallback(Key item, Key newPosition) { + int draggingIndex = _indexOfKey(item); + int newPositionIndex = _indexOfKey(newPosition); + + final draggedItem = renderItems[draggingIndex]; + + final List reorderedItems = reorderEnumItems(draggingIndex, newPositionIndex); + + setState(() { + renderItems.removeAt(draggingIndex); + renderItems.insert(newPositionIndex, draggedItem); + homeTopItemsList = reorderedItems; + }); + + return true; + } + + void _reorderDone(Key item) { + renderItems[_indexOfKey(item)]; + setState(() => persistHomeTopItemsList = homeTopItemsList); + } + + List reorderEnumItems(int oldIndex, int newIndex) { + final List list = [...homeTopItemsList]; + final HomeTopItems item = list.removeAt(oldIndex); + list.insert(newIndex, item); + return list; + } + + @override + void initState() { + final appConfigProvider = Provider.of(context, listen: false); + homeTopItemsList = appConfigProvider.homeTopItemsOrder; + persistHomeTopItemsList = appConfigProvider.homeTopItemsOrder; + renderItems = appConfigProvider.homeTopItemsOrder.asMap().entries.map( + (e) => _ItemData( + key: ValueKey(e.key), + title: e.value, + ) + ).toList(); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + Widget tile(HomeTopItems title) { + switch (title) { + case HomeTopItems.queriedDomains: + return CustomListTile( + title: AppLocalizations.of(context)!.topQueriedDomains, + icon: Icons.install_desktop_outlined, + padding: const EdgeInsets.all(16) + ); + + case HomeTopItems.blockedDomains: + return CustomListTile( + title: AppLocalizations.of(context)!.topBlockedDomains, + icon: Icons.block_rounded, + padding: const EdgeInsets.all(16) + ); + + case HomeTopItems.recurrentClients: + return CustomListTile( + title: AppLocalizations.of(context)!.topClients, + icon: Icons.smartphone_rounded, + padding: const EdgeInsets.all(16) + ); + + case HomeTopItems.topUpstreams: + return CustomListTile( + title: AppLocalizations.of(context)!.topUpstreams, + icon: Icons.upload_file_rounded, + padding: const EdgeInsets.all(16) + ); + + case HomeTopItems.avgUpstreamResponseTime: + return CustomListTile( + title: AppLocalizations.of(context)!.averageUpstreamResponseTime, + icon: Icons.timer_rounded, + padding: const EdgeInsets.all(16) + ); + + default: + return const SizedBox(); + } + } + + Future onWillPopScope(bool popInvoked) async { + if (!listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList)) { + await showDialog( + context: context, + useRootNavigator: false, + builder: (ctx) => AlertDialog( + title: Text(AppLocalizations.of(context)!.discardChanges), + content: Text(AppLocalizations.of(context)!.discardChangesDescription), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + Navigator.pop(context); + }, + child: Text(AppLocalizations.of(context)!.confirm) + ), + const SizedBox(width: 8), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + ], + ) + ], + ) + ); + return false; + } + else { + return true; + } + } + + void saveSettings() async { + final result = await appConfigProvider.setHomeTopItemsOrder(homeTopItemsList); + if (result == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.settingsSaved, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.settingsNotSaved, + color: Colors.red + ); + } + } + + return PopScope( + canPop: listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList), + onPopInvoked: onWillPopScope, + child: Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.topItemsOrder), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + actions: [ + IconButton( + onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) + ? () => saveSettings() + : null, + icon: const Icon(Icons.save_rounded), + tooltip: AppLocalizations.of(context)!.save, + ), + const SizedBox(width: 8) + ], + ), + body: Column( + children: [ + Card( + margin: const EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + Flexible( + child: Text(AppLocalizations.of(context)!.topItemsReorderInfo) + ) + ], + ), + ), + ), + Expanded( + child: reorderable_list_library.ReorderableList( + onReorder: _reorderCallback, + onReorderDone: _reorderDone, + child: ListView.builder( + itemBuilder: (context, index) => reorderable_list_library.ReorderableItem( + key: renderItems[index].key, + childBuilder: (context, state) => _Item( + tileWidget: tile(renderItems[index].title), + isFirst: index == 0, + isLast: index == renderItems.length - 1, + state: state + ), + ), + itemCount: renderItems.length, + ) + ), + ), + ], + ), + ), + ); + } +} + +class _Item extends StatelessWidget { + final Widget tileWidget; + final bool isFirst; + final bool isLast; + final reorderable_list_library.ReorderableItemState state; + + const _Item({ + required this.tileWidget, + required this.isFirst, + required this.isLast, + required this.state, + }); + + @override + Widget build(BuildContext context) { + BoxDecoration decoration; + + if ( + state == reorderable_list_library.ReorderableItemState.dragProxy || + state == reorderable_list_library.ReorderableItemState.dragProxyFinished + ) { + decoration = BoxDecoration( + color: Theme.of(context).colorScheme.surface.withOpacity(0.7) + ); + } + else { + bool placeholder = state == reorderable_list_library.ReorderableItemState.placeholder; + decoration = BoxDecoration( + border: Border( + top: isFirst && !placeholder ? BorderSide( + width: 1, + color: Theme.of(context).colorScheme.primary.withOpacity(0.1) + ) : BorderSide.none, + bottom: isLast && placeholder ? BorderSide.none : BorderSide( + width: 1, + color: Theme.of(context).colorScheme.primary.withOpacity(0.1) + ), + ), + ); + } + + return reorderable_list_library.DelayedReorderableListener( + child: Container( + decoration: decoration, + child: SafeArea( + top: false, + bottom: false, + child: Opacity( + opacity: state == reorderable_list_library.ReorderableItemState.placeholder ? 0.0 : 1.0, + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: tileWidget + ), + ], + ), + ), + ) + ), + ) + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart deleted file mode 100644 index 332a64b..0000000 --- a/lib/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart +++ /dev/null @@ -1,314 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as reorderable_list; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; - -import 'package:adguard_home_manager/constants/enums.dart'; - -class _ItemData { - final HomeTopItems title; - final Key key; - - const _ItemData({ - required this.title, - required this.key - }); -} - -enum DraggingMode { - iOS, - android, -} - -class ReorderableTopItemsHome extends StatefulWidget { - final List persistHomeTopItems; - final void Function(List value) setPersistHomeTopItems; - - const ReorderableTopItemsHome({ - super.key, - required this.persistHomeTopItems, - required this.setPersistHomeTopItems, - }); - - @override - State createState() => _ReorderableTopItemsHomeState(); -} - -class _ReorderableTopItemsHomeState extends State { - List homeTopItemsList = []; - List<_ItemData> renderItems = []; - - int _indexOfKey(Key key) { - return renderItems.indexWhere((_ItemData d) => d.key == key); - } - - bool _reorderCallback(Key item, Key newPosition) { - int draggingIndex = _indexOfKey(item); - int newPositionIndex = _indexOfKey(newPosition); - - final draggedItem = renderItems[draggingIndex]; - - final List reorderedItems = reorderEnumItems(draggingIndex, newPositionIndex); - - setState(() { - renderItems.removeAt(draggingIndex); - renderItems.insert(newPositionIndex, draggedItem); - homeTopItemsList = reorderedItems; - }); - - return true; - } - - void _reorderDone(Key item) { - renderItems[_indexOfKey(item)]; - widget.setPersistHomeTopItems(homeTopItemsList); - } - - List reorderEnumItems(int oldIndex, int newIndex) { - final List list = [...homeTopItemsList]; - final HomeTopItems item = list.removeAt(oldIndex); - list.insert(newIndex, item); - return list; - } - - @override - void initState() { - homeTopItemsList = widget.persistHomeTopItems; - renderItems = widget.persistHomeTopItems.asMap().entries.map( - (e) => _ItemData( - key: ValueKey(e.key), - title: e.value, - ) - ).toList(); - - super.initState(); - } - - @override - Widget build(BuildContext context) { - final draggingMode = Platform.isAndroid - ? DraggingMode.android - : DraggingMode.iOS; - - return SafeArea( - top: false, - bottom: true, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverList.list( - children: [ - Card( - margin: const EdgeInsets.all(16), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Flexible( - child: Text(AppLocalizations.of(context)!.topItemsReorderInfo) - ) - ], - ), - ), - ), - if (homeTopItemsList.isEmpty) Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Center( - child: Text( - AppLocalizations.of(context)!.noElementsReorderMessage, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ), - ], - ), - ), - if (homeTopItemsList.isNotEmpty) reorderable_list.ReorderableList( - onReorder: _reorderCallback, - onReorderDone: _reorderDone, - child: ListView.builder( - primary: false, - shrinkWrap: true, - padding: const EdgeInsets.only(top: 0), - itemBuilder: (context, index) => reorderable_list.ReorderableItem( - key: renderItems[index].key, - childBuilder: (context, state) { - if (draggingMode == DraggingMode.android) { - return reorderable_list.DelayedReorderableListener( - child: _ReorderableTile( - draggingMode: draggingMode, - isFirst: index == 0, - isLast: index == renderItems.length - 1, - state: state, - tileWidget: _TopItemTile(tile: renderItems[index].title), - ), - ); - } - else { - return _ReorderableTile( - draggingMode: draggingMode, - isFirst: index == 0, - isLast: index == renderItems.length - 1, - state: state, - tileWidget: _TopItemTile(tile: renderItems[index].title), - ); - } - }, - ), - itemCount: renderItems.length, - ) - ), - ] - ) - ], - ), - ), - ); - } -} - -class _TopItemTile extends StatelessWidget { - final HomeTopItems tile; - - const _TopItemTile({ - required this.tile, - }); - - @override - Widget build(BuildContext context) { - switch (tile) { - case HomeTopItems.queriedDomains: - return CustomListTile( - title: AppLocalizations.of(context)!.topQueriedDomains, - icon: Icons.install_desktop_outlined, - padding: const EdgeInsets.all(16) - ); - - case HomeTopItems.blockedDomains: - return CustomListTile( - title: AppLocalizations.of(context)!.topBlockedDomains, - icon: Icons.block_rounded, - padding: const EdgeInsets.all(16) - ); - - case HomeTopItems.recurrentClients: - return CustomListTile( - title: AppLocalizations.of(context)!.topClients, - icon: Icons.smartphone_rounded, - padding: const EdgeInsets.all(16) - ); - - case HomeTopItems.topUpstreams: - return CustomListTile( - title: AppLocalizations.of(context)!.topUpstreams, - icon: Icons.upload_file_rounded, - padding: const EdgeInsets.all(16) - ); - - case HomeTopItems.avgUpstreamResponseTime: - return CustomListTile( - title: AppLocalizations.of(context)!.averageUpstreamResponseTime, - icon: Icons.timer_rounded, - padding: const EdgeInsets.all(16) - ); - - default: - return const SizedBox(); - } - } -} - -class _ReorderableTile extends StatelessWidget { - final Widget tileWidget; - final bool isFirst; - final bool isLast; - final reorderable_list.ReorderableItemState state; - final DraggingMode draggingMode; - - const _ReorderableTile({ - required this.tileWidget, - required this.isFirst, - required this.isLast, - required this.state, - required this.draggingMode - }); - - @override - Widget build(BuildContext context) { - BoxDecoration getDecoration() { - if ( - state == reorderable_list.ReorderableItemState.dragProxy || - state == reorderable_list.ReorderableItemState.dragProxyFinished - ) { - return BoxDecoration( - color: Theme.of(context).colorScheme.surface.withOpacity(0.7) - ); - } - else { - bool placeholder = state == reorderable_list.ReorderableItemState.placeholder; - return BoxDecoration( - border: Border( - top: isFirst && !placeholder ? BorderSide( - width: 1, - color: Theme.of(context).colorScheme.primary.withOpacity(0.1) - ) : BorderSide.none, - bottom: isLast && placeholder ? BorderSide.none : BorderSide( - width: 1, - color: Theme.of(context).colorScheme.primary.withOpacity(0.1) - ), - ), - ); - } - } - - return Container( - decoration: getDecoration(), - child: SafeArea( - top: false, - bottom: false, - child: Opacity( - opacity: state == reorderable_list.ReorderableItemState.placeholder ? 0.0 : 1.0, - child: IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: tileWidget - ), - if (draggingMode == DraggingMode.iOS) reorderable_list.ReorderableListener( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Center( - child: Icon( - Icons.reorder, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ) - ], - ), - ), - ) - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart b/lib/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart deleted file mode 100644 index 3088692..0000000 --- a/lib/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; - -class ShowHideTopItemsList extends StatelessWidget { - final List enabledHomeTopItems; - final void Function(List) setEnabledHomeTopItems; - - const ShowHideTopItemsList({ - super.key, - required this.enabledHomeTopItems, - required this.setEnabledHomeTopItems, - }); - - @override - Widget build(BuildContext context) { - const padding = EdgeInsets.symmetric(horizontal: 16, vertical: 8); - - void updateValue(HomeTopItems value, bool newStatus) { - if (newStatus == true) { - setEnabledHomeTopItems([ - ...enabledHomeTopItems, - value - ]); - } - else { - setEnabledHomeTopItems(enabledHomeTopItems.where((e) => e != value).toList()); - } - } - - return SafeArea( - top: false, - bottom: true, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverList.list( - children: [ - const SizedBox(height: 8), - CustomSwitchListTile( - value: enabledHomeTopItems.contains(HomeTopItems.queriedDomains), - onChanged: (v) => updateValue(HomeTopItems.queriedDomains, v), - title: AppLocalizations.of(context)!.topQueriedDomains, - leadingIcon: Icons.install_desktop_outlined, - padding: padding, - ), - CustomSwitchListTile( - value: enabledHomeTopItems.contains(HomeTopItems.blockedDomains), - onChanged: (v) => updateValue(HomeTopItems.blockedDomains, v), - title: AppLocalizations.of(context)!.topBlockedDomains, - leadingIcon: Icons.block_rounded, - padding: padding, - ), - CustomSwitchListTile( - value: enabledHomeTopItems.contains(HomeTopItems.recurrentClients), - onChanged: (v) => updateValue(HomeTopItems.recurrentClients, v), - title: AppLocalizations.of(context)!.topClients, - leadingIcon: Icons.smartphone_rounded, - padding: padding, - ), - CustomSwitchListTile( - value: enabledHomeTopItems.contains(HomeTopItems.topUpstreams), - onChanged: (v) => updateValue(HomeTopItems.topUpstreams, v), - title: AppLocalizations.of(context)!.topUpstreams, - leadingIcon: Icons.upload_file_rounded, - padding: padding, - ), - CustomSwitchListTile( - value: enabledHomeTopItems.contains(HomeTopItems.avgUpstreamResponseTime), - onChanged: (v) => updateValue(HomeTopItems.avgUpstreamResponseTime, v), - title: AppLocalizations.of(context)!.averageUpstreamResponseTime, - leadingIcon: Icons.timer_rounded, - padding: padding, - ), - ] - ) - ], - ), - ) - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/general_settings/top_items_list/top_items_list_settings.dart b/lib/screens/settings/general_settings/top_items_list/top_items_list_settings.dart deleted file mode 100644 index 89b1613..0000000 --- a/lib/screens/settings/general_settings/top_items_list/top_items_list_settings.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart'; -import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart'; - -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; - -class TopItemsListSettings extends StatefulWidget { - const TopItemsListSettings({super.key}); - - @override - State createState() => _TopItemsListSettingsState(); -} - -class _TopItemsListSettingsState extends State with TickerProviderStateMixin { - late TabController _tabController; - - List persistHomeTopItemsList = []; - - @override - void initState() { - final appConfigProvider = Provider.of(context, listen: false); - persistHomeTopItemsList = appConfigProvider.homeTopItemsOrder; - - super.initState(); - - _tabController = TabController(length: 2, vsync: this); - } - - @override - Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - void saveSettings() async { - final result = await appConfigProvider.setHomeTopItemsOrder(persistHomeTopItemsList); - if (!context.mounted) return; - if (result == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.settingsSaved, - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.settingsNotSaved, - color: Colors.red - ); - } - } - - return Scaffold( - body: DefaultTabController( - length: 2, - child: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - title: Text(AppLocalizations.of(context)!.topItemsOrder), - actions: [ - IconButton( - onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) - ? () => saveSettings() - : null, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 8) - ], - bottom: TabBar( - controller: _tabController, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.reorder_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.reorder) - ], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.remove_red_eye_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.showHide) - ], - ), - ), - ] - ) - ), - ) - ], - body: TabBarView( - controller: _tabController, - children: [ - ReorderableTopItemsHome( - persistHomeTopItems: persistHomeTopItemsList, - setPersistHomeTopItems: (v) => setState(() => persistHomeTopItemsList = v), - ), - ShowHideTopItemsList( - enabledHomeTopItems: persistHomeTopItemsList, - setEnabledHomeTopItems: (v) => setState(() => persistHomeTopItemsList = v), - ) - ] - ) - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/logs_settings/config_widgets.dart b/lib/screens/settings/logs_settings/config_widgets.dart deleted file mode 100644 index 066f779..0000000 --- a/lib/screens/settings/logs_settings/config_widgets.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; -import 'package:flutter/material.dart'; -import 'package:uuid/uuid.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/settings/logs_settings/logs_settings.dart'; -import 'package:adguard_home_manager/widgets/section_label.dart'; -import 'package:adguard_home_manager/widgets/master_switch.dart'; -import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.dart'; - -class LogsConfigOptions extends StatelessWidget { - final bool generalSwitch; - final void Function(bool) updateGeneralSwitch; - final bool anonymizeClientIp; - final void Function(bool) updateAnonymizeClientIp; - final List retentionItems; - final String? retentionTime; - final void Function(String?) updateRetentionTime; - final void Function() onClear; - final void Function() onConfirm; - final List ignoredDomainsControllers; - final void Function(List) updateIgnoredDomainsControllers; - final TextEditingController customTimeController; - final String? customTimeError; - final void Function(String) validateCustomTime; - - const LogsConfigOptions({ - super.key, - required this.generalSwitch, - required this.updateGeneralSwitch, - required this.anonymizeClientIp, - required this.updateAnonymizeClientIp, - required this.retentionItems, - required this.retentionTime, - required this.updateRetentionTime, - required this.onClear, - required this.onConfirm, - required this.ignoredDomainsControllers, - required this.updateIgnoredDomainsControllers, - required this.customTimeController, - required this.customTimeError, - required this.validateCustomTime, - }); - - @override - Widget build(BuildContext context) { - const Uuid uuid = Uuid(); - - final List dropdownItemTranslation = [ - AppLocalizations.of(context)!.custom, - AppLocalizations.of(context)!.hours6, - AppLocalizations.of(context)!.hours24, - AppLocalizations.of(context)!.days7, - AppLocalizations.of(context)!.days30, - AppLocalizations.of(context)!.days90, - ]; - - void validateDomain(String value, String id) { - bool error = false; - if (Regexps.domain.hasMatch(value)) { - error = false; - } - else { - error = true; - } - updateIgnoredDomainsControllers( - ignoredDomainsControllers.map((entry) { - if (entry.id != id) return entry; - return DomainListItemController( - id: id, - controller: entry.controller, - error: error - ); - }).toList() - ); - } -print(retentionTime); - return ListView( - children: [ - const SizedBox(height: 16), - MasterSwitch( - label: AppLocalizations.of(context)!.enableLog, - value: generalSwitch, - onChange: updateGeneralSwitch - ), - const SizedBox(height: 16), - CustomCheckboxListTile( - value: anonymizeClientIp, - onChanged: (_) => updateAnonymizeClientIp(!anonymizeClientIp), - title: AppLocalizations.of(context)!.anonymizeClientIp, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: DropdownButtonFormField( - items: retentionItems.asMap().entries.map((item) => DropdownMenuItem( - value: item.value, - child: Text(dropdownItemTranslation[item.key]), - )).toList(), - value: retentionTime, - onChanged: (value) => updateRetentionTime(value.toString()), - decoration: InputDecoration( - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - label: Text(AppLocalizations.of(context)!.retentionTime) - ), - borderRadius: BorderRadius.circular(20), - ), - ), - if (retentionTime == "custom") Padding( - padding: const EdgeInsets.only(top: 24, left: 16, right: 16), - child: TextFormField( - controller: customTimeController, - onChanged: validateCustomTime, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.schedule_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.customTimeInHours, - errorText: customTimeError - ), - keyboardType: TextInputType.number, - ), - ), - Padding( - padding: const EdgeInsets.only(top: 24, bottom: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SectionLabel( - label: AppLocalizations.of(context)!.ignoredDomains, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0), - ), - Padding( - padding: const EdgeInsets.only(right: 6), - child: IconButton( - onPressed: () => updateIgnoredDomainsControllers([ - ...ignoredDomainsControllers, - DomainListItemController( - id: uuid.v4(), - controller: TextEditingController(), - error: false - ), - ]), - icon: const Icon(Icons.add) - ), - ) - ], - ), - ), - if (ignoredDomainsControllers.isNotEmpty) ...ignoredDomainsControllers.map((controller) => Padding( - padding: const EdgeInsets.only( - top: 12, bottom: 12, left: 16, right: 6 - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextFormField( - controller: controller.controller, - onChanged: (v) => validateDomain(v, controller.id), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.domain, - errorText: controller.error - ? AppLocalizations.of(context)!.invalidDomain - : null - ), - ), - ), - const SizedBox(width: 12), - Padding( - padding: controller.error - ? const EdgeInsets.only(bottom: 24) - : const EdgeInsets.all(0), - child: IconButton( - onPressed: () => updateIgnoredDomainsControllers( - ignoredDomainsControllers.where((e) => e.id != controller.id).toList() - ), - icon: const Icon(Icons.remove_circle_outline_outlined) - ), - ) - ], - ), - )), - if (ignoredDomainsControllers.isEmpty) Container( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text( - AppLocalizations.of(context)!.noIgnoredDomainsAdded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/logs_settings/logs_settings.dart b/lib/screens/settings/logs_settings/logs_settings.dart deleted file mode 100644 index c31304f..0000000 --- a/lib/screens/settings/logs_settings/logs_settings.dart +++ /dev/null @@ -1,246 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uuid/uuid.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/settings/logs_settings/config_widgets.dart'; -import 'package:adguard_home_manager/widgets/load_status_widgets.dart'; - -import 'package:adguard_home_manager/models/querylog_config.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; - -class DomainListItemController { - final String id; - final TextEditingController controller; - bool error; - - DomainListItemController({ - required this.id, - required this.controller, - required this.error - }); -} - -class LogsSettings extends StatefulWidget { - const LogsSettings({super.key}); - - @override - State createState() => _LogsSettingsState(); -} - -class _LogsSettingsState extends State { - final Uuid uuid = const Uuid(); - - bool generalSwitch = false; - bool anonymizeClientIp = false; - String? retentionTime; - List _ignoredDomainsControllers = []; - final _customTimeController = TextEditingController(); - String? _customTimeError = null; - - List retentionItems = [ - "custom", - "21600000", - "86400000", - "604800000", - "2592000000", - "7776000000" - ]; - - LoadStatus loadStatus = LoadStatus.loading; - - void loadData() async { - final serversProvider = Provider.of(context, listen: false); - - final result = await serversProvider.apiClient2!.getQueryLogInfo(); - - if (!mounted) return; - if (result.successful == true) { - final data = result.content as QueryLogConfig; - setState(() { - generalSwitch = data.enabled ?? false; - anonymizeClientIp = data.anonymizeClientIp ?? false; - retentionTime = retentionItems.contains(data.interval.toString()) ? data.interval.toString() : "custom"; - if (data.interval != null && !retentionItems.contains(data.interval.toString())) { - _customTimeController.text = (data.interval!/3.6e+6).toInt().toString(); - } - if (data.ignored != null) { - _ignoredDomainsControllers = data.ignored!.map((e) => DomainListItemController( - id: uuid.v4(), - controller: TextEditingController(text: e), - error: false - )).toList(); - } - loadStatus = LoadStatus.loaded; - }); - } - else { - setState(() => loadStatus = LoadStatus.error); - } - } - - void validateCustomTime(String value) { - try { - final regex = RegExp(r'^\d+$'); - final parsed = int.parse(value); - if (!regex.hasMatch(value)) { - setState(() => _customTimeError = AppLocalizations.of(context)!.invalidTime); - } - else if (parsed < 1) { - setState(() => _customTimeError = AppLocalizations.of(context)!.notLess1Hour); - } - else { - setState(() => _customTimeError = null); - } - } catch (_) { - setState(() => _customTimeError = AppLocalizations.of(context)!.invalidTime); - } - } - - bool checkValidValues() { - if (_ignoredDomainsControllers.where((d) => d.controller.text == "" || d.error == true).isNotEmpty) { - return false; - } - if (retentionTime == "custom" && (_customTimeError != null || _customTimeController.text == "")) { - return false; - } - return true; - } - - @override - void initState() { - loadData(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - final validValues = checkValidValues(); - - void clearQueries() async { - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = await serversProvider.apiClient2!.clearLogs(); - - processModal.close(); - - if (!context.mounted) return; - - if (result.successful == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsCleared, - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsNotCleared, - color: Colors.red - ); - } - } - - void updateConfig() async { - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = await serversProvider.apiClient2!.updateQueryLogParameters( - data: { - "enabled": generalSwitch, - "interval": retentionTime == "custom" ? int.parse(_customTimeController.text)*3.6e+6 : int.parse(retentionTime!) , - "anonymize_client_ip": anonymizeClientIp, - "ignored": _ignoredDomainsControllers.map((e) => e.controller.text).toList() - } - ); - - processModal.close(); - - if (!context.mounted) return; - - if (result.successful == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigUpdated, - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigNotUpdated, - color: Colors.red - ); - } - } - - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.logsSettings), - actions: [ - if (loadStatus == LoadStatus.loaded) IconButton( - onPressed: validValues ? () => updateConfig() : null, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ), - if (loadStatus == LoadStatus.loaded) PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - onTap: clearQueries, - child: Row( - children: [ - const Icon(Icons.delete_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.clearLogs), - ], - ) - ) - ], - ), - const SizedBox(width: 8) - ], - ), - body: Builder( - builder: (context) { - switch (loadStatus) { - case LoadStatus.loading: - return LoadingData(text: AppLocalizations.of(context)!.loadingLogsSettings); - - case LoadStatus.loaded: - return LogsConfigOptions( - generalSwitch: generalSwitch, - updateGeneralSwitch: (v) => setState(() => generalSwitch = v), - anonymizeClientIp: anonymizeClientIp, - updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), - retentionItems: retentionItems, - retentionTime: retentionTime, - updateRetentionTime: (v) => setState(() => retentionTime = v), - onClear: clearQueries, - onConfirm: updateConfig, - ignoredDomainsControllers: _ignoredDomainsControllers, - updateIgnoredDomainsControllers: (v) => setState(() => _ignoredDomainsControllers = v), - customTimeController: _customTimeController, - customTimeError: _customTimeError, - validateCustomTime: validateCustomTime, - ); - - case LoadStatus.error: - return ErrorLoadData(text: AppLocalizations.of(context)!.logSettingsNotLoaded,); - - default: - return const SizedBox(); - } - }, - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/safe_search_settings.dart b/lib/screens/settings/safe_search_settings.dart index c9d5eef..99f6ad7 100644 --- a/lib/screens/settings/safe_search_settings.dart +++ b/lib/screens/settings/safe_search_settings.dart @@ -14,7 +14,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class SafeSearchSettingsScreen extends StatefulWidget { - const SafeSearchSettingsScreen({super.key}); + const SafeSearchSettingsScreen({Key? key}) : super(key: key); @override State createState() => _SafeSearchSettingsScreenState(); @@ -90,7 +90,7 @@ class _SafeSearchSettingsScreenState extends State { processModal.close(); if (result == true) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.settingsUpdatedSuccessfully, color: Colors.green, @@ -98,7 +98,7 @@ class _SafeSearchSettingsScreenState extends State { ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.settingsNotSaved, color: Colors.red, @@ -123,168 +123,166 @@ class _SafeSearchSettingsScreenState extends State { const SizedBox(width: 8) ], ), - body: SafeArea( - child: Builder( - builder: (context) { - switch (statusProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingSafeSearchSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return RefreshIndicator( - onRefresh: requestSafeSearchSettings, - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 16, - left: 16, - right: 16, - bottom: 8 - ), - child: Material( - color: Colors.transparent, + body: Builder( + builder: (context) { + switch (statusProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingSafeSearchSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return RefreshIndicator( + onRefresh: requestSafeSearchSettings, + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: 8 + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(28), + child: InkWell( borderRadius: BorderRadius.circular(28), - child: InkWell( - borderRadius: BorderRadius.circular(28), - onTap: () => setState(() => generalEnabled = !generalEnabled), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28) - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - AppLocalizations.of(context)!.enableSafeSearch, - style: const TextStyle( - fontSize: 18 - ), + onTap: () => setState(() => generalEnabled = !generalEnabled), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + AppLocalizations.of(context)!.enableSafeSearch, + style: const TextStyle( + fontSize: 18 ), ), - Switch( - value: generalEnabled, - onChanged: (value) => setState(() => generalEnabled = value) - ) - ], - ), + ), + Switch( + value: generalEnabled, + onChanged: (value) => setState(() => generalEnabled = value) + ) + ], ), ), ), ), - CustomCheckboxListTile( - value: bingEnabled, - onChanged: (value) => setState(() => bingEnabled = value), - title: "Bing", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: bingEnabled, + onChanged: (value) => setState(() => bingEnabled = value), + title: "Bing", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 ), - CustomCheckboxListTile( - value: duckduckgoEnabled, - onChanged: (value) => setState(() => duckduckgoEnabled = value), - title: "DuckDuckGo", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: duckduckgoEnabled, + onChanged: (value) => setState(() => duckduckgoEnabled = value), + title: "DuckDuckGo", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 ), - CustomCheckboxListTile( - value: googleEnabled, - onChanged: (value) => setState(() => googleEnabled = value), - title: "Google", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: googleEnabled, + onChanged: (value) => setState(() => googleEnabled = value), + title: "Google", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 ), - CustomCheckboxListTile( - value: pixabayEnabled, - onChanged: (value) => setState(() => pixabayEnabled = value), - title: "Pixabay", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: pixabayEnabled, + onChanged: (value) => setState(() => pixabayEnabled = value), + title: "Pixabay", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 ), - CustomCheckboxListTile( - value: yandexEnabled, - onChanged: (value) => setState(() => yandexEnabled = value), - title: "Yandex", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: yandexEnabled, + onChanged: (value) => setState(() => yandexEnabled = value), + title: "Yandex", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 ), - CustomCheckboxListTile( - value: youtubeEnabled, - onChanged: (value) => setState(() => youtubeEnabled = value), - title: "YouTube", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: youtubeEnabled, + onChanged: (value) => setState(() => youtubeEnabled = value), + title: "YouTube", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 ), - ], - ), - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, + disabled: !generalEnabled, + ), + ], + ), + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - - default: - return const SizedBox(); - } - }, - ), + ) + ], + ), + ); + + + default: + return const SizedBox(); + } + }, ) ); } diff --git a/lib/screens/settings/server_info/dns_addresses_modal.dart b/lib/screens/settings/server_info/dns_addresses_modal.dart index e5fb87a..2a6fa2f 100644 --- a/lib/screens/settings/server_info/dns_addresses_modal.dart +++ b/lib/screens/settings/server_info/dns_addresses_modal.dart @@ -1,71 +1,54 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; -import 'package:adguard_home_manager/widgets/list_bottom_sheet.dart'; - class DnsAddressesModal extends StatelessWidget { - final bool isDialog; final List dnsAddresses; const DnsAddressesModal({ - super.key, - required this.isDialog, + Key? key, required this.dnsAddresses, - }); + }) : super(key: key); @override Widget build(BuildContext context) { - if (isDialog == true) { - return AlertDialog( - title: Column( - children: [ - Icon( - Icons.route_rounded, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.dnsAddresses, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - content: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 500), - child: SingleChildScrollView( - child: Wrap( - children: dnsAddresses.map((address) => ListTile( - title: Text( - address, - style: TextStyle( - fontWeight: FontWeight.normal, - color: Theme.of(context).listTileTheme.textColor - ), - ), - )).toList(), - ), + return AlertDialog( + title: Column( + children: [ + Icon( + Icons.route_rounded, + color: Theme.of(context).listTileTheme.iconColor ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close) + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.dnsAddresses, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), ) ], - ); - } - else { - return ListBottomSheet( - icon: Icons.route_rounded, - title: AppLocalizations.of(context)!.dnsAddresses, - children: dnsAddresses.map((address) => CustomListTile( - title: address, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - )).toList(), - ); - } + ), + content: SizedBox( + height: dnsAddresses.length*56 < 500 + ? dnsAddresses.length*56 : 500, + width: double.minPositive, + child: ListView( + children: dnsAddresses.map((address) => ListTile( + title: Text( + address, + style: TextStyle( + fontWeight: FontWeight.normal, + color: Theme.of(context).listTileTheme.textColor + ), + ), + )).toList(), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close) + ) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/settings/server_info/server_info.dart b/lib/screens/settings/server_info/server_info.dart index 8f5c927..3032dcf 100644 --- a/lib/screens/settings/server_info/server_info.dart +++ b/lib/screens/settings/server_info/server_info.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -50,131 +51,114 @@ class _ServerInformationState extends State { surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), - body: SafeArea( - child: Builder( - builder: (context) { - switch (serverInfo.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.loadingServerInfo, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return ListView( + body: Builder( + builder: (context) { + switch (serverInfo.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - CustomListTile( - title: AppLocalizations.of(context)!.dnsAddresses, - subtitle: AppLocalizations.of(context)!.seeDnsAddresses, - onTap: () { - if (width > 700) { - showDialog( - context: context, - builder: (context) => DnsAddressesModal( - dnsAddresses: serverInfo.data!.dnsAddresses, - isDialog: true, - ) - ); - } - else { - showModalBottomSheet( - context: context, - builder: (context) => DnsAddressesModal( - dnsAddresses: serverInfo.data!.dnsAddresses, - isDialog: false, - ), - isScrollControlled: true, - backgroundColor: Colors.transparent, - useSafeArea: true - ); - } - }, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsPort, - subtitle: serverInfo.data!.dnsPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.httpPort, - subtitle: serverInfo.data!.httpPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.protectionEnabled, - subtitle: serverInfo.data!.protectionEnabled == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dhcpAvailable, - subtitle: serverInfo.data!.dhcpAvailable == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverRunning, - subtitle: serverInfo.data!.running == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverVersion, - subtitle: serverInfo.data!.version, - ), - if (serverInfo.data!.language != "") CustomListTile( - title: AppLocalizations.of(context)!.serverLanguage, - subtitle: serverInfo.data!.language, - ), - ] - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.serverInfoNotLoaded, + const CircularProgressIndicator(), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.loadingServerInfo, textAlign: TextAlign.center, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ) - ], + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return ListView( + children: [ + CustomListTile( + title: AppLocalizations.of(context)!.dnsAddresses, + subtitle: AppLocalizations.of(context)!.seeDnsAddresses, + onTap: () { + showModal( + context: context, + builder: (context) => DnsAddressesModal( + dnsAddresses: serverInfo.data!.dnsAddresses + ) + ); + }, ), - ); - - default: - return const SizedBox(); - } - }, - ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsPort, + subtitle: serverInfo.data!.dnsPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.httpPort, + subtitle: serverInfo.data!.httpPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.protectionEnabled, + subtitle: serverInfo.data!.protectionEnabled == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dhcpAvailable, + subtitle: serverInfo.data!.dhcpAvailable == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverRunning, + subtitle: serverInfo.data!.running == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverVersion, + subtitle: serverInfo.data!.version, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverLanguage, + subtitle: serverInfo.data!.language, + ), + ] + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.serverInfoNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, ) ); } diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 6c388cf..8d5f384 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -1,17 +1,18 @@ +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/settings/server_info/server_info.dart'; import 'package:adguard_home_manager/screens/settings/encryption/encryption.dart'; -import 'package:adguard_home_manager/screens/settings/logs_settings/logs_settings.dart'; import 'package:adguard_home_manager/screens/settings/access_settings/access_settings.dart'; import 'package:adguard_home_manager/screens/settings/customization/customization.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp.dart'; -import 'package:adguard_home_manager/screens/settings/statistics_settings/statistics_settings.dart'; import 'package:adguard_home_manager/screens/settings/safe_search_settings.dart'; -import 'package:adguard_home_manager/screens/settings/update_server/update_screen.dart'; +import 'package:adguard_home_manager/screens/settings/update_server/update.dart'; import 'package:adguard_home_manager/screens/settings/dns/dns.dart'; import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrites.dart'; import 'package:adguard_home_manager/screens/servers/servers.dart'; @@ -23,42 +24,44 @@ import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/constants/strings.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/constants/urls.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -final settingsNavigatorKey = GlobalKey(); - class Settings extends StatelessWidget { - const Settings({super.key}); + const Settings({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth > 900) { - return Row( - children: [ - const Expanded( - flex: 1, - child: _SettingsWidget( - twoColumns: true, - ) - ), - Expanded( - flex: 2, - child: Navigator( - key: settingsNavigatorKey, - onGenerateRoute: (settings) => MaterialPageRoute(builder: (ctx) => const SizedBox()), + return SplitView.material( + hideDivider: true, + flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2), + placeholder: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + AppLocalizations.of(context)!.selectOptionLeftColumn, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), ), - ) - ], + ), + ), + child: const SettingsWidget( + twoColumns: true, + ), ); } else { - return const _SettingsWidget( + return const SettingsWidget( twoColumns: false, ); } @@ -66,20 +69,19 @@ class Settings extends StatelessWidget { ); } } -class _SettingsWidget extends StatefulWidget { +class SettingsWidget extends StatefulWidget { final bool twoColumns; - const _SettingsWidget({ + const SettingsWidget({ + Key? key, required this.twoColumns, - }); + }) : super(key: key); @override - State<_SettingsWidget> createState() => _SettingsWidgetState(); + State createState() => _SettingsWidgetState(); } -class _SettingsWidgetState extends State<_SettingsWidget> { - final _scaffoldMessengerKey = GlobalKey(); - +class _SettingsWidgetState extends State { @override void initState() { Provider.of(context, listen: false).setSelectedSettingsScreen(screen: null); @@ -98,257 +100,223 @@ class _SettingsWidgetState extends State<_SettingsWidget> { appConfigProvider.setSelectedSettingsScreen(screen: null); } - return ScaffoldMessenger( - key: widget.twoColumns ? _scaffoldMessengerKey : null, - child: Scaffold( - body: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - title: Text(AppLocalizations.of(context)!.settings), - ) + Widget settingsTile({ + required String title, + required String subtitle, + required IconData icon, + Widget? trailing, + required Widget screenToNavigate, + required int thisItem + }) { + if (widget.twoColumns) { + return CustomSettingsTile( + title: title, + subtitle: subtitle, + icon: icon, + trailing: trailing, + thisItem: thisItem, + selectedItem: appConfigProvider.selectedSettingsScreen, + onTap: () { + appConfigProvider.setSelectedSettingsScreen(screen: thisItem, notify: true); + SplitView.of(context).setSecondary(screenToNavigate); + }, + ); + } + else { + return CustomListTile( + title: title, + subtitle: subtitle, + icon: icon, + trailing: trailing, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => screenToNavigate) + ); + }, + ); + } + } + + return Scaffold( + body: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar.large( + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + title: Text(AppLocalizations.of(context)!.settings), ) - ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverList.list( - children: [ - if ( - serversProvider.selectedServer != null && - statusProvider.serverStatus != null && - serversProvider.apiClient2 != null - ) ...[ - SectionLabel(label: AppLocalizations.of(context)!.serverSettings), - _SettingsTile( - icon: Icons.search_rounded, - title: AppLocalizations.of(context)!.safeSearch, - subtitle: AppLocalizations.of(context)!.safeSearchSettings, - thisItem: 0, - screenToNavigate: const SafeSearchSettingsScreen(), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.list_alt_rounded, - title: AppLocalizations.of(context)!.logsSettings, - subtitle: AppLocalizations.of(context)!.logsSettingsDescription, - thisItem: 1, - screenToNavigate: const LogsSettings(), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.analytics_rounded, - title: AppLocalizations.of(context)!.statisticsSettings, - subtitle: AppLocalizations.of(context)!.statisticsSettingsDescription, - thisItem: 2, - screenToNavigate: const StatisticsSettings(), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.lock_rounded, - title: AppLocalizations.of(context)!.accessSettings, - subtitle: AppLocalizations.of(context)!.accessSettingsDescription, - thisItem: 3, - screenToNavigate: const AccessSettings(), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.install_desktop_rounded, - title: AppLocalizations.of(context)!.dhcpSettings, - subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription, - thisItem: 4, - screenToNavigate: const DhcpScreen(), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.dns_rounded, - title: AppLocalizations.of(context)!.dnsSettings, - subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, - thisItem: 5, - screenToNavigate: DnsSettings( - splitView: widget.twoColumns, - ), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.security_rounded, - title: AppLocalizations.of(context)!.encryptionSettings, - subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription, - thisItem: 6, - screenToNavigate: const EncryptionSettings(), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.route_rounded, - title: AppLocalizations.of(context)!.dnsRewrites, - subtitle: AppLocalizations.of(context)!.dnsRewritesDescription, - thisItem: 7, - screenToNavigate: const DnsRewritesScreen(), - twoColumns: widget.twoColumns, - ), - if (serversProvider.updateAvailable.data != null) _SettingsTile( - icon: Icons.system_update_rounded, - title: AppLocalizations.of(context)!.updates, - subtitle: AppLocalizations.of(context)!.updatesDescription, - trailing: serversProvider.updateAvailable.data != null && - serversProvider.updateAvailable.data!.canAutoupdate == true - ? Container( - width: 10, - height: 10, - margin: const EdgeInsets.only(right: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.red - ), - ) - : null, - thisItem: 8, - screenToNavigate: const UpdateScreen(), - twoColumns: widget.twoColumns, - ), - _SettingsTile( - icon: Icons.info_rounded, - title: AppLocalizations.of(context)!.serverInformation, - subtitle: AppLocalizations.of(context)!.serverInformationDescription, - thisItem: 9, - screenToNavigate: const ServerInformation(), - twoColumns: widget.twoColumns, - ), - ], - SectionLabel(label: AppLocalizations.of(context)!.appSettings), - _SettingsTile( - icon: Icons.palette_rounded, - title: AppLocalizations.of(context)!.customization, - subtitle: AppLocalizations.of(context)!.customizationDescription, - thisItem: 10, - screenToNavigate: const Customization(), - twoColumns: widget.twoColumns, + ) + ], + body: SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverList.list( + children: [ + if ( + serversProvider.selectedServer != null && + statusProvider.serverStatus != null && + serversProvider.apiClient2 != null + ) ...[ + SectionLabel(label: AppLocalizations.of(context)!.serverSettings), + settingsTile( + icon: Icons.search_rounded, + title: AppLocalizations.of(context)!.safeSearch, + subtitle: AppLocalizations.of(context)!.safeSearchSettings, + thisItem: 0, + screenToNavigate: const SafeSearchSettingsScreen(), ), - _SettingsTile( - icon: Icons.storage_rounded, - title: AppLocalizations.of(context)!.servers, - subtitle: serversProvider.selectedServer != null - ? statusProvider.serverStatus != null - ? "${AppLocalizations.of(context)!.connectedTo} ${serversProvider.selectedServer!.name}" - : "${AppLocalizations.of(context)!.selectedServer} ${serversProvider.selectedServer!.name}" - : AppLocalizations.of(context)!.noServerSelected, - thisItem: 11, - screenToNavigate: const Servers(), - twoColumns: widget.twoColumns, + settingsTile( + icon: Icons.lock_rounded, + title: AppLocalizations.of(context)!.accessSettings, + subtitle: AppLocalizations.of(context)!.accessSettingsDescription, + thisItem: 1, + screenToNavigate: const AccessSettings(), ), - _SettingsTile( - icon: Icons.settings, - title: AppLocalizations.of(context)!.generalSettings, - subtitle: AppLocalizations.of(context)!.generalSettingsDescription, - thisItem: 12, - screenToNavigate: GeneralSettings(splitView: widget.twoColumns), - twoColumns: widget.twoColumns, + settingsTile( + icon: Icons.install_desktop_rounded, + title: AppLocalizations.of(context)!.dhcpSettings, + subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription, + thisItem: 2, + screenToNavigate: const DhcpScreen(), ), - _SettingsTile( - icon: Icons.build_outlined, - title: AppLocalizations.of(context)!.advancedSettings, - subtitle: AppLocalizations.of(context)!.advancedSetupDescription, - thisItem: 13, - screenToNavigate: const AdvancedSettings(), - twoColumns: widget.twoColumns, + settingsTile( + icon: Icons.dns_rounded, + title: AppLocalizations.of(context)!.dnsSettings, + subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, + thisItem: 3, + screenToNavigate: DnsSettings( + splitView: widget.twoColumns, + ), ), - SectionLabel(label: AppLocalizations.of(context)!.aboutApp), - CustomListTile( - title: AppLocalizations.of(context)!.appVersion, - subtitle: appConfigProvider.getAppInfo!.version, + settingsTile( + icon: Icons.security_rounded, + title: AppLocalizations.of(context)!.encryptionSettings, + subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription, + thisItem: 4, + screenToNavigate: const EncryptionSettings(), ), - CustomListTile( - title: AppLocalizations.of(context)!.applicationDetails, - subtitle: AppLocalizations.of(context)!.applicationDetailsDescription, - trailing: Icon(Icons.open_in_new_rounded), - onTap: () => openUrl(Urls.appDetailsWebpage), + settingsTile( + icon: Icons.route_rounded, + title: AppLocalizations.of(context)!.dnsRewrites, + subtitle: AppLocalizations.of(context)!.dnsRewritesDescription, + thisItem: 5, + screenToNavigate: const DnsRewritesScreen(), ), - CustomListTile( - title: AppLocalizations.of(context)!.myOtherApps, - subtitle: AppLocalizations.of(context)!.myOtherAppsDescription, - trailing: Icon(Icons.open_in_new_rounded), - onTap: () => openUrl(Urls.jgeek00AppsWebpage), + if (serversProvider.updateAvailable.data != null) settingsTile( + icon: Icons.system_update_rounded, + title: AppLocalizations.of(context)!.updates, + subtitle: AppLocalizations.of(context)!.updatesDescription, + trailing: serversProvider.updateAvailable.data != null && + serversProvider.updateAvailable.data!.canAutoupdate == true + ? Container( + width: 10, + height: 10, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.red + ), + ) + : null, + thisItem: 6, + screenToNavigate: const UpdateScreen(), + ), + settingsTile( + icon: Icons.info_rounded, + title: AppLocalizations.of(context)!.serverInformation, + subtitle: AppLocalizations.of(context)!.serverInformationDescription, + thisItem: 7, + screenToNavigate: const ServerInformation(), ), - SizedBox(height: 16) ], - ) - ], - ) - ), + SectionLabel(label: AppLocalizations.of(context)!.appSettings), + settingsTile( + icon: Icons.palette_rounded, + title: AppLocalizations.of(context)!.customization, + subtitle: AppLocalizations.of(context)!.customizationDescription, + thisItem: 8, + screenToNavigate: const Customization(), + ), + settingsTile( + icon: Icons.storage_rounded, + title: AppLocalizations.of(context)!.servers, + subtitle: serversProvider.selectedServer != null + ? statusProvider.serverStatus != null + ? "${AppLocalizations.of(context)!.connectedTo} ${serversProvider.selectedServer!.name}" + : "${AppLocalizations.of(context)!.selectedServer} ${serversProvider.selectedServer!.name}" + : AppLocalizations.of(context)!.noServerSelected, + thisItem: 9, + screenToNavigate: const Servers(), + ), + settingsTile( + icon: Icons.settings, + title: AppLocalizations.of(context)!.generalSettings, + subtitle: AppLocalizations.of(context)!.generalSettingsDescription, + thisItem: 10, + screenToNavigate: const GeneralSettings(), + ), + settingsTile( + icon: Icons.build_outlined, + title: AppLocalizations.of(context)!.advancedSettings, + subtitle: AppLocalizations.of(context)!.advancedSetupDescription, + thisItem: 11, + screenToNavigate: const AdvancedSettings(), + ), + SectionLabel(label: AppLocalizations.of(context)!.aboutApp), + CustomListTile( + title: AppLocalizations.of(context)!.appVersion, + subtitle: appConfigProvider.getAppInfo!.version, + ), + CustomListTile( + title: AppLocalizations.of(context)!.createdBy, + subtitle: Strings.createdBy, + ), + Padding( + padding: const EdgeInsets.all(15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (Platform.isAndroid) IconButton( + onPressed: () => openUrl(Urls.playStore), + icon: SvgPicture.asset( + 'assets/resources/google-play.svg', + color: Theme.of(context).colorScheme.onSurfaceVariant, + width: 30, + height: 30, + ), + tooltip: AppLocalizations.of(context)!.visitGooglePlay, + ), + IconButton( + onPressed: () => openUrl(Urls.gitHub), + icon: SvgPicture.asset( + 'assets/resources/github.svg', + color: Theme.of(context).colorScheme.onSurfaceVariant, + width: 30, + height: 30, + ), + tooltip: AppLocalizations.of(context)!.gitHub, + ), + ], + ), + ) + ], + ) + ], + ) ), - ) - ), + ), + ) ); } -} - -class _SettingsTile extends StatelessWidget { - final String title; - final String subtitle; - final IconData icon; - final Widget? trailing; - final Widget screenToNavigate; - final int thisItem; - final bool twoColumns; - - const _SettingsTile({ - required this.title, - required this.subtitle, - required this.icon, - this.trailing, - required this.screenToNavigate, - required this.thisItem, - required this.twoColumns - }); - - @override - Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); - - if (twoColumns) { - return CustomSettingsTile( - title: title, - subtitle: subtitle, - icon: icon, - trailing: trailing, - thisItem: thisItem, - selectedItem: appConfigProvider.selectedSettingsScreen, - onTap: () { - appConfigProvider.setSelectedSettingsScreen(screen: thisItem, notify: true); - Navigator.of(settingsNavigatorKey.currentContext!).pushReplacement( - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => screenToNavigate, - transitionDuration: Duration.zero, - reverseTransitionDuration: Duration.zero, - ), - ); - }, - ); - } - else { - return CustomListTile( - title: title, - subtitle: subtitle, - icon: icon, - trailing: trailing, - onTap: () { - Navigator.of(context).push( - MaterialPageRoute(builder: (context) => screenToNavigate) - ); - }, - ); - } - } } \ No newline at end of file diff --git a/lib/screens/settings/statistics_settings/statistics_settings.dart b/lib/screens/settings/statistics_settings/statistics_settings.dart deleted file mode 100644 index 84439b1..0000000 --- a/lib/screens/settings/statistics_settings/statistics_settings.dart +++ /dev/null @@ -1,338 +0,0 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; -import 'package:flutter/material.dart'; -import 'package:uuid/uuid.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/settings/logs_settings/logs_settings.dart'; -import 'package:adguard_home_manager/widgets/load_status_widgets.dart'; -import 'package:adguard_home_manager/widgets/section_label.dart'; -import 'package:adguard_home_manager/widgets/master_switch.dart'; - -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; -import 'package:adguard_home_manager/models/statistics_config.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; - -class StatisticsSettings extends StatefulWidget { - const StatisticsSettings({super.key}); - - @override - State createState() => _StatisticsSettingsState(); -} - -class _StatisticsSettingsState extends State { - final Uuid uuid = const Uuid(); - LoadStatus _loadStatus = LoadStatus.loading; - bool _generalSwitch = false; - final List _retentionItems = [ - "custom", - "86400000", - "604800000", - "2592000000", - "7776000000" - ]; - final _customTimeController = TextEditingController(); - String? _customTimeError; - String? _retentionTime; - List _ignoredDomainsControllers = []; - - void loadData() async { - final serversProvider = Provider.of(context, listen: false); - - final result = await serversProvider.apiClient2!.getStatisticsConfig(); - - if (!mounted) return; - if (result.successful == true) { - final data = result.content as StatisticsConfig; - setState(() { - _generalSwitch = data.enabled ?? false; - if (_retentionItems.contains(data.interval.toString())) { - _retentionTime = data.interval.toString(); - } - else if (data.interval != null) { - _retentionTime = "custom"; - _customTimeController.text = Duration(milliseconds: data.interval!).inHours.toString(); - } - if (data.ignored != null) { - _ignoredDomainsControllers = data.ignored!.map((e) => DomainListItemController( - id: uuid.v4(), - controller: TextEditingController(text: e), - error: false - )).toList(); - } - _loadStatus = LoadStatus.loaded; - }); - } - else { - setState(() => _loadStatus = LoadStatus.error); - } - } - - @override - void initState() { - loadData(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - final List dropdownItemTranslation = [ - AppLocalizations.of(context)!.custom, - AppLocalizations.of(context)!.hours24, - AppLocalizations.of(context)!.days7, - AppLocalizations.of(context)!.days30, - AppLocalizations.of(context)!.days90, - ]; - - - void validateDomain(String value, String id) { - bool error = false; - if (Regexps.domain.hasMatch(value)) { - error = false; - } - else { - error = true; - } - setState(() { - _ignoredDomainsControllers = _ignoredDomainsControllers.map((entry) { - if (entry.id != id) return entry; - return DomainListItemController( - id: id, - controller: entry.controller, - error: error - ); - }).toList(); - }); - } - - void validateCustomTime(String v) { - try { - final regex = RegExp(r'^\d+$'); - final parsed = int.parse(v); - if (!regex.hasMatch(v)) { - setState(() => _customTimeError = AppLocalizations.of(context)!.invalidTime); - } - else if (parsed < 1) { - setState(() => _customTimeError = AppLocalizations.of(context)!.notLess1Hour); - } - else { - setState(() => _customTimeError = null); - } - } catch (_) { - setState(() => _customTimeError = AppLocalizations.of(context)!.invalidTime); - } - } - - void updateConfig() async { - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = await serversProvider.apiClient2!.updateStatisticsSettings( - body: { - "enabled": _generalSwitch, - "interval": _retentionTime == "custom" - ? Duration(hours: int.parse(_customTimeController.text)).inMilliseconds - : int.parse(_retentionTime ?? _retentionItems[0]), - "ignored": _ignoredDomainsControllers.map((e) => e.controller.text).toList() - } - ); - - processModal.close(); - - if (!context.mounted) return; - - if (result.successful == true) { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.statisticsConfigUpdated, - color: Colors.green - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.statisticsConfigNotUpdated, - color: Colors.red - ); - } - } - - final validValues = _ignoredDomainsControllers.where( - (d) => d.controller.text == "" || d.error == true - ).isEmpty && - (_retentionTime != "custom" || - (_retentionTime == "custom" && _customTimeController.text != "" && _customTimeError == null)); - - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.statisticsSettings), - actions: [ - IconButton( - onPressed: validValues ? () => updateConfig() : null, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 8) - ], - ), - body: Builder( - builder: (context) { - switch (_loadStatus) { - case LoadStatus.loading: - return LoadingData(text: AppLocalizations.of(context)!.loadingStatisticsSettings); - - case LoadStatus.loaded: - return ListView( - children: [ - const SizedBox(height: 8), - MasterSwitch( - label: AppLocalizations.of(context)!.enableLog, - value: _generalSwitch, - onChange: (v) => setState(() => _generalSwitch = v) - ), - const SizedBox(height: 24), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: DropdownButtonFormField( - items: _retentionItems.asMap().entries.map((item) => DropdownMenuItem( - value: item.value, - child: Text(dropdownItemTranslation[item.key]), - )).toList(), - value: _retentionTime, - onChanged: (value) => setState(() { - if (value != null && value != "custom") { - _customTimeError = null; - _customTimeController.text = ""; - } - _retentionTime = value; - }), - decoration: InputDecoration( - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - label: Text(AppLocalizations.of(context)!.retentionTime) - ), - borderRadius: BorderRadius.circular(20), - ), - ), - if (_retentionTime == "custom") Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - top: 24 - ), - child: TextFormField( - controller: _customTimeController, - onChanged: validateCustomTime, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.schedule_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.customTimeInHours, - errorText: _customTimeError - ), - keyboardType: TextInputType.number, - ), - ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SectionLabel( - label: AppLocalizations.of(context)!.ignoredDomains, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0), - ), - Padding( - padding: const EdgeInsets.only(right: 6), - child: IconButton( - onPressed: () => setState(() => _ignoredDomainsControllers = [ - ..._ignoredDomainsControllers, - DomainListItemController( - id: uuid.v4(), - controller: TextEditingController(), - error: false - ), - ]), - icon: const Icon(Icons.add), - tooltip: AppLocalizations.of(context)!.addDomain, - ), - ) - ], - ), - ), - if (_ignoredDomainsControllers.isNotEmpty) ..._ignoredDomainsControllers.map((controller) => Padding( - padding: const EdgeInsets.only( - top: 12, bottom: 12, left: 16, right: 6 - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextFormField( - controller: controller.controller, - onChanged: (v) => validateDomain(v, controller.id), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.domain, - errorText: controller.error - ? AppLocalizations.of(context)!.invalidDomain - : null - ), - ), - ), - const SizedBox(width: 12), - Padding( - padding: controller.error - ? const EdgeInsets.only(bottom: 24) - : const EdgeInsets.all(0), - child: IconButton( - onPressed: () => setState(() => _ignoredDomainsControllers = _ignoredDomainsControllers.where((e) => e.id != controller.id).toList()), - icon: const Icon(Icons.remove_circle_outline_outlined), - tooltip: AppLocalizations.of(context)!.removeDomain, - ), - ) - ], - ), - )), - if (_ignoredDomainsControllers.isEmpty) Container( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text( - AppLocalizations.of(context)!.noIgnoredDomainsAdded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ], - ); - - case LoadStatus.error: - return ErrorLoadData(text: AppLocalizations.of(context)!.statisticsSettingsLoadError); - - default: - return const SizedBox(); - } - }, - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/theme_modal.dart b/lib/screens/settings/theme_modal.dart index fac867e..4d8f2fe 100644 --- a/lib/screens/settings/theme_modal.dart +++ b/lib/screens/settings/theme_modal.dart @@ -14,10 +14,10 @@ class ThemeModal extends StatefulWidget { final int selectedTheme; const ThemeModal({ - super.key, + Key? key, required this.statusBarHeight, required this.selectedTheme, - }); + }) : super(key: key); @override State createState() => _ThemeModalState(); diff --git a/lib/screens/settings/update_server/autoupdate_unavailable.dart b/lib/screens/settings/update_server/autoupdate_unavailable.dart index 1ba4ae7..9846448 100644 --- a/lib/screens/settings/update_server/autoupdate_unavailable.dart +++ b/lib/screens/settings/update_server/autoupdate_unavailable.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class AutoUpdateUnavailableModal extends StatelessWidget { - const AutoUpdateUnavailableModal({super.key}); + const AutoUpdateUnavailableModal({Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/update_server/update.dart b/lib/screens/settings/update_server/update.dart index de951b7..dd91f6e 100644 --- a/lib/screens/settings/update_server/update.dart +++ b/lib/screens/settings/update_server/update.dart @@ -17,7 +17,7 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class UpdateScreen extends StatelessWidget { - const UpdateScreen({super.key}); + const UpdateScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -41,7 +41,7 @@ class UpdateScreen extends StatelessWidget { if (result.successful == true) { serversProvider.recheckPeriodServerUpdated(); - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.requestStartUpdateSuccessful, color: Colors.green, @@ -49,7 +49,7 @@ class UpdateScreen extends StatelessWidget { ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.requestStartUpdateFailed, color: Colors.red, @@ -166,44 +166,39 @@ class UpdateScreen extends StatelessWidget { ); } - final SafeArea? changelog; - if (serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null) { - changelog = SafeArea( - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - "Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true - ? serversProvider.updateAvailable.data!.newVersion - : serversProvider.updateAvailable.data!.currentVersion}", - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), + final changelog = serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null + ? ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + "Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true + ? serversProvider.updateAvailable.data!.newVersion + : serversProvider.updateAvailable.data!.currentVersion}", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurfaceVariant ), ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Html( - data: html.parse(md.markdownToHtml(serversProvider.updateAvailable.data!.changelog!)).outerHtml, - onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null, - ) + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Html( + data: html.parse(md.markdownToHtml(serversProvider.updateAvailable.data!.changelog!)).outerHtml, + onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null, ) - ], - ), - ); - } else { - changelog = null; - } + ) + ], + ) + : null; return Scaffold( body: Column( children: [ Container( - color: Theme.of(context).colorScheme.surfaceContainerHighest, + color: Theme.of(context).colorScheme.surfaceVariant, child: SafeArea( child: headerPortrait() ) diff --git a/lib/screens/settings/update_server/update_screen.dart b/lib/screens/settings/update_server/update_screen.dart deleted file mode 100644 index ae894f8..0000000 --- a/lib/screens/settings/update_server/update_screen.dart +++ /dev/null @@ -1,337 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_html/flutter_html.dart'; -import 'package:html/parser.dart' as html; -import 'package:markdown/markdown.dart' as md; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/functions/open_url.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; - -const _minExent = 64.0; -const _maxExent = 240.0; - -const _iconMaxBottomPositionExent = 130.0; -const _iconMinBottomPositionExent = 34.0; - -const _textMaxBottomPositionExent = 70.0; -const _textMinBottomPositionExent = 16.0; - -const _versionTextMaxBottomPositionExent = 30.0; -const _versionTextMinBottomPositionExent = 0.0; - -const _textMaxFontSize = 24.0; -const _textMinFontSize = 22.0; - -const _iconSafetyMargin = 15.0; - -const _iconSize = 45.0; - -class UpdateScreen extends StatefulWidget { - const UpdateScreen({super.key}); - - @override - State createState() => _UpdateScreenState(); -} - -class _UpdateScreenState extends State { - final _scrollController = ScrollController(); - bool _isScrolled = false; - - String? _htmlChangelog; - - void processChangelog() async { - final serversProvider = Provider.of(context, listen: false); - if (serversProvider.updateAvailable.data?.changelog == null) return; - final markdownResult = await compute(md.markdownToHtml, serversProvider.updateAvailable.data!.changelog!); - final htmlParsedResult = await compute(html.parse, markdownResult); - setState(() => _htmlChangelog = htmlParsedResult.outerHtml); - } - - @override - void initState() { - final serversProvider = Provider.of(context, listen: false); - _scrollController.addListener(() { - final newValue = _scrollController.offset > 20; - if (!( - serversProvider.updatingServer == false && - serversProvider.updateAvailable.data!.canAutoupdate != null && - serversProvider.updateAvailable.data!.canAutoupdate == true - )) return; - if (_isScrolled == newValue) return; - setState(() => _isScrolled = newValue); - }); - - processChangelog(); - - super.initState(); - } - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - void update() async { - ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.requestingUpdate); - - final result = await serversProvider.apiClient2!.requestUpdateServer(); - - processModal.close(); - - if (!context.mounted) return; - if (result.successful == true) { - serversProvider.recheckPeriodServerUpdated(); - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.requestStartUpdateSuccessful, - color: Colors.green, - labelColor: Colors.white, - ); - } - else { - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.requestStartUpdateFailed, - color: Colors.red, - labelColor: Colors.white, - ); - } - } - - return Scaffold( - body: SafeArea( - top: false, - child: Stack( - children: [ - CustomScrollView( - controller: _scrollController, - slivers: [ - SliverPersistentHeader( - pinned: true, - delegate: _Header( - onRefresh: () => serversProvider.checkServerUpdatesAvailable( - server: serversProvider.selectedServer!, - ), - viewPaddingTop: MediaQuery.of(context).viewPadding.top - ) - ), - SliverList.list( - children: [ - const SizedBox(height: 16), - if (_htmlChangelog != null) Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - "Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true - ? serversProvider.updateAvailable.data!.newVersion - : serversProvider.updateAvailable.data!.currentVersion}", - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ), - if (_htmlChangelog != null) Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Html( - data: _htmlChangelog, - onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null, - ) - ), - if (_htmlChangelog == null) Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 32), - child: Column( - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingChangelog, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ) - ], - ) - ), - ] - ) - ], - ), - if ( - serversProvider.updatingServer == false && - serversProvider.updateAvailable.data!.canAutoupdate != null && - serversProvider.updateAvailable.data!.canAutoupdate == true - ) AnimatedPositioned( - right: 20, - bottom: _isScrolled ? -70 : 20, - duration: const Duration(milliseconds: 200), - curve: Curves.ease, - child: FloatingActionButton( - onPressed: () => update(), - tooltip: AppLocalizations.of(context)!.updateNow, - child: const Icon(Icons.download_rounded), - ), - ) - ], - ), - ), - ); - } -} - -class _Header extends SliverPersistentHeaderDelegate { - final void Function() onRefresh; - final double viewPaddingTop; - - const _Header({ - required this.onRefresh, - required this.viewPaddingTop, - }); - - @override - Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { - final serversProvider = Provider.of(context); - - final iconMaxBottomPositionExent = _iconMaxBottomPositionExent + viewPaddingTop; - final iconMinBottomPositionExent = _iconMinBottomPositionExent + viewPaddingTop; - final textMaxBottomPositionExent = _textMaxBottomPositionExent + viewPaddingTop; - final textMinBottomPositionExent = _textMinBottomPositionExent + viewPaddingTop; - final versionTextMaxBottomPositionExent = _versionTextMaxBottomPositionExent + viewPaddingTop; - final versionTextMinBottomPositionExent = _versionTextMinBottomPositionExent + viewPaddingTop; - - final iconPercentage = shrinkOffset.clamp(0, _maxExent-_minExent-_iconSafetyMargin)/(_maxExent-_minExent-_iconSafetyMargin); - final textPercentage = shrinkOffset.clamp(0, _maxExent-_minExent)/(_maxExent-_minExent); - - final textFontSize = _textMinFontSize + (_textMaxFontSize-_textMinFontSize)*(1-textPercentage); - final mainText = _textMinBottomPositionExent + (textMaxBottomPositionExent-textMinBottomPositionExent)*(1-textPercentage); - final versionText = _versionTextMinBottomPositionExent + (versionTextMaxBottomPositionExent-versionTextMinBottomPositionExent)*(1-textPercentage); - - final iconBottom = _iconMinBottomPositionExent + (iconMaxBottomPositionExent-iconMinBottomPositionExent)*(1-iconPercentage); - - return LayoutBuilder( - builder: (context, constraints) => Stack( - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - ), - ), - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceTint.withOpacity(0.075), - ), - child: Align( - alignment: Alignment.topLeft, - child: SafeArea( - bottom: false, - child: Stack( - fit: StackFit.expand, - alignment: Alignment.center, - children: [ - if (Navigator.of(context).canPop()) Positioned( - top: 8, - left: 0, - child: BackButton( - onPressed: () => Navigator.pop(context), - ), - ), - Positioned( - top: 8, - right: 0, - child: IconButton( - onPressed: onRefresh, - icon: const Icon(Icons.refresh_rounded), - tooltip: AppLocalizations.of(context)!.refresh, - ) - ), - Positioned( - bottom: iconBottom, - left: (constraints.maxWidth/2)-(_iconSize/2), - child: Opacity( - opacity: 1-iconPercentage, - child: serversProvider.updateAvailable.loadStatus == LoadStatus.loading - ? const Column( - children: [ - CircularProgressIndicator(), - SizedBox(height: 4) - ], - ) - : Icon( - serversProvider.updateAvailable.data!.canAutoupdate == true - ? Icons.system_update_rounded - : Icons.system_security_update_good_rounded, - size: _iconSize, - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - Positioned( - bottom: mainText, - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth-100 - ), - child: Text( - serversProvider.updateAvailable.loadStatus == LoadStatus.loading - ? AppLocalizations.of(context)!.checkingUpdates - : serversProvider.updateAvailable.data!.canAutoupdate == true - ? AppLocalizations.of(context)!.updateAvailable - : AppLocalizations.of(context)!.serverUpdated, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: textFontSize, - fontWeight: FontWeight.w400 - ), - ), - ) - ), - Positioned( - bottom: versionText, - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth-100 - ), - child: Opacity( - opacity: 1-iconPercentage, - child: Text( - serversProvider.updateAvailable.data!.canAutoupdate == true - ? "${AppLocalizations.of(context)!.newVersion}: ${serversProvider.updateAvailable.data!.newVersion ?? 'N/A'}" - : "${AppLocalizations.of(context)!.installedVersion}: ${serversProvider.updateAvailable.data!.currentVersion}", - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ), - ) - ) - ], - ), - ), - ), - ), - ], - ), - ); - } - - @override - double get maxExtent => _maxExent + viewPaddingTop; - - @override - double get minExtent => _minExent + viewPaddingTop; - - @override - bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => false; -} \ No newline at end of file diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index ce3e033..636cc34 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -3,8 +3,6 @@ import 'dart:convert'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:adguard_home_manager/models/blocked_services.dart'; -import 'package:adguard_home_manager/models/querylog_config.dart'; -import 'package:adguard_home_manager/models/statistics_config.dart'; import 'package:adguard_home_manager/models/dns_info.dart'; import 'package:adguard_home_manager/models/encryption.dart'; import 'package:adguard_home_manager/models/dhcp.dart'; @@ -41,17 +39,10 @@ class ApiClientV2 { Future getServerVersion() async { final result = await HttpRequestClient.get(urlPath: '/status', server: server); if (result.successful == true) { - try { - return ApiResponse( - successful: true, - content: jsonDecode(result.body!)['version'] - ); - } on FormatException { - return const ApiResponse(successful: false); - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); - return const ApiResponse(successful: false); - } + return ApiResponse( + successful: true, + content: jsonDecode(result.body!)['version'] + ); } else { return const ApiResponse(successful: false); @@ -206,14 +197,14 @@ class ApiClientV2 { } Future getLogs({ - int? count, + required int count, int? offset, DateTime? olderThan, String? responseStatus, String? search }) async { final result = await HttpRequestClient.get( - urlPath: '/querylog?${count != null ? 'limit=$count' : ''}${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}${responseStatus != null ? '&response_status=$responseStatus' : ''}${search != null ? '&search=$search' : ''}', + urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}${responseStatus != null ? '&response_status=$responseStatus' : ''}${search != null ? '&search=$search' : ''}', server: server ); if (result.successful == true) { @@ -492,9 +483,7 @@ class ApiClientV2 { successful: true, content: DhcpModel( networkInterfaces: interfaces, - dhcpStatus: jsonDecode(results[1].body!)['message'] != null - ? null - : DhcpStatus.fromJson(jsonDecode(results[1].body!)) + dhcpStatus: DhcpStatus.fromJson(jsonDecode(results[1].body!)) ) ); } catch (e, stackTrace) { @@ -626,19 +615,10 @@ class ApiClientV2 { Future getQueryLogInfo() async { final result = await HttpRequestClient.get(urlPath: '/querylog/config', server: server); if (result.successful) { - try { - return ApiResponse( - successful: true, - content: QueryLogConfig.fromJson(jsonDecode(result.body!)) - ); - } catch (e, stackTrace) { - Sentry.captureException( - e, - stackTrace: stackTrace, - hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) - ); - return const ApiResponse(successful: false); - } + return ApiResponse( + successful: true, + content: jsonDecode(result.body!) + ); } else { return const ApiResponse(successful: false); @@ -657,7 +637,7 @@ class ApiClientV2 { } Future clearLogs() async { - final result = await HttpRequestClient.post( + final result = await HttpRequestClient.put( urlPath: '/querylog_clear', server: server, body: {}, @@ -868,51 +848,4 @@ class ApiClientV2 { ); return ApiResponse(successful: result.successful); } - - Future testUpstreamDns({ - required Map body - }) async { - final result = await HttpRequestClient.post( - urlPath: '/test_upstream_dns', - server: server, - body: body - ); - return ApiResponse( - successful: result.successful, - content: result.body != null ? jsonDecode(result.body!) : null - ); - } - - Future getStatisticsConfig() async { - final result = await HttpRequestClient.get(urlPath: '/stats/config', server: server); - if (result.successful) { - try { - return ApiResponse( - successful: true, - content: StatisticsConfig.fromJson(jsonDecode(result.body!)) - ); - } catch (e, stackTrace) { - Sentry.captureException( - e, - stackTrace: stackTrace, - hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) - ); - return const ApiResponse(successful: false); - } - } - else { - return const ApiResponse(successful: false); - } - } - - Future updateStatisticsSettings({ - required Map body - }) async { - final result = await HttpRequestClient.put( - urlPath: '/stats/config/update', - server: server, - body: body - ); - return ApiResponse(successful: result.successful); - } } \ No newline at end of file diff --git a/lib/services/auth.dart b/lib/services/auth.dart index 68e2717..a450348 100644 --- a/lib/services/auth.dart +++ b/lib/services/auth.dart @@ -2,8 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:sentry_flutter/sentry_flutter.dart'; - import 'package:adguard_home_manager/classes/http_client.dart'; import 'package:adguard_home_manager/models/server.dart'; @@ -54,8 +52,7 @@ class ServerAuth { return AuthStatus.timeoutException; } on HandshakeException { return AuthStatus.handshakeException; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } catch (e) { return AuthStatus.unknown; } } @@ -78,8 +75,7 @@ class ServerAuth { return AuthStatus.timeoutException; } on HandshakeException { return AuthStatus.handshakeException; - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } catch (e) { return AuthStatus.unknown; } } diff --git a/lib/services/db/database.dart b/lib/services/db/database.dart index fe09d16..7d8cf13 100644 --- a/lib/services/db/database.dart +++ b/lib/services/db/database.dart @@ -1,11 +1,135 @@ import 'package:sqflite/sqflite.dart'; -Future> loadDb() async { +import 'package:adguard_home_manager/config/home_top_items_default_order.dart'; + +Future> loadDb(bool acceptsDynamicTheme) async { List>? servers; + List>? appConfig; + + Future rebuildAppConfig(Database db) async { + await db.execute("DROP TABLE appConfig"); + await db.execute("CREATE TABLE appConfig (theme NUMERIC, overrideSslCheck NUMERIC, hideZeroValues NUMERIC, useDynamicColor NUMERIC, staticColor NUMERIC, useThemeColorForStatus NUMERIC, showTimeLogs NUMERIC, showIpLogs NUMERIC, combinedChart NUMERIC, doNotRememberVersion TEXT)"); + await db.execute("INSERT INTO appConfig (theme, overrideSslCheck, hideZeroValues, useDynamicColor, staticColor, useThemeColorForStatus, showTimeLogs, showIpLogs, combinedChart) VALUES (0, 0, 0, ${acceptsDynamicTheme == true ? 1 : 0}, 0, 0, 0, 0, 0)"); + } + + Future upgradeDbToV2(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN overrideSslCheck NUMERIC"); + await db.execute("UPDATE appConfig SET overrideSslCheck = 0"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } + + Future upgradeDbToV3(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN hideZeroValues NUMERIC"); + await db.execute("UPDATE appConfig SET hideZeroValues = 0"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } + + Future upgradeDbToV4(Database db) async { + await db.execute("ALTER TABLE servers ADD COLUMN runningOnHa INTEGER"); + await db.execute("UPDATE servers SET runningOnHa = 0"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM servers', + ); + }); + } + + Future upgradeDbToV5(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN useDynamicColor NUMERIC"); + await db.execute("ALTER TABLE appConfig ADD COLUMN staticColor NUMERIC"); + await db.execute("ALTER TABLE appConfig ADD COLUMN useThemeColorForStatus NUMERIC"); + await db.execute("UPDATE appConfig SET useDynamicColor = ${acceptsDynamicTheme == true ? 1 : 0}, staticColor = 0, useThemeColorForStatus = 0"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } + + Future upgradeDbToV6(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN showNameTimeLogs NUMERIC"); + await db.execute("UPDATE appConfig SET showNameTimeLogs = 0"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } + + Future upgradeDbToV7(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN doNotRememberVersion TEXT"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } + + Future upgradeDbToV8(Database db) async { + try { + final data = await db.rawQuery( + 'SELECT * FROM appConfig', + ); + await rebuildAppConfig(db); + await db.update( + 'appConfig', + { + 'theme': data[0]['theme'], + 'overrideSslCheck': data[0]['overrideSslCheck'], + 'hideZeroValues': data[0]['hideZeroValues'], + 'useDynamicColor': data[0]['useDynamicColor'], + 'staticColor': data[0]['staticColor'], + 'useThemeColorForStatus': data[0]['useThemeColorForStatus'], + 'showTimeLogs': data[0]['showNameTimeLogs'], + 'showIpLogs': data[0]['showIpLogs'], + 'combinedChart': data[0]['combinedChart'], + } + ); + } catch (e) { + await rebuildAppConfig(db); + } + } + + Future upgradeDbToV9(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN hideServerAddress NUMERIC"); + await db.execute("ALTER TABLE appConfig ADD COLUMN homeTopItemsOrder TEXT"); + await db.execute("UPDATE appConfig SET hideServerAddress = 0, homeTopItemsOrder = '$homeTopItemsDefaultOrderString'"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } + + Future upgradeDbToV10(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN showTopItemsChart NUMERIC"); + await db.execute("UPDATE appConfig SET showTopItemsChart = 1"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } Database db = await openDatabase( 'adguard_home_manager.db', - version: 11, + version: 10, onCreate: (Database db, int version) async { await db.execute( """ @@ -25,6 +149,126 @@ Future> loadDb() async { ) """ ); + + await db.execute( + """ + CREATE TABLE + appConfig ( + theme NUMERIC, + overrideSslCheck NUMERIC, + hideZeroValues NUMERIC, + useDynamicColor NUMERIC, + staticColor NUMERIC, + useThemeColorForStatus NUMERIC, + showTimeLogs NUMERIC, + showIpLogs NUMERIC, + combinedChart NUMERIC, + doNotRememberVersion TEXT, + hideServerAddress NUMERIC, + homeTopItemsOrder TEXT, + showTopItemsChart NUMERIC + ) + """ + ); + + await db.execute( + """ + INSERT INTO + appConfig ( + theme, + overrideSslCheck, + hideZeroValues, + useDynamicColor, + staticColor, + useThemeColorForStatus, + showTimeLogs, + showIpLogs, + combinedChart, + hideServerAddress, + homeTopItemsOrder, + showTopItemsChart + ) + VALUES ( + 0, + 0, + 0, + ${acceptsDynamicTheme == true ? 1 : 0}, + 0, + 0, + 0, + 0, + 0, + 0, + '$homeTopItemsDefaultOrderString', + 1 + ) + """ + ); + }, + onUpgrade: (Database db, int oldVersion, int newVersion) async { + if (oldVersion == 1) { + await upgradeDbToV2(db); + await upgradeDbToV3(db); + await upgradeDbToV4(db); + await upgradeDbToV5(db); + await upgradeDbToV6(db); + await upgradeDbToV7(db); + await upgradeDbToV8(db); + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 2) { + await upgradeDbToV3(db); + await upgradeDbToV4(db); + await upgradeDbToV5(db); + await upgradeDbToV6(db); + await upgradeDbToV7(db); + await upgradeDbToV8(db); + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 3) { + await upgradeDbToV4(db); + await upgradeDbToV5(db); + await upgradeDbToV6(db); + await upgradeDbToV7(db); + await upgradeDbToV8(db); + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 4) { + await upgradeDbToV5(db); + await upgradeDbToV6(db); + await upgradeDbToV7(db); + await upgradeDbToV8(db); + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 5) { + await upgradeDbToV6(db); + await upgradeDbToV7(db); + await upgradeDbToV8(db); + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 6) { + await upgradeDbToV7(db); + await upgradeDbToV8(db); + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 7) { + await upgradeDbToV8(db); + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 8) { + await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 9) { + await upgradeDbToV10(db); + } }, onOpen: (Database db) async { await db.transaction((txn) async{ @@ -32,11 +276,17 @@ Future> loadDb() async { 'SELECT * FROM servers', ); }); + await db.transaction((txn) async{ + appConfig = await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); } ); return { "servers": servers, + "appConfig": appConfig![0], "dbInstance": db, }; } \ No newline at end of file diff --git a/lib/widgets/add_server/add_server_functions.dart b/lib/widgets/add_server/add_server_functions.dart index bbb88fd..6dd9c38 100644 --- a/lib/widgets/add_server/add_server_functions.dart +++ b/lib/widgets/add_server/add_server_functions.dart @@ -1,4 +1,3 @@ -import 'package:adguard_home_manager/constants/regexps.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -50,7 +49,8 @@ String? validateSubroute({ required String? value }) { if (value != null && value != '') { - if (Regexps.subroute.hasMatch(value) == true) { + RegExp subrouteRegexp = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$'); + if (subrouteRegexp.hasMatch(value) == true) { return null; } else { @@ -67,7 +67,9 @@ String? validateAddress({ required String? value }) { if (value != null && value != '') { - if (Regexps.ipv4Address.hasMatch(value) == true || Regexps.domain.hasMatch(value) == true) { + RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); + RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)$'); + if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) { return null; } else { diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index a7fc768..59e8a55 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -127,7 +127,6 @@ class _AddServerModalState extends State { if (status == AuthStatus.manyAttepts) return AppLocalizations.of(context)!.tooManyAttempts; if (status == AuthStatus.socketException || status == AuthStatus.timeoutException) return AppLocalizations.of(context)!.cantReachServer; if (status == AuthStatus.serverError) return AppLocalizations.of(context)!.serverError; - if (status == AuthStatus.handshakeException) return AppLocalizations.of(context)!.sslError; return AppLocalizations.of(context)!.unknownError; } @@ -142,7 +141,6 @@ class _AddServerModalState extends State { port: portController.text != '' ? int.parse(portController.text) : null, user: userController.text != "" ? userController.text : null, password: passwordController.text != "" ? passwordController.text : null, - path: pathController.text != "" ? pathController.text : null, defaultServer: defaultServer, authToken: homeAssistant == true ? encodeBase64UserPass(userController.text, passwordController.text) @@ -158,7 +156,7 @@ class _AddServerModalState extends State { if (result != AuthStatus.success) { cancelConnecting(); if (mounted) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: getErrorMessage(result), color: Colors.red @@ -175,8 +173,6 @@ class _AddServerModalState extends State { final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); final serverStatus = await apiClient2.getServerStatus(); - if (!context.mounted) return; - // If something goes wrong when fetching server status if (serverStatus.successful == false) { statusProvider.setServerStatusLoad(LoadStatus.error); @@ -205,16 +201,16 @@ class _AddServerModalState extends State { final serverCreated = await serversProvider.createServer(serverObj); - if (!context.mounted) return; - // If something goes wrong when saving the connection on the db if (serverCreated != null) { - setState(() => isConnecting = false); - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionNotCreated, - color: Colors.red - ); + if (mounted) setState(() => isConnecting = false); + if (mounted) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionNotCreated, + color: Colors.red + ); + } return; } @@ -259,7 +255,7 @@ class _AddServerModalState extends State { if (result != AuthStatus.success) { cancelConnecting(); if (mounted) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: getErrorMessage(result), color: Colors.red @@ -297,12 +293,10 @@ class _AddServerModalState extends State { } final serverSaved = await serversProvider.editServer(serverObj); - - if (!mounted) return; // If something goes wrong when saving the connection on the db if (serverSaved != null) { - setState(() => isConnecting = false); + if (mounted) setState(() => isConnecting = false); appConfigProvider.addLog( AppLog( type: 'save_connection_db', @@ -310,11 +304,13 @@ class _AddServerModalState extends State { message: serverSaved.toString() ) ); - showSnackbar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionNotCreated, - color: Colors.red - ); + if (mounted) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionNotCreated, + color: Colors.red + ); + } return; } @@ -422,40 +418,16 @@ class _AddServerModalState extends State { colors: SegmentedButtonSlideColors( barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2), backgroundSelectedColor: Theme.of(context).colorScheme.primary, + foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary, + foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface, + hoverColor: Theme.of(context).colorScheme.onSurfaceVariant, ), textOverflow: TextOverflow.ellipsis, + fontSize: 14, height: 40, margin: const EdgeInsets.symmetric( horizontal: 24, ), - selectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, - fontWeight: FontWeight.w700 - ), - unselectedTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - hoverTextStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - Card( - margin: const EdgeInsets.only( - top: 16, left: 24, right: 24 - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.warning_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Flexible(child: Text(AppLocalizations.of(context)!.redirectHttpsWarning)) - ], - ), - ), ), if (connectionType == ConnectionType.https) Card( margin: const EdgeInsets.only( @@ -465,10 +437,7 @@ class _AddServerModalState extends State { padding: const EdgeInsets.all(16), child: Row( children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + const Icon(Icons.info_rounded), const SizedBox(width: 16), Flexible(child: Text(AppLocalizations.of(context)!.sslWarning)) ], @@ -581,10 +550,8 @@ class _AddServerModalState extends State { const SizedBox(width: 8) ], ), - body: SafeArea( - child: ListView( - children: form() - ), + body: ListView( + children: form() ), ), ); diff --git a/lib/widgets/add_server/form_text_field.dart b/lib/widgets/add_server/form_text_field.dart index 72d410b..0fb6f94 100644 --- a/lib/widgets/add_server/form_text_field.dart +++ b/lib/widgets/add_server/form_text_field.dart @@ -13,7 +13,7 @@ class FormTextField extends StatelessWidget { final bool isConnecting; const FormTextField({ - super.key, + Key? key, required this.label, required this.controller, this.error, @@ -24,7 +24,7 @@ class FormTextField extends StatelessWidget { this.hintText, this.helperText, required this.isConnecting - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/combined_line_chart.dart b/lib/widgets/combined_line_chart.dart index 9aceb67..d22b2cc 100644 --- a/lib/widgets/combined_line_chart.dart +++ b/lib/widgets/combined_line_chart.dart @@ -15,12 +15,12 @@ class CustomCombinedLineChart extends StatelessWidget { final bool daysInterval; const CustomCombinedLineChart({ - super.key, + Key? key, required this.inputData, required this.context, required this.dates, required this.daysInterval - }); + }) : super(key: key); LineChartData mainData(Map data, ThemeMode selectedTheme) { String chartDate(DateTime date) { @@ -55,11 +55,11 @@ class CustomCombinedLineChart extends StatelessWidget { } return LineChartData( - gridData: const FlGridData( + gridData: FlGridData( show: false, drawVerticalLine: false, ), - titlesData: const FlTitlesData( + titlesData: FlTitlesData( show: false, ), borderData: FlBorderData( @@ -73,7 +73,7 @@ class CustomCombinedLineChart extends StatelessWidget { barWidth: 2, isStrokeCapRound: true, preventCurveOverShooting: true, - dotData: const FlDotData( + dotData: FlDotData( show: false, ), belowBarData: BarAreaData( @@ -86,7 +86,7 @@ class CustomCombinedLineChart extends StatelessWidget { enabled: true, touchTooltipData: LineTouchTooltipData( fitInsideHorizontally: true, - getTooltipColor: (touchedSpot) => selectedTheme == ThemeMode.light + tooltipBgColor: selectedTheme == ThemeMode.light ? const Color.fromRGBO(220, 220, 220, 0.9) : const Color.fromRGBO(35, 35, 35, 0.9), getTooltipItems: (items) { diff --git a/lib/widgets/confirm_action_modal.dart b/lib/widgets/confirm_action_modal.dart index 4ed9cd7..d0bb314 100644 --- a/lib/widgets/confirm_action_modal.dart +++ b/lib/widgets/confirm_action_modal.dart @@ -8,12 +8,12 @@ class ConfirmActionModal extends StatelessWidget { final void Function() onConfirm; const ConfirmActionModal({ - super.key, + Key? key, required this.icon, required this.title, required this.message, required this.onConfirm - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_checkbox_list_tile.dart b/lib/widgets/custom_checkbox_list_tile.dart index 39c4cdf..be14a3c 100644 --- a/lib/widgets/custom_checkbox_list_tile.dart +++ b/lib/widgets/custom_checkbox_list_tile.dart @@ -9,14 +9,14 @@ class CustomCheckboxListTile extends StatelessWidget { final EdgeInsets? padding; const CustomCheckboxListTile({ - super.key, + Key? key, required this.value, required this.onChanged, required this.title, this.disabled, this.subtitle, this.padding - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_list_tile.dart b/lib/widgets/custom_list_tile.dart index 5bf1e42..0523468 100644 --- a/lib/widgets/custom_list_tile.dart +++ b/lib/widgets/custom_list_tile.dart @@ -14,7 +14,7 @@ class CustomListTile extends StatelessWidget { final Color? color; const CustomListTile({ - super.key, + Key? key, required this.title, this.subtitle, this.subtitleWidget, @@ -26,7 +26,7 @@ class CustomListTile extends StatelessWidget { this.disabled, this.onHover, this.color, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_pie_chart.dart b/lib/widgets/custom_pie_chart.dart index 343ec58..0c8f9f6 100644 --- a/lib/widgets/custom_pie_chart.dart +++ b/lib/widgets/custom_pie_chart.dart @@ -7,11 +7,11 @@ class CustomPieChart extends StatelessWidget { final Duration? animationDuration; const CustomPieChart({ - super.key, + Key? key, required this.data, required this.colors, - this.animationDuration = const Duration(milliseconds: 800), - }); + this.animationDuration = const Duration(milliseconds: 800), + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_radio.dart b/lib/widgets/custom_radio.dart index 5058316..f8f2a08 100644 --- a/lib/widgets/custom_radio.dart +++ b/lib/widgets/custom_radio.dart @@ -7,12 +7,12 @@ class CustomRadio extends StatelessWidget { final Color backgroundColor; const CustomRadio({ - super.key, + Key? key, required this.value, required this.groupValue, this.onChange, required this.backgroundColor, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_radio_list_tile.dart b/lib/widgets/custom_radio_list_tile.dart index 0469bc9..4d59b3c 100644 --- a/lib/widgets/custom_radio_list_tile.dart +++ b/lib/widgets/custom_radio_list_tile.dart @@ -11,14 +11,14 @@ class CustomRadioListTile extends StatelessWidget { final void Function(String) onChanged; const CustomRadioListTile({ - super.key, + Key? key, required this.groupValue, required this.value, required this.radioBackgroundColor, required this.title, this.subtitle, required this.onChanged, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_radio_toggle.dart b/lib/widgets/custom_radio_toggle.dart index 2698243..59cb9e9 100644 --- a/lib/widgets/custom_radio_toggle.dart +++ b/lib/widgets/custom_radio_toggle.dart @@ -7,12 +7,12 @@ class CustomRadioToggle extends StatelessWidget { final void Function(String) onTap; const CustomRadioToggle({ - super.key, + Key? key, required this.groupSelected, required this.value, required this.label, required this.onTap, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_settings_tile.dart b/lib/widgets/custom_settings_tile.dart index 824b710..554f13b 100644 --- a/lib/widgets/custom_settings_tile.dart +++ b/lib/widgets/custom_settings_tile.dart @@ -12,7 +12,7 @@ class CustomSettingsTile extends StatelessWidget { final int? selectedItem; const CustomSettingsTile({ - super.key, + Key? key, required this.title, this.subtitle, this.subtitleWidget, @@ -22,7 +22,7 @@ class CustomSettingsTile extends StatelessWidget { this.padding, required this.thisItem, required this.selectedItem, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/custom_switch_list_tile.dart b/lib/widgets/custom_switch_list_tile.dart index 84e65c5..84db718 100644 --- a/lib/widgets/custom_switch_list_tile.dart +++ b/lib/widgets/custom_switch_list_tile.dart @@ -7,18 +7,16 @@ class CustomSwitchListTile extends StatelessWidget { final String? subtitle; final bool? disabled; final EdgeInsets? padding; - final IconData? leadingIcon; const CustomSwitchListTile({ - super.key, + Key? key, required this.value, required this.onChanged, required this.title, this.disabled, this.subtitle, - this.padding, - this.leadingIcon, - }); + this.padding + }) : super(key: key); @override Widget build(BuildContext context) { @@ -35,13 +33,6 @@ class CustomSwitchListTile extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (leadingIcon != null) ...[ - Icon( - leadingIcon, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/widgets/floating_search_bar.dart b/lib/widgets/floating_search_bar.dart deleted file mode 100644 index bc09058..0000000 --- a/lib/widgets/floating_search_bar.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/config/globals.dart'; - -class FloatingSearchBar extends StatefulWidget { - final void Function(String) onSearchCompleted; - final RenderBox? searchButtonRenderBox; - final String? existingSearchValue; - final void Function(String)? onSearchFieldUpdated; - final void Function()? onSearchFieldCleared; - - const FloatingSearchBar({ - super.key, - required this.onSearchCompleted, - required this.searchButtonRenderBox, - this.existingSearchValue, - this.onSearchFieldUpdated, - this.onSearchFieldCleared, - }); - - @override - State createState() => _SearchState(); -} - -class _SearchState extends State { - final _searchController = TextEditingController(); - - @override - void initState() { - if (widget.existingSearchValue != null) { - _searchController.text = widget.existingSearchValue!; - } - - super.initState(); - } - - @override - Widget build(BuildContext context) { - final position = widget.searchButtonRenderBox?.localToGlobal(Offset.zero); - final topPadding = MediaQuery.of(globalNavigatorKey.currentContext!).viewPadding.top; - - return GestureDetector( - onTap: () => Navigator.pop(context), - child: Material( - color: Colors.transparent, - child: LayoutBuilder( - builder: (context, constraints) { - final double width = constraints.maxWidth - 32 > 500 ? 500 : constraints.maxWidth - 32; - return Stack( - alignment: Alignment.topCenter, - children: [ - Positioned( - top: position != null ? position.dy - topPadding : topPadding, - child: SizedBox( - width: width, - child: GestureDetector( - onTap: () => {}, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16) - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: TextFormField( - controller: _searchController, - onChanged: widget.onSearchFieldUpdated, - onFieldSubmitted: (v) { - widget.onSearchCompleted(v); - Navigator.pop(context); - }, - autofocus: true, - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.search, - prefixIcon: const Icon(Icons.search_rounded), - border: InputBorder.none, - filled: true, - fillColor: Colors.grey.withOpacity(0.2), - suffixIcon: _searchController.text != "" - ? IconButton( - onPressed: () { - setState(() => _searchController.text = ""); - if (widget.onSearchFieldCleared != null) { - widget.onSearchFieldCleared!(); - } - }, - icon: const Icon( - Icons.close_rounded, - size: 20, - ), - tooltip: AppLocalizations.of(context)!.clearSearch, - ) - : null, - contentPadding: const EdgeInsets.symmetric(vertical: 12), - ), - ), - ), - ), - ), - ), - ) - ], - ); - } - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 119bee1..9a42415 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -1,10 +1,14 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/widgets/update_modal.dart'; import 'package:adguard_home_manager/widgets/system_ui_overlay_style.dart'; +import 'package:adguard_home_manager/functions/check_app_updates.dart'; +import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/config/app_screens.dart'; import 'package:adguard_home_manager/config/sizes.dart'; @@ -33,23 +37,23 @@ class _LayoutState extends State with WidgetsBindingObserver { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { - // if (kDebugMode) return; // Don't check for app updates on debug mode - // final appConfigProvider = Provider.of(context, listen: false); - // final result = await checkAppUpdates( - // currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber, - // installationSource: appConfigProvider.installationSource, - // setUpdateAvailable: appConfigProvider.setAppUpdatesAvailable, - // isBeta: appConfigProvider.getAppInfo!.version.contains('beta'), - // ); - // if (result != null && appConfigProvider.doNotRememberVersion != result.tagName && mounted) { - // await showDialog( - // context: context, - // builder: (context) => UpdateModal( - // gitHubRelease: result, - // onDownload: (link, version) => openUrl(link), - // ), - // ); - // } + if (kDebugMode) return; // Don't check for app updates on debug mode + final appConfigProvider = Provider.of(context, listen: false); + final result = await checkAppUpdates( + currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber, + installationSource: appConfigProvider.installationSource, + setUpdateAvailable: appConfigProvider.setAppUpdatesAvailable, + isBeta: appConfigProvider.getAppInfo!.version.contains('beta'), + ); + if (result != null && appConfigProvider.doNotRememberVersion != result.tagName && mounted) { + await showDialog( + context: context, + builder: (context) => UpdateModal( + gitHubRelease: result, + onDownload: (link, version) => openUrl(link), + ), + ); + } }); } diff --git a/lib/widgets/line_chart.dart b/lib/widgets/line_chart.dart index f6899d1..5a5e181 100644 --- a/lib/widgets/line_chart.dart +++ b/lib/widgets/line_chart.dart @@ -13,13 +13,13 @@ class CustomLineChart extends StatelessWidget { final BuildContext context; const CustomLineChart({ - super.key, + Key? key, required this.data, required this.color, required this.dates, required this.daysInterval, required this.context - }); + }) : super(key: key); String chartDate(DateTime date) { String twoDigits(int number) => number.toString().padLeft(2, '0'); @@ -55,11 +55,11 @@ class CustomLineChart extends StatelessWidget { LineChartData mainData(Map data, ThemeMode selectedTheme) { return LineChartData( - gridData: const FlGridData( + gridData: FlGridData( show: false, drawVerticalLine: false, ), - titlesData: const FlTitlesData( + titlesData: FlTitlesData( show: false, ), borderData: FlBorderData( @@ -73,7 +73,7 @@ class CustomLineChart extends StatelessWidget { barWidth: 2, isStrokeCapRound: true, preventCurveOverShooting: true, - dotData: const FlDotData( + dotData: FlDotData( show: false, ), belowBarData: BarAreaData( @@ -91,7 +91,7 @@ class CustomLineChart extends StatelessWidget { enabled: true, touchTooltipData: LineTouchTooltipData( fitInsideHorizontally: true, - getTooltipColor: (touchedSpot) => selectedTheme == ThemeMode.light + tooltipBgColor: selectedTheme == ThemeMode.light ? const Color.fromRGBO(220, 220, 220, 0.9) : const Color.fromRGBO(35, 35, 35, 0.9), getTooltipItems: (items) => [ diff --git a/lib/widgets/list_bottom_sheet.dart b/lib/widgets/list_bottom_sheet.dart deleted file mode 100644 index 83f9c78..0000000 --- a/lib/widgets/list_bottom_sheet.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; - -class ListBottomSheet extends StatelessWidget { - final IconData icon; - final String title; - final List children; - final double? initialChildSize; - final double? minChildSize; - final double? maxChildSize; - - const ListBottomSheet({ - super.key, - required this.icon, - required this.title, - required this.children, - this.initialChildSize, - this.maxChildSize, - this.minChildSize, - }); - - @override - Widget build(BuildContext context) { - return DraggableScrollableSheet( - initialChildSize: initialChildSize ?? 0.6, - minChildSize: minChildSize ?? 0.3, - maxChildSize: maxChildSize ?? 1, - builder: (context, controller) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - ), - child: SafeArea( - child: ListView( - controller: controller, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - margin: const EdgeInsets.all(16), - width: 36, - height: 4, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - children: [ - Icon( - icon, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), - Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ), - ...children - ], - ), - ), - ); - }, - ); - } -} \ No newline at end of file diff --git a/lib/widgets/load_status_widgets.dart b/lib/widgets/load_status_widgets.dart deleted file mode 100644 index 2b1e699..0000000 --- a/lib/widgets/load_status_widgets.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter/material.dart'; - -class LoadingData extends StatelessWidget { - final String text; - - const LoadingData({ - super.key, - required this.text, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(24), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ) - ], - ), - ), - ); - } -} - -class ErrorLoadData extends StatelessWidget { - final String text; - - const ErrorLoadData({ - super.key, - required this.text - }); - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ) - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/widgets/master_switch.dart b/lib/widgets/master_switch.dart deleted file mode 100644 index 323de68..0000000 --- a/lib/widgets/master_switch.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; - -class MasterSwitch extends StatelessWidget { - final String label; - final bool value; - final void Function(bool) onChange; - final EdgeInsets? margin; - - const MasterSwitch({ - super.key, - required this.label, - required this.value, - required this.onChange, - this.margin - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: margin ?? const EdgeInsets.symmetric(horizontal: 16), - child: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: () => onChange(!value), - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: const TextStyle( - fontSize: 18, - ), - ), - Switch( - value: value, - onChanged: onChange, - ) - ], - ), - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/widgets/menu_bar.dart b/lib/widgets/menu_bar.dart index fe80446..0151b87 100644 --- a/lib/widgets/menu_bar.dart +++ b/lib/widgets/menu_bar.dart @@ -11,9 +11,9 @@ class CustomMenuBar extends StatelessWidget { final Widget child; const CustomMenuBar({ - super.key, + Key? key, required this.child - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/option_box.dart b/lib/widgets/option_box.dart index e8514ef..f19a88f 100644 --- a/lib/widgets/option_box.dart +++ b/lib/widgets/option_box.dart @@ -7,12 +7,12 @@ class OptionBox extends StatelessWidget { final String label; const OptionBox({ - super.key, + Key? key, required this.optionsValue, required this.itemValue, required this.onTap, required this.label, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/options_menu.dart b/lib/widgets/options_menu.dart index 8daeb00..861edab 100644 --- a/lib/widgets/options_menu.dart +++ b/lib/widgets/options_menu.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/models/menu_option.dart'; class OptionsMenu extends StatelessWidget { final Widget child; - final List Function(dynamic) options; + final List options; final dynamic value; final BorderRadius? borderRadius; final void Function(dynamic)? onTap; @@ -40,11 +40,11 @@ class OptionsMenu extends StatelessWidget { return Material( color: Colors.transparent, child: ContextMenuArea( - builder: (context) => options(value).map((opt) => CustomListTile( + builder: (context) => options.map((opt) => CustomListTile( title: opt.title, icon: opt.icon, onTap: () { - opt.action(); + opt.action(value); Navigator.pop(context); }, )).toList(), @@ -64,7 +64,7 @@ class OptionsMenu extends StatelessWidget { } class _OptionsModal extends StatelessWidget { - final List Function(dynamic) options; + final List options; final dynamic value; const _OptionsModal({ @@ -76,7 +76,6 @@ class _OptionsModal extends StatelessWidget { Widget build(BuildContext context) { return AlertDialog( contentPadding: const EdgeInsets.symmetric(vertical: 16), - scrollable: true, title: Column( children: [ Icon( @@ -95,17 +94,19 @@ class _OptionsModal extends StatelessWidget { ), content: ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 500 + maxWidth: 400 ), - child: Column( - children: options(value).map((opt) => CustomListTileDialog( - title: opt.title, - icon: opt.icon, - onTap: () { - Navigator.pop(context); - opt.action(); - }, - )).toList() + child: SingleChildScrollView( + child: Wrap( + children: options.map((opt) => CustomListTileDialog( + title: opt.title, + icon: opt.icon, + onTap: () { + Navigator.pop(context); + opt.action(value); + }, + )).toList() + ), ), ), actions: [ diff --git a/lib/widgets/process_dialog.dart b/lib/widgets/process_dialog.dart index f7f85a9..0c91442 100644 --- a/lib/widgets/process_dialog.dart +++ b/lib/widgets/process_dialog.dart @@ -4,9 +4,9 @@ class ProcessDialog extends StatelessWidget { final String message; const ProcessDialog({ - super.key, + Key? key, required this.message, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/section_label.dart b/lib/widgets/section_label.dart index 1e3980f..c1f767a 100644 --- a/lib/widgets/section_label.dart +++ b/lib/widgets/section_label.dart @@ -5,10 +5,10 @@ class SectionLabel extends StatelessWidget { final EdgeInsets? padding; const SectionLabel({ - super.key, + Key? key, required this.label, this.padding - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/servers_list/delete_modal.dart b/lib/widgets/servers_list/delete_modal.dart index 4067d18..dbf169d 100644 --- a/lib/widgets/servers_list/delete_modal.dart +++ b/lib/widgets/servers_list/delete_modal.dart @@ -13,9 +13,9 @@ class DeleteModal extends StatelessWidget { final Server serverToDelete; const DeleteModal({ - super.key, + Key? key, required this.serverToDelete, - }); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -34,14 +34,14 @@ class DeleteModal extends StatelessWidget { appConfigProvider.setSelectedScreen(0); } - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.connectionRemoved, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.connectionCannotBeRemoved, color: Colors.red diff --git a/lib/widgets/servers_list/server_tile_functions.dart b/lib/widgets/servers_list/server_tile_functions.dart index 06dbaee..ec3c92e 100644 --- a/lib/widgets/servers_list/server_tile_functions.dart +++ b/lib/widgets/servers_list/server_tile_functions.dart @@ -50,15 +50,14 @@ void showDeleteModal({ required BuildContext context, required Server server }) async { - await Future.delayed(const Duration(seconds: 0), () { - if (!context.mounted) return; + await Future.delayed(const Duration(seconds: 0), () => { showDialog( context: context, builder: (context) => DeleteModal( serverToDelete: server, ), barrierDismissible: false - ); + ) }); } @@ -134,7 +133,7 @@ void connectToServer({ process.close(); if (!context.mounted) return; final appConfigProvider = Provider.of(context, listen: false); - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.cannotConnect, color: Colors.red @@ -151,14 +150,14 @@ void setDefaultServer({ if (!context.mounted) return; final appConfigProvider = Provider.of(context, listen: false); if (result == null) { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.connectionDefaultSuccessfully, color: Colors.green ); } else { - showSnackbar( + showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.connectionDefaultFailed, color: Colors.red diff --git a/lib/widgets/servers_list/servers_list.dart b/lib/widgets/servers_list/servers_list.dart index d5a8d1b..8e530f2 100644 --- a/lib/widgets/servers_list/servers_list.dart +++ b/lib/widgets/servers_list/servers_list.dart @@ -16,13 +16,13 @@ class ServersList extends StatelessWidget { final double breakingWidth; const ServersList({ - super.key, + Key? key, required this.context, required this.controllers, required this.onChange, required this.scrollController, required this.breakingWidth - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index 29b11a5..47ca6fe 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -68,7 +68,7 @@ class _ServersListItemState extends State with SingleTickerProv decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: Theme.of(context).colorScheme.surfaceContainerHighest, + color: Theme.of(context).colorScheme.surfaceVariant, width: 1 ) ) diff --git a/lib/widgets/system_ui_overlay_style.dart b/lib/widgets/system_ui_overlay_style.dart index ab5b8d0..83557f1 100644 --- a/lib/widgets/system_ui_overlay_style.dart +++ b/lib/widgets/system_ui_overlay_style.dart @@ -5,14 +5,12 @@ class OverlayStyle extends StatelessWidget { final Widget child; const OverlayStyle({ - super.key, + Key? key, required this.child - }); + }) : super(key: key); @override Widget build(BuildContext context) { - final systemGestureInsets = MediaQuery.of(context).systemGestureInsets; - return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarColor: Colors.transparent, @@ -22,13 +20,7 @@ class OverlayStyle extends StatelessWidget { statusBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light, - systemNavigationBarColor: systemGestureInsets.left > 0 // If true gestures navigation - ? Colors.transparent - : ElevationOverlay.applySurfaceTint( - Theme.of(context).colorScheme.surface, - Theme.of(context).colorScheme.surfaceTint, - 3 - ), + systemNavigationBarColor: Theme.of(context).colorScheme.background, systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light, diff --git a/lib/widgets/tab_content_list.dart b/lib/widgets/tab_content_list.dart index 0e09a4f..d1f2ad0 100644 --- a/lib/widgets/tab_content_list.dart +++ b/lib/widgets/tab_content_list.dart @@ -17,10 +17,9 @@ class CustomTabContentList extends StatelessWidget { final bool? fabVisible; final bool? noSliver; final EdgeInsets? listPadding; - final double? heightFabHidden; const CustomTabContentList({ - super.key, + Key? key, required this.loadingGenerator, required this.itemsCount, required this.contentWidget, @@ -32,9 +31,8 @@ class CustomTabContentList extends StatelessWidget { this.fab, this.fabVisible, this.noSliver, - this.listPadding, - this.heightFabHidden, - }); + this.listPadding + }) : super(key: key); @override Widget build(BuildContext context) { @@ -51,6 +49,7 @@ class CustomTabContentList extends StatelessWidget { else { return SafeArea( top: false, + bottom: false, child: Builder( builder: (BuildContext context) => CustomScrollView( slivers: [ @@ -73,45 +72,41 @@ class CustomTabContentList extends StatelessWidget { case LoadStatus.loaded: if (noSliver == true) { if (itemsCount > 0) { - return SafeArea( - child: Stack( - children: [ - ListView.builder( - padding: listPadding, - itemCount: itemsCount, - itemBuilder: (context, index) => contentWidget(index), - ), - if (fab != null) AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: fabVisible != null && fabVisible == true ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: fab! - ), - ], - ), + return Stack( + children: [ + ListView.builder( + padding: listPadding, + itemCount: itemsCount, + itemBuilder: (context, index) => contentWidget(index), + ), + if (fab != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible != null && fabVisible == true ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: fab! + ), + ], ); } else { - return SafeArea( - child: Stack( - children: [ - noData, - if (fab != null) AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: fabVisible != null && fabVisible == true ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: fab! - ), - ], - ), + return Stack( + children: [ + noData, + if (fab != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible != null && fabVisible == true ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: fab! + ), + ], ); } } @@ -151,10 +146,10 @@ class CustomTabContentList extends StatelessWidget { curve: Curves.easeInOut, bottom: fabVisible != null && fabVisible == true ? appConfigProvider.showingSnackbar - ? 90 : 20 - : (heightFabHidden ?? -90), + ? 70 : 20 + : -70, right: 20, - child: SafeArea(child: fab!) + child: fab! ), ], ); @@ -174,6 +169,7 @@ class CustomTabContentList extends StatelessWidget { else { return SafeArea( top: false, + bottom: false, child: Builder( builder: (BuildContext context) => CustomScrollView( slivers: [ diff --git a/lib/widgets/update_modal.dart b/lib/widgets/update_modal.dart index 8cedd2e..5b99b66 100644 --- a/lib/widgets/update_modal.dart +++ b/lib/widgets/update_modal.dart @@ -11,10 +11,10 @@ class UpdateModal extends StatefulWidget { final void Function(String, String) onDownload; const UpdateModal({ - super.key, + Key? key, required this.gitHubRelease, required this.onDownload, - }); + }) : super(key: key); @override State createState() => _UpdateModalState(); diff --git a/lib/widgets/version_warning_modal.dart b/lib/widgets/version_warning_modal.dart index 72dbe4d..f2230b0 100644 --- a/lib/widgets/version_warning_modal.dart +++ b/lib/widgets/version_warning_modal.dart @@ -5,9 +5,9 @@ class VersionWarningModal extends StatelessWidget { final String version; const VersionWarningModal({ - super.key, + Key? key, required this.version, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 4fbee04..1786709 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,19 +7,15 @@ #include "generated_plugin_registrant.h" #include -#include #include #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) screen_retriever_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); - screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); @@ -29,7 +25,7 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); - g_autoptr(FlPluginRegistrar) window_manager_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); - window_manager_plugin_register_with_registrar(window_manager_registrar); + g_autoptr(FlPluginRegistrar) window_size_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin"); + window_size_plugin_register_with_registrar(window_size_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 787cc64..59ac090 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,11 +4,10 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color - screen_retriever_linux sentry_flutter sqlite3_flutter_libs url_launcher_linux - window_manager + window_size ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 69e9dbc..31a4f66 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,23 +8,19 @@ import Foundation import device_info_plus import dynamic_color import package_info_plus -import screen_retriever_macos import sentry_flutter -import shared_preferences_foundation -import sqflite_darwin +import sqflite import sqlite3_flutter_libs import url_launcher_macos -import window_manager +import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) - ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) + WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index ae5c12f..4543f6f 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -4,43 +4,39 @@ PODS: - dynamic_color (0.0.2): - FlutterMacOS - FlutterMacOS (1.0.0) + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) - package_info_plus (0.0.1): - FlutterMacOS - - screen_retriever_macos (0.0.1): - - FlutterMacOS - - Sentry/HybridSDK (8.44.0) - - sentry_flutter (8.13.2): + - Sentry/HybridSDK (8.15.2): + - SentryPrivate (= 8.15.2) + - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.44.0) - - shared_preferences_foundation (0.0.1): - - Flutter + - Sentry/HybridSDK (= 8.15.2) + - SentryPrivate (8.15.2) + - sqflite (0.0.2): - FlutterMacOS - - sqflite_darwin (0.0.4): - - Flutter - - FlutterMacOS - - sqlite3 (3.49.1): - - sqlite3/common (= 3.49.1) - - sqlite3/common (3.49.1) - - sqlite3/dbstatvtab (3.49.1): + - FMDB (>= 2.7.5) + - sqlite3 (3.44.0): + - sqlite3/common (= 3.44.0) + - sqlite3/common (3.44.0) + - sqlite3/fts5 (3.44.0): - sqlite3/common - - sqlite3/fts5 (3.49.1): + - sqlite3/perf-threadsafe (3.44.0): - sqlite3/common - - sqlite3/perf-threadsafe (3.49.1): - - sqlite3/common - - sqlite3/rtree (3.49.1): + - sqlite3/rtree (3.44.0): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - - Flutter - FlutterMacOS - - sqlite3 (~> 3.49.1) - - sqlite3/dbstatvtab + - sqlite3 (~> 3.44.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree - url_launcher_macos (0.0.1): - FlutterMacOS - - window_manager (0.2.0): + - window_size (0.0.2): - FlutterMacOS DEPENDENCIES: @@ -48,17 +44,17 @@ DEPENDENCIES: - dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`) - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) + - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) + - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) + - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) SPEC REPOS: trunk: + - FMDB - Sentry + - SentryPrivate - sqlite3 EXTERNAL SOURCES: @@ -70,36 +66,32 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos - screen_retriever_macos: - :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos sentry_flutter: :path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos - shared_preferences_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin - sqflite_darwin: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin + sqflite: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos sqlite3_flutter_libs: - :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin + :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos - window_manager: - :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos + window_size: + :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos SPEC CHECKSUMS: - device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215 + device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b - screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 - Sentry: 0f9bc9adfc0b960e7f3bb5ec67e9a3d8193f3bdb - sentry_flutter: 64a43fb39ab4c7f67d8a4cad52b49e22439e58b7 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 - sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce + Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923 + sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b + SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 + sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea + sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 + sqlite3_flutter_libs: 5b7e226d522d67be60d7ade93f5aa11ebc0cd796 + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.15.2 +COCOAPODS: 1.12.1 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index f7a463a..cb14ee0 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index dcd4e29..b1bc689 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index b3c1761..d53ef64 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,13 +1,9 @@ import Cocoa import FlutterMacOS -@main +@NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } - - override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { - return true - } } diff --git a/pubspec.lock b/pubspec.lock index 207783c..274cfb3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,58 +13,58 @@ packages: dependency: "direct main" description: name: animations - sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb + sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70 url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.0.8" ansicolor: dependency: transitive description: name: ansicolor - sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.2" archive: dependency: transitive description: name: archive - sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12" + sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "3.4.9" args: dependency: transitive description: name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.4.2" async: dependency: "direct main" description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.11.0" 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" 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: @@ -77,26 +77,26 @@ packages: dependency: transitive description: name: cli_util - sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.4.0" clock: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.1" collection: dependency: transitive description: name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.1" + version: "1.18.0" contextmenu: dependency: "direct main" description: @@ -105,62 +105,70 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" crypto: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.3" csslib: dependency: transitive description: name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "0.17.3" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.6" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513" + sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6" url: "https://pub.dev" source: hosted - version: "11.3.3" + version: "9.1.1" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.0" dynamic_color: dependency: "direct main" description: name: dynamic_color - sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f" url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.6.8" equatable: dependency: transitive description: name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.5" expandable: dependency: "direct main" description: @@ -173,119 +181,79 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.1" ffi: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.0" file: dependency: transitive description: name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" + version: "7.0.0" fl_chart: dependency: "direct main" description: name: fl_chart - sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" + sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79" url: "https://pub.dev" source: hosted - version: "0.70.2" + version: "0.65.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_custom_tabs: + flutter_displaymode: dependency: "direct main" description: - name: flutter_custom_tabs - sha256: "34167bd15fa3479855c011f868e0789c9569c12b64358ca7250accc5a24c3312" + name: flutter_displaymode + sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" url: "https://pub.dev" source: hosted - version: "2.1.0" - flutter_custom_tabs_android: - dependency: transitive - description: - name: flutter_custom_tabs_android - sha256: cf06fde8c002f326dc6cbf69ee3f97c3feead4436229da02d2e2aa39d5a5dbf4 - url: "https://pub.dev" - source: hosted - version: "2.1.0" - flutter_custom_tabs_ios: - dependency: transitive - description: - name: flutter_custom_tabs_ios - sha256: ef2de533bc45fb84fefc3854bc8b1e43001671c6bc6bc55faa57942eecd3f70a - url: "https://pub.dev" - source: hosted - version: "2.1.0" - flutter_custom_tabs_platform_interface: - dependency: transitive - description: - name: flutter_custom_tabs_platform_interface - sha256: e18e9b08f92582123bdb84fb6e4c91804b0579700fed6f887d32fd9a1e96a5d5 - url: "https://pub.dev" - source: hosted - version: "2.1.0" - flutter_custom_tabs_web: - dependency: transitive - description: - name: flutter_custom_tabs_web - sha256: "08ae322b11e1972a233d057542279873d0f9d1d5f8159c2c741457239d9d562c" - url: "https://pub.dev" - source: hosted - version: "2.1.0" + version: "0.6.0" flutter_dotenv: dependency: "direct main" description: name: flutter_dotenv - sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "5.1.0" flutter_html: dependency: "direct main" description: name: flutter_html - sha256: "38a2fd702ffdf3243fb7441ab58aa1bc7e6922d95a50db76534de8260638558d" + sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.0-beta.2" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" url: "https://pub.dev" source: hosted - version: "0.14.3" + version: "0.13.1" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "3.0.1" flutter_localizations: dependency: "direct main" description: flutter @@ -295,18 +263,18 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5 + sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196" url: "https://pub.dev" source: hosted - version: "0.7.6+2" + version: "0.6.18+2" flutter_native_splash: dependency: "direct dev" description: name: flutter_native_splash - sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a + sha256: c4d899312b36e7454bedfd0a4740275837b99e532d81c8477579d8183db1de6c url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.3.6" flutter_reorderable_list: dependency: "direct main" description: @@ -315,19 +283,36 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + flutter_split_view: + dependency: "direct main" + description: + path: "." + ref: master-alt + resolved-ref: a6aa2419243cc0abccf9474ce340790d6c0ded6f + url: "https://github.com/JGeek00/flutter_split_view" + source: git + version: "0.1.2" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.9" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_browser: + dependency: "direct main" + description: + name: flutter_web_browser + sha256: a5564b736253f745e147b8c4eff86de436324d081974cc1f16bff881134a400f + url: "https://pub.dev" + source: hosted + version: "0.17.1" flutter_web_plugins: dependency: transitive description: flutter @@ -337,82 +322,66 @@ packages: dependency: "direct main" description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.4" http: dependency: "direct main" description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.1.0" 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: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.1.3" intl: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.18.1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" json_annotation: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.9.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec - url: "https://pub.dev" - source: hosted - version: "10.0.8" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" - source: hosted - version: "3.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" + version: "4.8.1" lints: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "3.0.0" list_counter: dependency: transitive description: @@ -425,34 +394,34 @@ packages: dependency: "direct main" description: name: markdown - sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.1.1" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.10.0" nested: dependency: transitive description: @@ -465,74 +434,50 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "5.0.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "2.0.1" path: dependency: transitive description: name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.8.3" path_parsing: dependency: transitive description: name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf url: "https://pub.dev" source: hosted - version: "1.1.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" + version: "1.0.1" percent_indicator: dependency: "direct main" description: name: percent_indicator - sha256: "0d77d5c6fa9b7f60202cedf748b568ba9ba38d3f30405d6ceae4da76f5185462" + sha256: c37099ad833a883c9d71782321cb65c3a848c21b6939b6185f0ff6640d05814c url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "4.2.3" petitparser: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.0.1" pie_chart: dependency: "direct main" description: @@ -541,171 +486,67 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.8" - posix: + version: "2.1.7" + pointycastle: dependency: transitive description: - name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "3.7.3" provider: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" url: "https://pub.dev" source: hosted - version: "6.1.2" - screen_retriever: - dependency: transitive - description: - name: screen_retriever - sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_linux: - dependency: transitive - description: - name: screen_retriever_linux - sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_macos: - dependency: transitive - description: - name: screen_retriever_macos - sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_platform_interface: - dependency: transitive - description: - name: screen_retriever_platform_interface - sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_windows: - dependency: transitive - description: - name: screen_retriever_windows - sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" - url: "https://pub.dev" - source: hosted - version: "0.2.0" + version: "6.1.1" segmented_button_slide: dependency: "direct main" description: name: segmented_button_slide - sha256: d0563010c79a101340cfc127ee5a27c8c7d8640d5ef857a36e9d0b46bc01fed4 + sha256: "96d67344fa65f6b98a317d930bfd4e81e47c4f9e3e2ab1ee8925b55f72cded13" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.0.4" sentry: dependency: transitive description: name: sentry - sha256: "3a64dd001bff768ce5ab6fc3608deef4dde22acd4b5d947763557b20db9e2a32" + sha256: e7ded42974bac5f69e4ca4ddc57d30499dd79381838f24b7e8fd9aa4139e7b79 url: "https://pub.dev" source: hosted - version: "8.14.0" + version: "7.13.2" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: "3d361f2d5f805783e2e4ed1bd475ef126b36cf525b359dc3627a765a3fb7424d" + sha256: d6f55ec7a1f681784165021f749007712a72ff57eadf91e963331b6ae326f089 url: "https://pub.dev" source: hosted - version: "8.14.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" - url: "https://pub.dev" - source: hosted - version: "2.5.2" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" - url: "https://pub.dev" - source: hosted - version: "2.4.8" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" + version: "7.13.2" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" 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: @@ -718,130 +559,106 @@ packages: dependency: "direct main" description: name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" 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.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.0+2" sqflite_common_ffi: dependency: "direct main" description: name: sqflite_common_ffi - sha256: "1f3ef3888d3bfbb47785cc1dda0dc7dd7ebd8c1955d32a9e8e9dae1e38d1c4c1" + sha256: "35d2fce1e971707c227cc4775cc017d5eafe06c2654c3435ebd5c3ad6c170f5f" url: "https://pub.dev" source: hosted - version: "2.3.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.3.0+4" sqlite3: dependency: transitive description: name: sqlite3 - sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e" + sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb url: "https://pub.dev" source: hosted - version: "2.7.5" + version: "2.1.0" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: "1a96b59227828d9eb1463191d684b37a27d66ee5ed7597fcf42eee6452c88a14" + sha256: "3e3583b77cf888a68eae2e49ee4f025f66b86623ef0d83c297c8d903daa14871" url: "https://pub.dev" source: hosted - version: "0.5.32" + version: "0.5.18" 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" + store_checker: + dependency: "direct main" + description: + name: store_checker + sha256: c9c1a93f11fa756fea23a2a08d7e54e84626a8d3c759cf49fc694a3a5afdc705 + url: "https://pub.dev" + source: hosted + version: "1.4.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.2" 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: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.1.0" 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: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.4" - timezone: - dependency: "direct main" - description: - name: timezone - sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d - url: "https://pub.dev" - source: hosted - version: "0.10.0" + version: "0.6.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_io: dependency: transitive description: @@ -854,98 +671,98 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.2.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" url: "https://pub.dev" source: hosted - version: "6.3.15" + version: "6.2.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.2.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.1.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.2.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.2.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.0" uuid: dependency: "direct main" description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.2.1" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" + sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" url: "https://pub.dev" source: hosted - version: "1.1.18" + version: "1.1.9+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" url: "https://pub.dev" source: hosted - version: "1.1.13" + version: "1.1.9+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.9+1" vector_math: dependency: transitive description: @@ -954,70 +771,55 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.dev" - source: hosted - version: "14.3.1" web: dependency: transitive description: name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "0.3.0" win32: dependency: transitive description: name: win32 - sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f + sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "5.1.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" url: "https://pub.dev" source: hosted - version: "2.1.0" - window_manager: + version: "1.1.2" + window_size: dependency: "direct main" description: - name: window_manager - sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059" - url: "https://pub.dev" - source: hosted - version: "0.4.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" + path: "plugins/window_size" + ref: HEAD + resolved-ref: "6c66ad23ee79749f30a8eece542cf54eaf157ed8" + url: "https://github.com/google/flutter-desktop-embedding" + source: git + version: "0.1.0" xml: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.4.2" yaml: 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.27.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2fffd97..c769999 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ 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: 2.20.4+151 +version: 2.12.2+113 environment: sdk: '>=2.18.1 <3.0.0' @@ -41,34 +41,41 @@ dependencies: intl: any provider: ^6.1.1 sqflite: ^2.3.0 - package_info_plus: ^8.0.0 - dynamic_color: ^1.7.0 - animations: ^2.0.10 - device_info_plus: ^11.2.1 + package_info_plus: ^5.0.1 + flutter_displaymode: ^0.6.0 + dynamic_color: ^1.6.8 + animations: ^2.0.8 + device_info_plus: ^9.1.1 uuid: ^4.2.1 expandable: ^5.0.1 - fl_chart: ^0.70.2 + fl_chart: ^0.65.0 + flutter_web_browser: ^0.17.1 flutter_svg: ^2.0.9 percent_indicator: ^4.2.3 - flutter_markdown: ^0.7.1 + store_checker: ^1.4.0 + flutter_markdown: ^0.6.18+2 markdown: ^7.1.1 html: ^0.15.4 flutter_html: ^3.0.0-beta.2 sqlite3_flutter_libs: ^0.5.18 - sqflite_common_ffi: ^2.3.1 + sqflite_common_ffi: ^2.3.0+4 + window_size: + git: + url: https://github.com/google/flutter-desktop-embedding + path: plugins/window_size + flutter_split_view: + git: + url: https://github.com/JGeek00/flutter_split_view + ref: master-alt + url_launcher: ^6.1.11 contextmenu: ^3.0.0 async: ^2.11.0 - sentry_flutter: ^8.0.0 + sentry_flutter: ^7.13.2 flutter_dotenv: ^5.1.0 flutter_reorderable_list: ^1.3.1 pie_chart: ^5.4.0 - segmented_button_slide: ^2.0.0 - http: ^1.1.2 - timezone: ^0.10.0 - flutter_custom_tabs: ^2.0.0+1 - url_launcher: ^6.2.4 - shared_preferences: ^2.2.2 - window_manager: ^0.4.2 + segmented_button_slide: ^1.0.4 + http: ^1.1.0 dev_dependencies: flutter_test: @@ -79,9 +86,9 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 - flutter_launcher_icons: ^0.14.1 - flutter_native_splash: ^2.3.8 + flutter_lints: ^3.0.1 + flutter_launcher_icons: ^0.13.1 + flutter_native_splash: ^2.3.6 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index b0ea5ac..50bb1c8 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,23 +7,20 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include -#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); - ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SentryFlutterPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SentryFlutterPlugin")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); - WindowManagerPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("WindowManagerPlugin")); + WindowSizePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowSizePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 5ab1216..71f5fcf 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,11 +4,10 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color - screen_retriever_windows sentry_flutter sqlite3_flutter_libs url_launcher_windows - window_manager + window_size ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/windows/innosetup_installer_builder.iss b/windows/innosetup_installer_builder.iss index ed5df88..92a4b72 100644 --- a/windows/innosetup_installer_builder.iss +++ b/windows/innosetup_installer_builder.iss @@ -40,11 +40,11 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{ Source: "..\build\windows\x64\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\windows\x64\runner\Release\dynamic_color_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\windows\x64\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\x64\runner\Release\screen_retriever_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\sentry_flutter_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\windows\x64\runner\Release\sqlite3.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\windows\x64\runner\Release\sqlite3_flutter_libs_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\windows\x64\runner\Release\url_launcher_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\x64\runner\Release\window_manager_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\window_size_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\windows\x64\runner\Release\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index 618eef7..1c992e7 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -26,7 +26,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); - Win32Window::Size size(500,700); + Win32Window::Size size(1280, 720); if (!window.Create(L"AdGuard Home Manager", origin, size)) { return EXIT_FAILURE; }