mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-06-30 12:59:52 +00:00
Compare commits
104 commits
2.17.0_(13
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
a4bf4cd3c5 | ||
|
61b0f724ba | ||
|
52945b04ff | ||
|
8e5bbdbd4b | ||
|
e6a01ac546 | ||
|
e8440f7d1d | ||
|
6cc212751b | ||
|
ae9d23d4a8 | ||
|
566254e617 | ||
|
88339d1c40 | ||
|
254bbcef57 | ||
|
e7aacfbec1 | ||
|
7a75a67701 | ||
|
7632d9ef87 | ||
|
eded494024 | ||
|
e5979edf63 | ||
|
f27b17aad0 | ||
|
fd4daba2aa | ||
|
ce7a8e8cc5 | ||
|
4282792ebd | ||
|
4766d1907f | ||
|
7f6f686b2b | ||
|
02b659c1bc | ||
|
db6e63c4aa | ||
|
a666d109d9 | ||
|
1f23f1f3ca | ||
|
51b8a6b610 | ||
|
28034d4b74 | ||
|
10ff5183f1 | ||
|
47b5313bf3 | ||
|
791400f565 | ||
|
06c9f7c771 | ||
|
2a0db84959 | ||
|
74cade6553 | ||
|
1b5f258c96 | ||
|
aa511f8c42 | ||
|
c28d2440b1 | ||
|
9d4c002813 | ||
|
d7392e4b8d | ||
|
4dc54794bd | ||
|
a36335ef92 | ||
|
9096367843 | ||
|
0d0321a5ab | ||
|
d903da0051 | ||
|
b65dc35cb7 | ||
|
7bb7ad40c4 | ||
|
7579e2d580 | ||
|
6eb1d73ca4 | ||
|
3055c3582b | ||
|
f9f7e8c2c9 | ||
|
fc2305266f | ||
|
6df7d89867 | ||
|
67bc6a1716 | ||
|
391d1da1ff | ||
|
a171eda41c | ||
|
6e0f437c6b | ||
|
2f1e8d38a8 | ||
|
c0f7449a95 | ||
|
b223076dae | ||
|
9eb200f5da | ||
|
f9149056fd | ||
|
29af26f118 | ||
|
f7c3ba0374 | ||
|
715ca0ab3f | ||
|
ffcc0c2da0 | ||
|
8761652eaa | ||
|
b7d4680f9c | ||
|
d6b16b230c | ||
|
7d1845f806 | ||
|
bd08c98849 | ||
|
7991f29707 | ||
|
be3e76eafc | ||
|
5bf2db8a52 | ||
|
0eae951eed | ||
|
f8f18be723 | ||
|
b685010a03 | ||
|
a8cbed5ca0 | ||
|
cce54c8ba5 | ||
|
4f903d8e4c | ||
|
9a747dd2fb | ||
|
27b0c3a3a0 | ||
|
b981f4a5cb | ||
|
d34d881722 | ||
|
ad6e75b6fb | ||
|
4dd77a39d8 | ||
|
8801428167 | ||
|
1dd23906c3 | ||
|
c5d2892ec2 | ||
|
1c5f0e46f5 | ||
|
5f5d79147f | ||
|
38d2955a8c | ||
|
212e57149f | ||
|
c4ee498d62 | ||
|
9295321cda | ||
|
1e84fbfcb0 | ||
|
2326470578 | ||
|
3c397d208f | ||
|
e4298cc062 | ||
|
8b7b85fd61 | ||
|
a88dc21974 | ||
|
5e72d5579a | ||
|
f838302720 | ||
|
18ee68d684 | ||
|
d3b6833e15 |
148 changed files with 2546 additions and 1974 deletions
123
.github/workflows/release-beta.yaml
vendored
123
.github/workflows/release-beta.yaml
vendored
|
@ -1,123 +0,0 @@
|
||||||
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 }}
|
|
302
.github/workflows/release-stable.yaml
vendored
302
.github/workflows/release-stable.yaml
vendored
|
@ -1,302 +0,0 @@
|
||||||
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/<REPLACE_VERSION_NUMBER_ACTIONS>/${{ 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 build/linux/x64/release/debian/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 '<REPLACE_VERSION_ACTIONS>', '${{ 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 }}
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -5,9 +5,11 @@
|
||||||
*.swp
|
*.swp
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.atom/
|
.atom/
|
||||||
|
.build/
|
||||||
.buildlog/
|
.buildlog/
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
|
|
||||||
# Env
|
# Env
|
||||||
|
@ -48,4 +50,6 @@ app.*.map.json
|
||||||
|
|
||||||
/debian/packages
|
/debian/packages
|
||||||
|
|
||||||
untranslated.json
|
untranslated.json
|
||||||
|
|
||||||
|
android/app/.cxx
|
||||||
|
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
|
@ -12,5 +12,9 @@
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.formatOnPaste": false,
|
"editor.formatOnPaste": false,
|
||||||
"editor.formatOnType": false
|
"editor.formatOnType": false
|
||||||
}
|
},
|
||||||
|
"cSpell.ignorePaths": [
|
||||||
|
"/pubspec.yaml",
|
||||||
|
"/.github/workflows"
|
||||||
|
],
|
||||||
}
|
}
|
15
README.md
15
README.md
|
@ -40,7 +40,16 @@ 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.
|
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
|
## Recommended configuration and lists
|
||||||
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).
|
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.
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://www.paypal.com/donate/?hosted_button_id=T63UK6AVL3MG8">
|
||||||
|
<img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" alt="Donate with PayPal" height="100" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Generate production build
|
## Generate production build
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -104,7 +113,6 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
|
||||||
- [expandable](https://pub.dev/packages/expandable)
|
- [expandable](https://pub.dev/packages/expandable)
|
||||||
- [package info plus](https://pub.dev/packages/package_info_plus)
|
- [package info plus](https://pub.dev/packages/package_info_plus)
|
||||||
- [flutter phoenix](https://pub.dev/packages/flutter_phoenix)
|
- [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 launcher icons](https://pub.dev/packages/flutter_launcher_icons)
|
||||||
- [flutter native splash](https://pub.dev/packages/flutter_native_splash)
|
- [flutter native splash](https://pub.dev/packages/flutter_native_splash)
|
||||||
- [intl](https://pub.dev/packages/intl)
|
- [intl](https://pub.dev/packages/intl)
|
||||||
|
@ -114,7 +122,6 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
|
||||||
- [fl chart](https://pub.dev/packages/fl_chart)
|
- [fl chart](https://pub.dev/packages/fl_chart)
|
||||||
- [flutter svg](https://pub.dev/packages/flutter_svg)
|
- [flutter svg](https://pub.dev/packages/flutter_svg)
|
||||||
- [percent indicator](https://pub.dev/packages/percent_indicator)
|
- [percent indicator](https://pub.dev/packages/percent_indicator)
|
||||||
- [store checker](https://pub.dev/packages/store_checker)
|
|
||||||
- [flutter markdown](https://pub.dev/packages/flutter_markdown)
|
- [flutter markdown](https://pub.dev/packages/flutter_markdown)
|
||||||
- [markdown](https://pub.dev/packages/markdown)
|
- [markdown](https://pub.dev/packages/markdown)
|
||||||
- [html](https://pub.dev/packages/html)
|
- [html](https://pub.dev/packages/html)
|
||||||
|
@ -132,6 +139,8 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
|
||||||
- [timezone](https://pub.dev/packages/timezone)
|
- [timezone](https://pub.dev/packages/timezone)
|
||||||
- [url launcher](https://pub.dev/packages/url_launcher)
|
- [url launcher](https://pub.dev/packages/url_launcher)
|
||||||
- [flutter custom tabs](https://pub.dev/packages/flutter_custom_tabs)
|
- [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)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,9 @@ if (keystorePropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 34
|
namespace "com.jgeek00.adguard_home_manager"
|
||||||
ndkVersion flutter.ndkVersion
|
compileSdkVersion 35
|
||||||
|
ndkVersion "26.1.10909125"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
@ -46,12 +47,9 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId "com.jgeek00.adguard_home_manager"
|
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
|
minSdkVersion 26
|
||||||
targetSdkVersion 34
|
targetSdkVersion 35
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ pluginManagement {
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version '7.4.2' apply false
|
id "com.android.application" version '8.6.0' apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.8.20" apply false
|
id "org.jetbrains.kotlin.android" version "1.8.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
debian/debian.yaml
vendored
2
debian/debian.yaml
vendored
|
@ -5,7 +5,7 @@ flutter_app:
|
||||||
|
|
||||||
control:
|
control:
|
||||||
Package: AdGuardHomeManager
|
Package: AdGuardHomeManager
|
||||||
Version: <REPLACE_VERSION_NUMBER_ACTIONS>
|
Version: 2.20.1
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
Essential: no
|
Essential: no
|
||||||
Priority: optional
|
Priority: optional
|
||||||
|
|
|
@ -15,6 +15,24 @@ ThemeData lightTheme(ColorScheme? dynamicColorScheme) => ThemeData(
|
||||||
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1),
|
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1),
|
||||||
iconColor: 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(
|
ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData(
|
||||||
|
@ -33,6 +51,24 @@ ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData(
|
||||||
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1),
|
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1),
|
||||||
iconColor: 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(
|
ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
||||||
|
@ -51,6 +87,12 @@ ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
||||||
iconColor: Color.fromRGBO(117, 117, 117, 1),
|
iconColor: Color.fromRGBO(117, 117, 117, 1),
|
||||||
),
|
),
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
|
// DISABLE PREDICTIVE BACK GESTURE
|
||||||
|
// pageTransitionsTheme: const PageTransitionsTheme(
|
||||||
|
// builders: {
|
||||||
|
// TargetPlatform.android: PredictiveBackPageTransitionsBuilder()
|
||||||
|
// }
|
||||||
|
// )
|
||||||
);
|
);
|
||||||
|
|
||||||
ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
||||||
|
@ -72,4 +114,10 @@ ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
||||||
iconColor: Color.fromRGBO(187, 187, 187, 1),
|
iconColor: Color.fromRGBO(187, 187, 187, 1),
|
||||||
),
|
),
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
|
// DISABLE PREDICTIVE BACK GESTURE
|
||||||
|
// pageTransitionsTheme: const PageTransitionsTheme(
|
||||||
|
// builders: {
|
||||||
|
// TargetPlatform.android: PredictiveBackPageTransitionsBuilder()
|
||||||
|
// }
|
||||||
|
// )
|
||||||
);
|
);
|
|
@ -1,2 +1,3 @@
|
||||||
enum LoadStatus { loading, loaded, error }
|
enum LoadStatus { loading, loaded, error }
|
||||||
enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime }
|
enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime }
|
||||||
|
enum CustomRulesSorting { topBottom, bottomTop }
|
12
lib/constants/regexps.dart
Normal file
12
lib/constants/regexps.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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]+))?$');
|
||||||
|
}
|
|
@ -7,4 +7,6 @@ class Urls {
|
||||||
static const String adGuardHomeReleasesTags = "https://api.github.com/repos/AdGuardTeam/AdGuardHome/releases/tags";
|
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 googleSearchUrl = "https://www.google.com/search";
|
||||||
static const String connectionInstructions = "https://github.com/JGeek00/adguard-home-manager/wiki/Create-a-connection";
|
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";
|
||||||
}
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
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<GitHubRelease?> 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<GitHubRelease>).firstWhere((r) => r.prerelease == true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
gitHubRelease = result.content as GitHubRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
final update = gitHubUpdateExists(
|
|
||||||
currentBuildNumber: currentBuildNumber,
|
|
||||||
gitHubRelease: gitHubRelease,
|
|
||||||
isBeta: isBeta
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
10
lib/functions/is_ip.dart
Normal file
10
lib/functions/is_ip.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,15 @@ import 'package:url_launcher/url_launcher.dart' as url_launcher;
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
void openUrl(String url) async {
|
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) {
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
try {
|
try {
|
||||||
await flutter_custom_tabs.launchUrl(
|
await flutter_custom_tabs.launchUrl(
|
||||||
|
@ -20,6 +29,7 @@ void openUrl(String url) async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
url_launcher.launchUrl(Uri.parse(url));
|
||||||
Sentry.captureException(e, stackTrace: stackTrace);
|
Sentry.captureException(e, stackTrace: stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
import 'package:adguard_home_manager/config/globals.dart';
|
import 'package:adguard_home_manager/config/globals.dart';
|
||||||
|
|
||||||
void showSnacbkar({
|
void showSnackbar({
|
||||||
required AppConfigProvider appConfigProvider,
|
required AppConfigProvider appConfigProvider,
|
||||||
required String label,
|
required String label,
|
||||||
required Color color,
|
required Color color,
|
||||||
|
|
|
@ -791,5 +791,20 @@
|
||||||
"clientDisallowedSuccessfully": "Client disallowed successfully",
|
"clientDisallowedSuccessfully": "Client disallowed successfully",
|
||||||
"changesNotSaved": "Changes could not be saved",
|
"changesNotSaved": "Changes could not be saved",
|
||||||
"allowingClient": "Allowing client...",
|
"allowingClient": "Allowing client...",
|
||||||
"disallowingClient": "Disallowing 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"
|
||||||
}
|
}
|
|
@ -403,7 +403,7 @@
|
||||||
"dnsRewriteRuleDeleted": "Reescritura DNS eliminada correctamente",
|
"dnsRewriteRuleDeleted": "Reescritura DNS eliminada correctamente",
|
||||||
"dnsRewriteRuleNotDeleted": "La reescritura DNS no pudo ser eliminada",
|
"dnsRewriteRuleNotDeleted": "La reescritura DNS no pudo ser eliminada",
|
||||||
"addDnsRewrite": "Añadir reescritura DNS",
|
"addDnsRewrite": "Añadir reescritura DNS",
|
||||||
"addingRewrite": "Añadiend reescritura...",
|
"addingRewrite": "Añadiendo reescritura...",
|
||||||
"dnsRewriteRuleAdded": "Regla de reescritura DNS añadida correctamente",
|
"dnsRewriteRuleAdded": "Regla de reescritura DNS añadida correctamente",
|
||||||
"dnsRewriteRuleNotAdded": "La regla de reescritura DNS no ha podido ser añadida",
|
"dnsRewriteRuleNotAdded": "La regla de reescritura DNS no ha podido ser añadida",
|
||||||
"logsSettings": "Ajustes de registros",
|
"logsSettings": "Ajustes de registros",
|
||||||
|
@ -791,5 +791,20 @@
|
||||||
"clientDisallowedSuccessfully": "Cliente no permitido correctamente",
|
"clientDisallowedSuccessfully": "Cliente no permitido correctamente",
|
||||||
"changesNotSaved": "Los cambios no han podido ser guardados",
|
"changesNotSaved": "Los cambios no han podido ser guardados",
|
||||||
"allowingClient": "Permitiendo cliente...",
|
"allowingClient": "Permitiendo cliente...",
|
||||||
"disallowingClient": "No 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"
|
||||||
}
|
}
|
|
@ -779,5 +779,32 @@
|
||||||
"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.",
|
"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",
|
"date": "Tarih",
|
||||||
"loadingChangelog": "Değişiklikler yükleniyor...",
|
"loadingChangelog": "Değişiklikler yükleniyor...",
|
||||||
"invalidIpOrUrl": "Geçersiz IP adresi veya URL"
|
"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"
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"invalidUsernamePassword": "用户名或密码错误",
|
"invalidUsernamePassword": "用户名或密码错误",
|
||||||
"tooManyAttempts": "尝试次数过多,请稍后再试",
|
"tooManyAttempts": "尝试次数过多,请稍后再试",
|
||||||
"cantReachServer": "无法连接服务器,请检查连接信息是否正确",
|
"cantReachServer": "无法连接服务器,请检查连接信息是否正确",
|
||||||
"sslError": "SSL 错误。转到 设置 > 高级设置 并启用 不检查 SSL 证书",
|
"sslError": "SSL 错误 转到 设置 > 高级设置 并启用 不检查 SSL 证书",
|
||||||
"unknownError": "未知错误",
|
"unknownError": "未知错误",
|
||||||
"connectionNotCreated": "连接无法创建",
|
"connectionNotCreated": "连接无法创建",
|
||||||
"connecting": "正在连接...",
|
"connecting": "正在连接...",
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
"notSelected": "未选择",
|
"notSelected": "未选择",
|
||||||
"resetFilters": "重置过滤器",
|
"resetFilters": "重置过滤器",
|
||||||
"noLogsDisplay": "无日志可显示",
|
"noLogsDisplay": "无日志可显示",
|
||||||
"noLogsThatOld": "选择的时间段可能没有日志。尝试选择近期时间。",
|
"noLogsThatOld": "选择的时间段可能没有日志 请尝试选择近期时间 ",
|
||||||
"apply": "应用",
|
"apply": "应用",
|
||||||
"selectAll": "全选",
|
"selectAll": "全选",
|
||||||
"unselectAll": "取消全选",
|
"unselectAll": "取消全选",
|
||||||
|
@ -235,7 +235,7 @@
|
||||||
"urlNotValid": "URL 无效",
|
"urlNotValid": "URL 无效",
|
||||||
"urlAbsolutePath": "URL 或绝对路径",
|
"urlAbsolutePath": "URL 或绝对路径",
|
||||||
"addingList": "正在添加订阅规则...",
|
"addingList": "正在添加订阅规则...",
|
||||||
"listAdded": "订阅规则添加成功 。已添加项目:",
|
"listAdded": "订阅规则添加成功 已添加项目:",
|
||||||
"listAlreadyAdded": "订阅规则已被添加",
|
"listAlreadyAdded": "订阅规则已被添加",
|
||||||
"listUrlInvalid": "订阅规则 URL 无效",
|
"listUrlInvalid": "订阅规则 URL 无效",
|
||||||
"listNotAdded": "无法添加订阅规则",
|
"listNotAdded": "无法添加订阅规则",
|
||||||
|
@ -282,7 +282,7 @@
|
||||||
"clientsNotLoaded": "无法加载客户端",
|
"clientsNotLoaded": "无法加载客户端",
|
||||||
"noAllowedClients": "没有已允许的客户端",
|
"noAllowedClients": "没有已允许的客户端",
|
||||||
"allowedClientsDescription": "如果此列表中有条目,AdGuard Home 将仅接受来自这些客户端的请求",
|
"allowedClientsDescription": "如果此列表中有条目,AdGuard Home 将仅接受来自这些客户端的请求",
|
||||||
"blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求。如果已允许客户端中有条目,则会忽略此字段",
|
"blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求 如果已允许客户端中有条目,则会忽略此字段",
|
||||||
"disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中",
|
"disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中",
|
||||||
"addClientFieldDescription": "CIDR、IP 地址或客户端 ID",
|
"addClientFieldDescription": "CIDR、IP 地址或客户端 ID",
|
||||||
"clientIdentifier": "客户端标识符",
|
"clientIdentifier": "客户端标识符",
|
||||||
|
@ -358,7 +358,7 @@
|
||||||
"seconds": "{time} 秒",
|
"seconds": "{time} 秒",
|
||||||
"leaseTimeNotValid": "租期无效",
|
"leaseTimeNotValid": "租期无效",
|
||||||
"restoreConfiguration": "重置配置",
|
"restoreConfiguration": "重置配置",
|
||||||
"restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置。此操作无法撤消",
|
"restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置 此操作无法撤消",
|
||||||
"changeInterface": "更改接口",
|
"changeInterface": "更改接口",
|
||||||
"savingSettings": "正在保存设置...",
|
"savingSettings": "正在保存设置...",
|
||||||
"settingsSaved": "设置保存成功",
|
"settingsSaved": "设置保存成功",
|
||||||
|
@ -372,7 +372,7 @@
|
||||||
"staticLeaseDeleted": "DHCP 静态租用删除成功",
|
"staticLeaseDeleted": "DHCP 静态租用删除成功",
|
||||||
"staticLeaseNotDeleted": "无法删除 DHCP 静态租用",
|
"staticLeaseNotDeleted": "无法删除 DHCP 静态租用",
|
||||||
"deleteStaticLease": "删除静态租用",
|
"deleteStaticLease": "删除静态租用",
|
||||||
"deleteStaticLeaseDescription": "DHCP 静态租用将被删除。此操作无法撤消",
|
"deleteStaticLeaseDescription": "DHCP 静态租用将被删除 此操作无法撤消",
|
||||||
"addStaticLease": "添加静态租用",
|
"addStaticLease": "添加静态租用",
|
||||||
"macAddress": "MAC 地址",
|
"macAddress": "MAC 地址",
|
||||||
"macAddressNotValid": "MAC 地址无效",
|
"macAddressNotValid": "MAC 地址无效",
|
||||||
|
@ -384,7 +384,7 @@
|
||||||
"staticLeaseExists": "DHCP 静态租用已存在",
|
"staticLeaseExists": "DHCP 静态租用已存在",
|
||||||
"serverNotConfigured": "未配置服务器",
|
"serverNotConfigured": "未配置服务器",
|
||||||
"restoreLeases": "重置租用",
|
"restoreLeases": "重置租用",
|
||||||
"restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用。此操作无法撤消",
|
"restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用 此操作无法撤消",
|
||||||
"restoringLeases": "正在重置租用...",
|
"restoringLeases": "正在重置租用...",
|
||||||
"leasesRestored": "租用重置成功",
|
"leasesRestored": "租用重置成功",
|
||||||
"leasesNotRestored": "无法重置租用",
|
"leasesNotRestored": "无法重置租用",
|
||||||
|
@ -427,24 +427,24 @@
|
||||||
"dnsSettingsDescription": "配置与 DNS 服务器的连接",
|
"dnsSettingsDescription": "配置与 DNS 服务器的连接",
|
||||||
"upstreamDns": "上游 DNS 服务器",
|
"upstreamDns": "上游 DNS 服务器",
|
||||||
"bootstrapDns": "引导 DNS 服务器",
|
"bootstrapDns": "引导 DNS 服务器",
|
||||||
"noUpstreamDns": "未添加上游 DNS 服务器。",
|
"noUpstreamDns": "未添加上游 DNS 服务器 ",
|
||||||
"dnsMode": "DNS 模式",
|
"dnsMode": "DNS 模式",
|
||||||
"noDnsMode": "未选择 DNS 模式",
|
"noDnsMode": "未选择 DNS 模式",
|
||||||
"loadBalancing": "负载均衡",
|
"loadBalancing": "负载均衡",
|
||||||
"parallelRequests": "并行请求",
|
"parallelRequests": "并行请求",
|
||||||
"fastestIpAddress": "最快的 IP 地址",
|
"fastestIpAddress": "最快的 IP 地址",
|
||||||
"loadBalancingDescription": "每次查询一个上游服务器。AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器",
|
"loadBalancingDescription": "每次查询一个上游服务器 AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器",
|
||||||
"parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器",
|
"parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器",
|
||||||
"fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址。这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性",
|
"fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址 这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性",
|
||||||
"noBootstrapDns": "未添加引导 DNS 服务器。",
|
"noBootstrapDns": "未添加引导 DNS 服务器 ",
|
||||||
"bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址。",
|
"bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址 ",
|
||||||
"privateReverseDnsServers": "私有反向 DNS 服务器",
|
"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 解析器",
|
"reverseDnsDefault": "默认情况下,AdGuard Home 使用以下默认反向 DNS 解析器",
|
||||||
"addItem": "添加项目",
|
"addItem": "添加项目",
|
||||||
"noServerAddressesAdded": "未添加服务器地址",
|
"noServerAddressesAdded": "未添加服务器地址",
|
||||||
"usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器",
|
"usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器",
|
||||||
"usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询。如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN",
|
"usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询 如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN",
|
||||||
"enableReverseResolving": "启用客户端 IP 地址的反向解析",
|
"enableReverseResolving": "启用客户端 IP 地址的反向解析",
|
||||||
"enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)",
|
"enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)",
|
||||||
"dnsServerSettings": "AdGuard Home DNS 服务器设置",
|
"dnsServerSettings": "AdGuard Home DNS 服务器设置",
|
||||||
|
@ -492,7 +492,7 @@
|
||||||
"dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存",
|
"dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存",
|
||||||
"comment": "注释",
|
"comment": "注释",
|
||||||
"address": "地址",
|
"address": "地址",
|
||||||
"commentsDescription": "注释始终以 # 开头。您无需添加它,系统将自动添加",
|
"commentsDescription": "注释始终以 # 开头 您无需添加它,系统将自动添加",
|
||||||
"encryptionSettings": "加密设置",
|
"encryptionSettings": "加密设置",
|
||||||
"encryptionSettingsDescription": "加密(HTTPS/QUIC/TLS)支持",
|
"encryptionSettingsDescription": "加密(HTTPS/QUIC/TLS)支持",
|
||||||
"loadingEncryptionSettings": "正在加载加密设置...",
|
"loadingEncryptionSettings": "正在加载加密设置...",
|
||||||
|
@ -502,13 +502,13 @@
|
||||||
"enableEncryptionDescription": "如果启用加密,AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求",
|
"enableEncryptionDescription": "如果启用加密,AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求",
|
||||||
"serverConfiguration": "服务器配置",
|
"serverConfiguration": "服务器配置",
|
||||||
"domainName": "域名",
|
"domainName": "域名",
|
||||||
"domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证。如果未设置,这些功能将被禁用。必须与证书中的 DNS 名称之一匹配",
|
"domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证 如果未设置,这些功能将被禁用 必须与证书中的 DNS 名称之一匹配",
|
||||||
"redirectHttps": "自动重定向到 HTTPS",
|
"redirectHttps": "自动重定向到 HTTPS",
|
||||||
"httpsPort": "HTTPS 端口",
|
"httpsPort": "HTTPS 端口",
|
||||||
"tlsPort": "DNS-over-TLS 端口",
|
"tlsPort": "DNS-over-TLS 端口",
|
||||||
"dnsOverQuicPort": "DNS-over-QUIC 端口",
|
"dnsOverQuicPort": "DNS-over-QUIC 端口",
|
||||||
"certificates": "证书",
|
"certificates": "证书",
|
||||||
"certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链。您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买",
|
"certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链 您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买",
|
||||||
"certificateFilePath": "设置证书文件路径",
|
"certificateFilePath": "设置证书文件路径",
|
||||||
"pasteCertificateContent": "粘贴证书内容",
|
"pasteCertificateContent": "粘贴证书内容",
|
||||||
"certificatePath": "证书路径",
|
"certificatePath": "证书路径",
|
||||||
|
@ -526,13 +526,13 @@
|
||||||
"encryptionConfigSaved": "加密配置已成功保存",
|
"encryptionConfigSaved": "加密配置已成功保存",
|
||||||
"encryptionConfigNotSaved": "无法保存加密配置",
|
"encryptionConfigNotSaved": "无法保存加密配置",
|
||||||
"configError": "配置错误",
|
"configError": "配置错误",
|
||||||
"enterOnlyCertificate": "只输入证书。不要输入 ---BEGIN--- 和 ---END--- 行。",
|
"enterOnlyCertificate": "只输入证书 不要输入 ---BEGIN--- 和 ---END--- 行 ",
|
||||||
"enterOnlyPrivateKey": "只输入密钥。不要输入 ---BEGIN--- 和 ---END--- 行。",
|
"enterOnlyPrivateKey": "只输入密钥 不要输入 ---BEGIN--- 和 ---END--- 行 ",
|
||||||
"noItemsSearch": "没有匹配的项目",
|
"noItemsSearch": "没有匹配的项目",
|
||||||
"clearSearch": "清除搜索",
|
"clearSearch": "清除搜索",
|
||||||
"exitSearch": "退出搜索",
|
"exitSearch": "退出搜索",
|
||||||
"searchClients": "搜索客户端",
|
"searchClients": "搜索客户端",
|
||||||
"noClientsSearch": "没有匹配的客户端。",
|
"noClientsSearch": "没有匹配的客户端 ",
|
||||||
"customization": "定制",
|
"customization": "定制",
|
||||||
"customizationDescription": "自定义此应用程序",
|
"customizationDescription": "自定义此应用程序",
|
||||||
"color": "颜色",
|
"color": "颜色",
|
||||||
|
@ -598,7 +598,7 @@
|
||||||
"checkUpdates": "检查更新",
|
"checkUpdates": "检查更新",
|
||||||
"requestingUpdate": "正在请求更新...",
|
"requestingUpdate": "正在请求更新...",
|
||||||
"autoupdateUnavailable": "自动更新不可用",
|
"autoupdateUnavailable": "自动更新不可用",
|
||||||
"autoupdateUnavailableDescription": "此服务器不支持自动更新服务。这可能是因为服务器正在 Docker 容器中运行。您需要手动更新服务器",
|
"autoupdateUnavailableDescription": "此服务器不支持自动更新服务 这可能是因为服务器正在 Docker 容器中运行 您需要手动更新服务器",
|
||||||
"minute": "{time} 分钟",
|
"minute": "{time} 分钟",
|
||||||
"minutes": "{time} 分钟",
|
"minutes": "{time} 分钟",
|
||||||
"hour": "{time} 小时",
|
"hour": "{time} 小时",
|
||||||
|
@ -606,7 +606,7 @@
|
||||||
"remainingTime": "剩余时间",
|
"remainingTime": "剩余时间",
|
||||||
"safeSearchSettings": "安全搜索设置",
|
"safeSearchSettings": "安全搜索设置",
|
||||||
"loadingSafeSearchSettings": "正在加载安全搜索设置...",
|
"loadingSafeSearchSettings": "正在加载安全搜索设置...",
|
||||||
"safeSearchSettingsNotLoaded": "加载安全搜索设置时出错。",
|
"safeSearchSettingsNotLoaded": "加载安全搜索设置时出错 ",
|
||||||
"loadingLogsSettings": "正在加载日志设置...",
|
"loadingLogsSettings": "正在加载日志设置...",
|
||||||
"selectOptionLeftColumn": "在左侧栏中选择一个选项",
|
"selectOptionLeftColumn": "在左侧栏中选择一个选项",
|
||||||
"selectClientLeftColumn": "在左侧栏中选择一个客户端",
|
"selectClientLeftColumn": "在左侧栏中选择一个客户端",
|
||||||
|
@ -619,7 +619,7 @@
|
||||||
"copyListUrl": "复制订阅规则 URL",
|
"copyListUrl": "复制订阅规则 URL",
|
||||||
"listUrlCopied": "订阅规则 URL 已复制到剪贴板",
|
"listUrlCopied": "订阅规则 URL 已复制到剪贴板",
|
||||||
"unsupportedVersion": "不支持的版本",
|
"unsupportedVersion": "不支持的版本",
|
||||||
"unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本。alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题",
|
"unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本 alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题",
|
||||||
"iUnderstand": "我了解",
|
"iUnderstand": "我了解",
|
||||||
"appUpdates": "应用程序更新",
|
"appUpdates": "应用程序更新",
|
||||||
"usingLatestVersion": "您正在使用最新版本",
|
"usingLatestVersion": "您正在使用最新版本",
|
||||||
|
@ -658,7 +658,136 @@
|
||||||
"hideServerAddressDescription": "在主页上隐藏服务器地址",
|
"hideServerAddressDescription": "在主页上隐藏服务器地址",
|
||||||
"topItemsOrder": "顶部项目顺序",
|
"topItemsOrder": "顶部项目顺序",
|
||||||
"topItemsOrderDescription": "排列主页顶部项目列表",
|
"topItemsOrderDescription": "排列主页顶部项目列表",
|
||||||
"topItemsReorderInfo": "按住并滑动一个项目以重新排序。",
|
"topItemsReorderInfo": "按住并滑动一个项目以重新排序 ",
|
||||||
"discardChanges": "放弃更改",
|
"discardChanges": "放弃更改",
|
||||||
"discardChangesDescription": "您确定要放弃更改吗?"
|
"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": "正在禁止客户端..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"invalidUsernamePassword": "用户名或密码错误",
|
"invalidUsernamePassword": "用户名或密码错误",
|
||||||
"tooManyAttempts": "尝试次数过多,请稍后再试",
|
"tooManyAttempts": "尝试次数过多,请稍后再试",
|
||||||
"cantReachServer": "无法连接服务器,请检查连接信息是否正确",
|
"cantReachServer": "无法连接服务器,请检查连接信息是否正确",
|
||||||
"sslError": "SSL 错误。转到 设置 > 高级设置 并启用 不检查 SSL 证书",
|
"sslError": "SSL 错误 转到 设置 > 高级设置 并启用 不检查 SSL 证书",
|
||||||
"unknownError": "未知错误",
|
"unknownError": "未知错误",
|
||||||
"connectionNotCreated": "连接无法创建",
|
"connectionNotCreated": "连接无法创建",
|
||||||
"connecting": "正在连接...",
|
"connecting": "正在连接...",
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
"notSelected": "未选择",
|
"notSelected": "未选择",
|
||||||
"resetFilters": "重置过滤器",
|
"resetFilters": "重置过滤器",
|
||||||
"noLogsDisplay": "无日志可显示",
|
"noLogsDisplay": "无日志可显示",
|
||||||
"noLogsThatOld": "选择的时间段可能没有日志。尝试选择近期时间。",
|
"noLogsThatOld": "选择的时间段可能没有日志 请尝试选择近期时间 ",
|
||||||
"apply": "应用",
|
"apply": "应用",
|
||||||
"selectAll": "全选",
|
"selectAll": "全选",
|
||||||
"unselectAll": "取消全选",
|
"unselectAll": "取消全选",
|
||||||
|
@ -235,7 +235,7 @@
|
||||||
"urlNotValid": "URL 无效",
|
"urlNotValid": "URL 无效",
|
||||||
"urlAbsolutePath": "URL 或绝对路径",
|
"urlAbsolutePath": "URL 或绝对路径",
|
||||||
"addingList": "正在添加订阅规则...",
|
"addingList": "正在添加订阅规则...",
|
||||||
"listAdded": "订阅规则添加成功 。已添加项目:",
|
"listAdded": "订阅规则添加成功 已添加项目:",
|
||||||
"listAlreadyAdded": "订阅规则已被添加",
|
"listAlreadyAdded": "订阅规则已被添加",
|
||||||
"listUrlInvalid": "订阅规则 URL 无效",
|
"listUrlInvalid": "订阅规则 URL 无效",
|
||||||
"listNotAdded": "无法添加订阅规则",
|
"listNotAdded": "无法添加订阅规则",
|
||||||
|
@ -282,7 +282,7 @@
|
||||||
"clientsNotLoaded": "无法加载客户端",
|
"clientsNotLoaded": "无法加载客户端",
|
||||||
"noAllowedClients": "没有已允许的客户端",
|
"noAllowedClients": "没有已允许的客户端",
|
||||||
"allowedClientsDescription": "如果此列表中有条目,AdGuard Home 将仅接受来自这些客户端的请求",
|
"allowedClientsDescription": "如果此列表中有条目,AdGuard Home 将仅接受来自这些客户端的请求",
|
||||||
"blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求。如果已允许客户端中有条目,则会忽略此字段",
|
"blockedClientsDescription": "如果此列表中有条目,AdGuard Home 将拒绝来自这些客户端的请求 如果已允许客户端中有条目,则会忽略此字段",
|
||||||
"disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中",
|
"disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中",
|
||||||
"addClientFieldDescription": "CIDR、IP 地址或客户端 ID",
|
"addClientFieldDescription": "CIDR、IP 地址或客户端 ID",
|
||||||
"clientIdentifier": "客户端标识符",
|
"clientIdentifier": "客户端标识符",
|
||||||
|
@ -358,7 +358,7 @@
|
||||||
"seconds": "{time} 秒",
|
"seconds": "{time} 秒",
|
||||||
"leaseTimeNotValid": "租期无效",
|
"leaseTimeNotValid": "租期无效",
|
||||||
"restoreConfiguration": "重置配置",
|
"restoreConfiguration": "重置配置",
|
||||||
"restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置。此操作无法撤消",
|
"restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置 此操作无法撤消",
|
||||||
"changeInterface": "更改接口",
|
"changeInterface": "更改接口",
|
||||||
"savingSettings": "正在保存设置...",
|
"savingSettings": "正在保存设置...",
|
||||||
"settingsSaved": "设置保存成功",
|
"settingsSaved": "设置保存成功",
|
||||||
|
@ -372,7 +372,7 @@
|
||||||
"staticLeaseDeleted": "DHCP 静态租用删除成功",
|
"staticLeaseDeleted": "DHCP 静态租用删除成功",
|
||||||
"staticLeaseNotDeleted": "无法删除 DHCP 静态租用",
|
"staticLeaseNotDeleted": "无法删除 DHCP 静态租用",
|
||||||
"deleteStaticLease": "删除静态租用",
|
"deleteStaticLease": "删除静态租用",
|
||||||
"deleteStaticLeaseDescription": "DHCP 静态租用将被删除。此操作无法撤消",
|
"deleteStaticLeaseDescription": "DHCP 静态租用将被删除 此操作无法撤消",
|
||||||
"addStaticLease": "添加静态租用",
|
"addStaticLease": "添加静态租用",
|
||||||
"macAddress": "MAC 地址",
|
"macAddress": "MAC 地址",
|
||||||
"macAddressNotValid": "MAC 地址无效",
|
"macAddressNotValid": "MAC 地址无效",
|
||||||
|
@ -384,7 +384,7 @@
|
||||||
"staticLeaseExists": "DHCP 静态租用已存在",
|
"staticLeaseExists": "DHCP 静态租用已存在",
|
||||||
"serverNotConfigured": "未配置服务器",
|
"serverNotConfigured": "未配置服务器",
|
||||||
"restoreLeases": "重置租用",
|
"restoreLeases": "重置租用",
|
||||||
"restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用。此操作无法撤消",
|
"restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用 此操作无法撤消",
|
||||||
"restoringLeases": "正在重置租用...",
|
"restoringLeases": "正在重置租用...",
|
||||||
"leasesRestored": "租用重置成功",
|
"leasesRestored": "租用重置成功",
|
||||||
"leasesNotRestored": "无法重置租用",
|
"leasesNotRestored": "无法重置租用",
|
||||||
|
@ -427,24 +427,24 @@
|
||||||
"dnsSettingsDescription": "配置与 DNS 服务器的连接",
|
"dnsSettingsDescription": "配置与 DNS 服务器的连接",
|
||||||
"upstreamDns": "上游 DNS 服务器",
|
"upstreamDns": "上游 DNS 服务器",
|
||||||
"bootstrapDns": "引导 DNS 服务器",
|
"bootstrapDns": "引导 DNS 服务器",
|
||||||
"noUpstreamDns": "未添加上游 DNS 服务器。",
|
"noUpstreamDns": "未添加上游 DNS 服务器 ",
|
||||||
"dnsMode": "DNS 模式",
|
"dnsMode": "DNS 模式",
|
||||||
"noDnsMode": "未选择 DNS 模式",
|
"noDnsMode": "未选择 DNS 模式",
|
||||||
"loadBalancing": "负载均衡",
|
"loadBalancing": "负载均衡",
|
||||||
"parallelRequests": "并行请求",
|
"parallelRequests": "并行请求",
|
||||||
"fastestIpAddress": "最快的 IP 地址",
|
"fastestIpAddress": "最快的 IP 地址",
|
||||||
"loadBalancingDescription": "每次查询一个上游服务器。AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器",
|
"loadBalancingDescription": "每次查询一个上游服务器 AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器",
|
||||||
"parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器",
|
"parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器",
|
||||||
"fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址。这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性",
|
"fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址 这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性",
|
||||||
"noBootstrapDns": "未添加引导 DNS 服务器。",
|
"noBootstrapDns": "未添加引导 DNS 服务器 ",
|
||||||
"bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址。",
|
"bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址 ",
|
||||||
"privateReverseDnsServers": "私有反向 DNS 服务器",
|
"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 解析器",
|
"reverseDnsDefault": "默认情况下,AdGuard Home 使用以下默认反向 DNS 解析器",
|
||||||
"addItem": "添加项目",
|
"addItem": "添加项目",
|
||||||
"noServerAddressesAdded": "未添加服务器地址",
|
"noServerAddressesAdded": "未添加服务器地址",
|
||||||
"usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器",
|
"usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器",
|
||||||
"usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询。如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN",
|
"usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询 如果禁用,AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN",
|
||||||
"enableReverseResolving": "启用客户端 IP 地址的反向解析",
|
"enableReverseResolving": "启用客户端 IP 地址的反向解析",
|
||||||
"enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)",
|
"enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)",
|
||||||
"dnsServerSettings": "AdGuard Home DNS 服务器设置",
|
"dnsServerSettings": "AdGuard Home DNS 服务器设置",
|
||||||
|
@ -492,7 +492,7 @@
|
||||||
"dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存",
|
"dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存",
|
||||||
"comment": "注释",
|
"comment": "注释",
|
||||||
"address": "地址",
|
"address": "地址",
|
||||||
"commentsDescription": "注释始终以 # 开头。您无需添加它,系统将自动添加",
|
"commentsDescription": "注释始终以 # 开头 您无需添加它,系统将自动添加",
|
||||||
"encryptionSettings": "加密设置",
|
"encryptionSettings": "加密设置",
|
||||||
"encryptionSettingsDescription": "加密(HTTPS/QUIC/TLS)支持",
|
"encryptionSettingsDescription": "加密(HTTPS/QUIC/TLS)支持",
|
||||||
"loadingEncryptionSettings": "正在加载加密设置...",
|
"loadingEncryptionSettings": "正在加载加密设置...",
|
||||||
|
@ -502,13 +502,13 @@
|
||||||
"enableEncryptionDescription": "如果启用加密,AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求",
|
"enableEncryptionDescription": "如果启用加密,AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求",
|
||||||
"serverConfiguration": "服务器配置",
|
"serverConfiguration": "服务器配置",
|
||||||
"domainName": "域名",
|
"domainName": "域名",
|
||||||
"domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证。如果未设置,这些功能将被禁用。必须与证书中的 DNS 名称之一匹配",
|
"domainNameDescription": "如果设置,AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证 如果未设置,这些功能将被禁用 必须与证书中的 DNS 名称之一匹配",
|
||||||
"redirectHttps": "自动重定向到 HTTPS",
|
"redirectHttps": "自动重定向到 HTTPS",
|
||||||
"httpsPort": "HTTPS 端口",
|
"httpsPort": "HTTPS 端口",
|
||||||
"tlsPort": "DNS-over-TLS 端口",
|
"tlsPort": "DNS-over-TLS 端口",
|
||||||
"dnsOverQuicPort": "DNS-over-QUIC 端口",
|
"dnsOverQuicPort": "DNS-over-QUIC 端口",
|
||||||
"certificates": "证书",
|
"certificates": "证书",
|
||||||
"certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链。您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买",
|
"certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链 您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买",
|
||||||
"certificateFilePath": "设置证书文件路径",
|
"certificateFilePath": "设置证书文件路径",
|
||||||
"pasteCertificateContent": "粘贴证书内容",
|
"pasteCertificateContent": "粘贴证书内容",
|
||||||
"certificatePath": "证书路径",
|
"certificatePath": "证书路径",
|
||||||
|
@ -526,13 +526,13 @@
|
||||||
"encryptionConfigSaved": "加密配置已成功保存",
|
"encryptionConfigSaved": "加密配置已成功保存",
|
||||||
"encryptionConfigNotSaved": "无法保存加密配置",
|
"encryptionConfigNotSaved": "无法保存加密配置",
|
||||||
"configError": "配置错误",
|
"configError": "配置错误",
|
||||||
"enterOnlyCertificate": "只输入证书。不要输入 ---BEGIN--- 和 ---END--- 行。",
|
"enterOnlyCertificate": "只输入证书 不要输入 ---BEGIN--- 和 ---END--- 行 ",
|
||||||
"enterOnlyPrivateKey": "只输入密钥。不要输入 ---BEGIN--- 和 ---END--- 行。",
|
"enterOnlyPrivateKey": "只输入密钥 不要输入 ---BEGIN--- 和 ---END--- 行 ",
|
||||||
"noItemsSearch": "没有匹配的项目",
|
"noItemsSearch": "没有匹配的项目",
|
||||||
"clearSearch": "清除搜索",
|
"clearSearch": "清除搜索",
|
||||||
"exitSearch": "退出搜索",
|
"exitSearch": "退出搜索",
|
||||||
"searchClients": "搜索客户端",
|
"searchClients": "搜索客户端",
|
||||||
"noClientsSearch": "没有匹配的客户端。",
|
"noClientsSearch": "没有匹配的客户端 ",
|
||||||
"customization": "定制",
|
"customization": "定制",
|
||||||
"customizationDescription": "自定义此应用程序",
|
"customizationDescription": "自定义此应用程序",
|
||||||
"color": "颜色",
|
"color": "颜色",
|
||||||
|
@ -598,7 +598,7 @@
|
||||||
"checkUpdates": "检查更新",
|
"checkUpdates": "检查更新",
|
||||||
"requestingUpdate": "正在请求更新...",
|
"requestingUpdate": "正在请求更新...",
|
||||||
"autoupdateUnavailable": "自动更新不可用",
|
"autoupdateUnavailable": "自动更新不可用",
|
||||||
"autoupdateUnavailableDescription": "此服务器不支持自动更新服务。这可能是因为服务器正在 Docker 容器中运行。您需要手动更新服务器",
|
"autoupdateUnavailableDescription": "此服务器不支持自动更新服务 这可能是因为服务器正在 Docker 容器中运行 您需要手动更新服务器",
|
||||||
"minute": "{time} 分钟",
|
"minute": "{time} 分钟",
|
||||||
"minutes": "{time} 分钟",
|
"minutes": "{time} 分钟",
|
||||||
"hour": "{time} 小时",
|
"hour": "{time} 小时",
|
||||||
|
@ -606,7 +606,7 @@
|
||||||
"remainingTime": "剩余时间",
|
"remainingTime": "剩余时间",
|
||||||
"safeSearchSettings": "安全搜索设置",
|
"safeSearchSettings": "安全搜索设置",
|
||||||
"loadingSafeSearchSettings": "正在加载安全搜索设置...",
|
"loadingSafeSearchSettings": "正在加载安全搜索设置...",
|
||||||
"safeSearchSettingsNotLoaded": "加载安全搜索设置时出错。",
|
"safeSearchSettingsNotLoaded": "加载安全搜索设置时出错 ",
|
||||||
"loadingLogsSettings": "正在加载日志设置...",
|
"loadingLogsSettings": "正在加载日志设置...",
|
||||||
"selectOptionLeftColumn": "在左侧栏中选择一个选项",
|
"selectOptionLeftColumn": "在左侧栏中选择一个选项",
|
||||||
"selectClientLeftColumn": "在左侧栏中选择一个客户端",
|
"selectClientLeftColumn": "在左侧栏中选择一个客户端",
|
||||||
|
@ -619,7 +619,7 @@
|
||||||
"copyListUrl": "复制订阅规则 URL",
|
"copyListUrl": "复制订阅规则 URL",
|
||||||
"listUrlCopied": "订阅规则 URL 已复制到剪贴板",
|
"listUrlCopied": "订阅规则 URL 已复制到剪贴板",
|
||||||
"unsupportedVersion": "不支持的版本",
|
"unsupportedVersion": "不支持的版本",
|
||||||
"unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本。alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题",
|
"unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本 alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题",
|
||||||
"iUnderstand": "我了解",
|
"iUnderstand": "我了解",
|
||||||
"appUpdates": "应用程序更新",
|
"appUpdates": "应用程序更新",
|
||||||
"usingLatestVersion": "您正在使用最新版本",
|
"usingLatestVersion": "您正在使用最新版本",
|
||||||
|
@ -658,7 +658,136 @@
|
||||||
"hideServerAddressDescription": "在主页上隐藏服务器地址",
|
"hideServerAddressDescription": "在主页上隐藏服务器地址",
|
||||||
"topItemsOrder": "顶部项目顺序",
|
"topItemsOrder": "顶部项目顺序",
|
||||||
"topItemsOrderDescription": "排列主页顶部项目列表",
|
"topItemsOrderDescription": "排列主页顶部项目列表",
|
||||||
"topItemsReorderInfo": "按住并滑动一个项目以重新排序。",
|
"topItemsReorderInfo": "按住并滑动一个项目以重新排序 ",
|
||||||
"discardChanges": "放弃更改",
|
"discardChanges": "放弃更改",
|
||||||
"discardChangesDescription": "您确定要放弃更改吗?"
|
"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": "正在禁止客户端..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,12 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:store_checker/store_checker.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -79,11 +77,6 @@ void main() async {
|
||||||
HttpOverrides.global = MyHttpOverrides();
|
HttpOverrides.global = MyHttpOverrides();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
|
||||||
Source installationSource = await StoreChecker.getSource;
|
|
||||||
appConfigProvider.setInstallationSource(installationSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
final dbData = await loadDb();
|
final dbData = await loadDb();
|
||||||
serversProvider.setDbInstance(dbData['dbInstance']);
|
serversProvider.setDbInstance(dbData['dbInstance']);
|
||||||
serversProvider.saveFromDb(dbData['servers']);
|
serversProvider.saveFromDb(dbData['servers']);
|
||||||
|
@ -169,6 +162,31 @@ void main() async {
|
||||||
(options) {
|
(options) {
|
||||||
options.dsn = dotenv.env['SENTRY_DSN'];
|
options.dsn = dotenv.env['SENTRY_DSN'];
|
||||||
options.sendDefaultPii = false;
|
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()
|
appRunner: () => startApp()
|
||||||
);
|
);
|
||||||
|
@ -178,37 +196,9 @@ void main() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Main extends StatefulWidget {
|
class Main extends StatelessWidget {
|
||||||
const Main({super.key});
|
const Main({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<Main> createState() => _MainState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MainState extends State<Main> {
|
|
||||||
List<DisplayMode> modes = <DisplayMode>[];
|
|
||||||
DisplayMode? active;
|
|
||||||
DisplayMode? preferred;
|
|
||||||
|
|
||||||
Future<void> 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
|
@ -26,6 +26,7 @@ class DnsInfo {
|
||||||
int? ratelimitSubnetLenIpv4;
|
int? ratelimitSubnetLenIpv4;
|
||||||
int? ratelimitSubnetLenIpv6;
|
int? ratelimitSubnetLenIpv6;
|
||||||
List<String>? ratelimitWhitelist;
|
List<String>? ratelimitWhitelist;
|
||||||
|
int? upstreamTimeout;
|
||||||
|
|
||||||
DnsInfo({
|
DnsInfo({
|
||||||
required this.upstreamDns,
|
required this.upstreamDns,
|
||||||
|
@ -55,6 +56,7 @@ class DnsInfo {
|
||||||
required this.ratelimitSubnetLenIpv4,
|
required this.ratelimitSubnetLenIpv4,
|
||||||
required this.ratelimitSubnetLenIpv6,
|
required this.ratelimitSubnetLenIpv6,
|
||||||
required this.ratelimitWhitelist,
|
required this.ratelimitWhitelist,
|
||||||
|
required this.upstreamTimeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DnsInfo.fromJson(Map<String, dynamic> json) => DnsInfo(
|
factory DnsInfo.fromJson(Map<String, dynamic> json) => DnsInfo(
|
||||||
|
@ -85,6 +87,7 @@ class DnsInfo {
|
||||||
ratelimitSubnetLenIpv4: json["ratelimit_subnet_len_ipv4"],
|
ratelimitSubnetLenIpv4: json["ratelimit_subnet_len_ipv4"],
|
||||||
ratelimitSubnetLenIpv6: json["ratelimit_subnet_len_ipv6"],
|
ratelimitSubnetLenIpv6: json["ratelimit_subnet_len_ipv6"],
|
||||||
ratelimitWhitelist: json["ratelimit_whitelist"] != null ? List<String>.from(json["ratelimit_whitelist"].map((x) => x)) : [],
|
ratelimitWhitelist: json["ratelimit_whitelist"] != null ? List<String>.from(json["ratelimit_whitelist"].map((x) => x)) : [],
|
||||||
|
upstreamTimeout: json["upstream_timeout"],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
|
@ -115,5 +118,6 @@ class DnsInfo {
|
||||||
"ratelimit_subnet_len_ipv4": ratelimitSubnetLenIpv4,
|
"ratelimit_subnet_len_ipv4": ratelimitSubnetLenIpv4,
|
||||||
"ratelimit_subnet_len_ipv6": ratelimitSubnetLenIpv6,
|
"ratelimit_subnet_len_ipv6": ratelimitSubnetLenIpv6,
|
||||||
"ratelimit_whitelist": ratelimitWhitelist != null ? List<String>.from(ratelimitWhitelist!.map((x) => x)) : null,
|
"ratelimit_whitelist": ratelimitWhitelist != null ? List<String>.from(ratelimitWhitelist!.map((x) => x)) : null,
|
||||||
|
"upstream_timeout": upstreamTimeout,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ DnsStatistics dnsStatisticsFromJson(String str) => DnsStatistics.fromJson(json.d
|
||||||
String dnsStatisticsToJson(DnsStatistics data) => json.encode(data.toJson());
|
String dnsStatisticsToJson(DnsStatistics data) => json.encode(data.toJson());
|
||||||
|
|
||||||
class DnsStatistics {
|
class DnsStatistics {
|
||||||
final String timeUnits;
|
final String? timeUnits;
|
||||||
final List<Map<String, int>> topQueriedDomains;
|
final List<Map<String, int>> topQueriedDomains;
|
||||||
final List<Map<String, int>> topClients;
|
final List<Map<String, int>> topClients;
|
||||||
final List<Map<String, int>> topBlockedDomains;
|
final List<Map<String, int>> topBlockedDomains;
|
||||||
|
@ -43,9 +43,9 @@ class DnsStatistics {
|
||||||
|
|
||||||
factory DnsStatistics.fromJson(Map<String, dynamic> json) => DnsStatistics(
|
factory DnsStatistics.fromJson(Map<String, dynamic> json) => DnsStatistics(
|
||||||
timeUnits: json["time_units"],
|
timeUnits: json["time_units"],
|
||||||
topQueriedDomains: List<Map<String, int>>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
|
topQueriedDomains: json["top_queried_domains"] != null ? List<Map<String, int>>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : [],
|
||||||
topClients: List<Map<String, int>>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
|
topClients: json["top_clients"] != null ? List<Map<String, int>>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : [],
|
||||||
topBlockedDomains: List<Map<String, int>>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
|
topBlockedDomains: json["top_blocked_domains"] != null ? List<Map<String, int>>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))): [],
|
||||||
topUpstreamResponses: json["top_upstreams_responses"] != null ? List<Map<String, int>>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : null,
|
topUpstreamResponses: json["top_upstreams_responses"] != null ? List<Map<String, int>>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : null,
|
||||||
topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List<Map<String, double>>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry<String, double>(k, v)))) : null,
|
topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List<Map<String, double>>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry<String, double>(k, v)))) : null,
|
||||||
dnsQueries: List<int>.from(json["dns_queries"].map((x) => x)),
|
dnsQueries: List<int>.from(json["dns_queries"].map((x) => x)),
|
||||||
|
|
|
@ -63,7 +63,7 @@ class Filter {
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"url": url,
|
"url": url,
|
||||||
"name": name,
|
"name": name,
|
||||||
"last_updated": lastUpdated != null ? lastUpdated!.toIso8601String() : null,
|
"last_updated": lastUpdated?.toIso8601String(),
|
||||||
"id": id,
|
"id": id,
|
||||||
"rules_count": rulesCount,
|
"rules_count": rulesCount,
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
|
|
|
@ -65,7 +65,7 @@ class Filter {
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"url": url,
|
"url": url,
|
||||||
"name": name,
|
"name": name,
|
||||||
"last_updated": lastUpdated != null ? lastUpdated!.toIso8601String() : null,
|
"last_updated": lastUpdated?.toIso8601String(),
|
||||||
"id": id,
|
"id": id,
|
||||||
"rules_count": rulesCount,
|
"rules_count": rulesCount,
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
|
|
|
@ -30,7 +30,7 @@ class LogsData {
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"data": List<dynamic>.from(data.map((x) => x.toJson())),
|
"data": List<dynamic>.from(data.map((x) => x.toJson())),
|
||||||
"oldest": oldest != null ? oldest!.toIso8601String() : null,
|
"oldest": oldest?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.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:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
@ -33,7 +32,7 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
int _selectedTheme = 0;
|
int _selectedTheme = 0;
|
||||||
bool _useDynamicColor = true;
|
bool _useDynamicColor = true;
|
||||||
int _staticColor = 0;
|
int _staticColor = 0;
|
||||||
bool _useThemeColorForStatus = false;
|
final bool _useThemeColorForStatus = false;
|
||||||
|
|
||||||
int _selectedClientsTab = 0;
|
int _selectedClientsTab = 0;
|
||||||
int _selectedFiltersTab = 0;
|
int _selectedFiltersTab = 0;
|
||||||
|
@ -58,8 +57,6 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
|
|
||||||
GitHubRelease? _appUpdatesAvailable;
|
GitHubRelease? _appUpdatesAvailable;
|
||||||
|
|
||||||
Source _installationSource = Source.UNKNOWN;
|
|
||||||
|
|
||||||
PackageInfo? get getAppInfo {
|
PackageInfo? get getAppInfo {
|
||||||
return _appInfo;
|
return _appInfo;
|
||||||
}
|
}
|
||||||
|
@ -162,10 +159,6 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
return _appUpdatesAvailable;
|
return _appUpdatesAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
Source get installationSource {
|
|
||||||
return _installationSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<HomeTopItems> get homeTopItemsOrder {
|
List<HomeTopItems> get homeTopItemsOrder {
|
||||||
return _homeTopItemsOrder;
|
return _homeTopItemsOrder;
|
||||||
}
|
}
|
||||||
|
@ -227,11 +220,6 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInstallationSource(Source value) {
|
|
||||||
_installationSource = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> setOverrideSslCheck(bool status) async {
|
Future<bool> setOverrideSslCheck(bool status) async {
|
||||||
try {
|
try {
|
||||||
sharedPreferencesInstance.setBool('overrideSslCheck', status);
|
sharedPreferencesInstance.setBool('overrideSslCheck', status);
|
||||||
|
|
|
@ -151,20 +151,26 @@ class DnsProvider with ChangeNotifier {
|
||||||
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
||||||
data: value
|
data: value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void updateValue(dynamic parameter, dynamic value) {
|
||||||
|
if (value != null) {
|
||||||
|
parameter = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
DnsInfo data = dnsInfo!;
|
DnsInfo data = dnsInfo!;
|
||||||
data.ratelimit = value['ratelimit'];
|
updateValue(data.ratelimit, value['ratelimit']);
|
||||||
data.ednsCsEnabled = value['edns_cs_enabled'];
|
updateValue(data.ednsCsEnabled, value['edns_cs_enabled']);
|
||||||
data.dnssecEnabled = value['dnssec_enabled'];
|
updateValue(data.dnssecEnabled, value['dnssec_enabled']);
|
||||||
data.disableIpv6 = value['disable_ipv6'];
|
updateValue(data.disableIpv6, value['disable_ipv6']);
|
||||||
data.blockingMode = value['blocking_mode'];
|
updateValue(data.blockingMode, value['blocking_mode']);
|
||||||
data.blockingIpv4 = value['blocking_ipv4'];
|
updateValue(data.blockingIpv4, value['blocking_ipv4']);
|
||||||
data.blockingIpv6 = value['blocking_ipv6'];
|
updateValue(data.blockingIpv6, value['blocking_ipv6']);
|
||||||
data.blockedResponseTtl = value['blocked_response_ttl'];
|
updateValue(data.blockedResponseTtl, value['blocked_response_ttl']);
|
||||||
data.ratelimitSubnetLenIpv4 = value['ratelimit_subnet_len_ipv4'];
|
updateValue(data.ratelimitSubnetLenIpv4, value['ratelimit_subnet_len_ipv4']);
|
||||||
data.ratelimitSubnetLenIpv6 = value['ratelimit_subnet_len_ipv6'];
|
updateValue(data.ratelimitSubnetLenIpv6, value['ratelimit_subnet_len_ipv6']);
|
||||||
data.ratelimitWhitelist = value['ratelimit_whitelist'];
|
updateValue(data.ratelimitWhitelist, value['ratelimit_whitelist']);
|
||||||
setDnsInfoData(data);
|
setDnsInfoData(data);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
51
lib/providers/live_logs_provider.dart
Normal file
51
lib/providers/live_logs_provider.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
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<Log> _logs = [];
|
||||||
|
|
||||||
|
List<Log> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -82,14 +82,14 @@ class _AddedListState extends State<AddedList> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientUpdatedSuccessfully,
|
label: AppLocalizations.of(context)!.clientUpdatedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientNotUpdated,
|
label: AppLocalizations.of(context)!.clientNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -109,14 +109,14 @@ class _AddedListState extends State<AddedList> {
|
||||||
if (widget.splitView == true) {
|
if (widget.splitView == true) {
|
||||||
Navigator.of(clientsNavigatorKey.currentContext!).popUntil((route) => false);
|
Navigator.of(clientsNavigatorKey.currentContext!).popUntil((route) => false);
|
||||||
}
|
}
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientDeletedSuccessfully,
|
label: AppLocalizations.of(context)!.clientDeletedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientNotDeleted,
|
label: AppLocalizations.of(context)!.clientNotDeleted,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ClientPlaceholder extends StatelessWidget {
|
class ClientPlaceholder extends StatelessWidget {
|
||||||
const ClientPlaceholder({Key? key}) : super(key: key);
|
const ClientPlaceholder({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return const Center(
|
||||||
child: Text("Select a client"),
|
child: Text("Select a client"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,8 @@ class _LogsListClientState extends State<LogsListClient> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => LogDetailsScreen(
|
builder: (context) => LogDetailsScreen(
|
||||||
log: log,
|
log: log,
|
||||||
dialog: true
|
dialog: true,
|
||||||
|
twoColumns: widget.splitView,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -246,7 +247,8 @@ class _LogsListClientState extends State<LogsListClient> {
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => LogDetailsScreen(
|
builder: (context) => LogDetailsScreen(
|
||||||
log: log,
|
log: log,
|
||||||
dialog: false
|
dialog: false,
|
||||||
|
twoColumns: widget.splitView,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,9 +5,9 @@ class RemoveClientModal extends StatelessWidget {
|
||||||
final void Function() onConfirm;
|
final void Function() onConfirm;
|
||||||
|
|
||||||
const RemoveClientModal({
|
const RemoveClientModal({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.onConfirm
|
required this.onConfirm
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -11,11 +11,11 @@ class SafeSearchModal extends StatefulWidget {
|
||||||
final void Function(SafeSearch) onConfirm;
|
final void Function(SafeSearch) onConfirm;
|
||||||
|
|
||||||
const SafeSearchModal({
|
const SafeSearchModal({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.safeSearch,
|
required this.safeSearch,
|
||||||
required this.disabled,
|
required this.disabled,
|
||||||
required this.onConfirm
|
required this.onConfirm
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SafeSearchModal> createState() => _SafeSearchModalState();
|
State<SafeSearchModal> createState() => _SafeSearchModalState();
|
||||||
|
|
|
@ -10,10 +10,10 @@ class ServicesModal extends StatefulWidget {
|
||||||
final void Function(List<String>) onConfirm;
|
final void Function(List<String>) onConfirm;
|
||||||
|
|
||||||
const ServicesModal({
|
const ServicesModal({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.blockedServices,
|
required this.blockedServices,
|
||||||
required this.onConfirm,
|
required this.onConfirm,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ServicesModal> createState() => _ServicesModalStateWidget();
|
State<ServicesModal> createState() => _ServicesModalStateWidget();
|
||||||
|
|
|
@ -25,22 +25,26 @@ class ClientsFab extends StatelessWidget {
|
||||||
final width = MediaQuery.of(context).size.width;
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void confirmAddClient(Client client) async {
|
void confirmAddClient(Client client) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ProcessModal processModal = ProcessModal();
|
ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||||
|
|
||||||
final result = await clientsProvider.addClient(client);
|
final result = await clientsProvider.addClient(client);
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientNotAdded,
|
label: AppLocalizations.of(context)!.clientNotAdded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -95,14 +95,14 @@ class _SearchClientsState extends State<SearchClients> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientDeletedSuccessfully,
|
label: AppLocalizations.of(context)!.clientDeletedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientNotDeleted,
|
label: AppLocalizations.of(context)!.clientNotDeleted,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -119,14 +119,14 @@ class _SearchClientsState extends State<SearchClients> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientUpdatedSuccessfully,
|
label: AppLocalizations.of(context)!.clientUpdatedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientNotUpdated,
|
label: AppLocalizations.of(context)!.clientNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -205,7 +205,7 @@ class _SearchClientsState extends State<SearchClients> {
|
||||||
height: 1,
|
height: 1,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: showDivider == true
|
color: showDivider == true
|
||||||
? Theme.of(context).colorScheme.surfaceVariant
|
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||||
: Colors.transparent
|
: Colors.transparent
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
|
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
|
||||||
|
|
||||||
class FabConnect extends StatelessWidget {
|
class FabConnect extends StatelessWidget {
|
||||||
const FabConnect({Key? key}) : super(key: key);
|
const FabConnect({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -33,6 +33,8 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
final width = MediaQuery.of(context).size.width;
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void confirmAddRule(String rule) async {
|
void confirmAddRule(String rule) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ProcessModal processModal = ProcessModal();
|
ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.addingRule);
|
processModal.open(AppLocalizations.of(context)!.addingRule);
|
||||||
|
|
||||||
|
@ -42,14 +44,14 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.ruleAddedSuccessfully,
|
label: AppLocalizations.of(context)!.ruleAddedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.ruleNotAdded,
|
label: AppLocalizations.of(context)!.ruleNotAdded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -58,6 +60,8 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void confirmEditCustomRules(List<String> rules) async {
|
void confirmEditCustomRules(List<String> rules) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ProcessModal processModal = ProcessModal();
|
ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.savingCustomRules);
|
processModal.open(AppLocalizations.of(context)!.savingCustomRules);
|
||||||
|
|
||||||
|
@ -67,14 +71,14 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.customRulesUpdatedSuccessfully,
|
label: AppLocalizations.of(context)!.customRulesUpdatedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.customRulesNotUpdated,
|
label: AppLocalizations.of(context)!.customRulesNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -137,6 +141,8 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void confirmAddList({required String name, required String url, required String type}) async {
|
void confirmAddList({required String name, required String url, required String type}) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ProcessModal processModal = ProcessModal();
|
ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.addingList);
|
processModal.open(AppLocalizations.of(context)!.addingList);
|
||||||
|
|
||||||
|
@ -146,28 +152,28 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result['success'] == true) {
|
if (result['success'] == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: "${AppLocalizations.of(context)!.listAdded} ${result['data']}.",
|
label: "${AppLocalizations.of(context)!.listAdded} ${result['data']}.",
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result['success'] == false && result['error'] == 'invalid_url') {
|
else if (result['success'] == false && result['error'] == 'invalid_url') {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listUrlInvalid,
|
label: AppLocalizations.of(context)!.listUrlInvalid,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result['success'] == false && result['error'] == 'url_exists') {
|
else if (result['success'] == false && result['error'] == 'url_exists') {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listAlreadyAdded,
|
label: AppLocalizations.of(context)!.listAlreadyAdded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listNotAdded,
|
label: AppLocalizations.of(context)!.listNotAdded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.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/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/widgets/tab_content_list.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
|
@ -34,6 +35,8 @@ class CustomRulesList extends StatefulWidget {
|
||||||
class _CustomRulesListState extends State<CustomRulesList> {
|
class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
late bool isVisible;
|
late bool isVisible;
|
||||||
|
|
||||||
|
CustomRulesSorting _sortingMethod = CustomRulesSorting.topBottom;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState(){
|
initState(){
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -60,6 +63,8 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final renderData = _sortingMethod == CustomRulesSorting.bottomTop ? widget.data.reversed.toList() : widget.data.toList();
|
||||||
|
|
||||||
bool checkIfComment(String value) {
|
bool checkIfComment(String value) {
|
||||||
final regex = RegExp(r'^(!|#).*$');
|
final regex = RegExp(r'^(!|#).*$');
|
||||||
if (regex.hasMatch(value)) {
|
if (regex.hasMatch(value)) {
|
||||||
|
@ -104,6 +109,16 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showSortingMethodModal() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => SortCustomRulesModal(
|
||||||
|
sortingMethod: _sortingMethod,
|
||||||
|
onSelect: (value) => setState(() => _sortingMethod = value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return CustomTabContentList(
|
return CustomTabContentList(
|
||||||
loadingGenerator: () => SizedBox(
|
loadingGenerator: () => SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
|
@ -124,10 +139,10 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
itemsCount: widget.data.length,
|
itemsCount: renderData.length,
|
||||||
contentWidget: (index) => ListTile(
|
contentWidget: (index) => ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.data[index],
|
renderData[index],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: checkIfComment(widget.data[index]) == true
|
color: checkIfComment(widget.data[index]) == true
|
||||||
? Theme.of(context).colorScheme.onSurface.withOpacity(0.6)
|
? Theme.of(context).colorScheme.onSurface.withOpacity(0.6)
|
||||||
|
@ -135,9 +150,9 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: generateSubtitle(widget.data[index]),
|
subtitle: generateSubtitle(renderData[index]),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () => widget.onRemoveCustomRule(widget.data[index]),
|
onPressed: () => widget.onRemoveCustomRule(renderData[index]),
|
||||||
icon: const Icon(Icons.delete)
|
icon: const Icon(Icons.delete)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -162,8 +177,8 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await filteringProvider.fetchFilters();
|
final result = await filteringProvider.fetchFilters();
|
||||||
if (result == false) {
|
if (result == false && context.mounted) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -203,7 +218,7 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
final result = await filteringProvider.fetchFilters();
|
final result = await filteringProvider.fetchFilters();
|
||||||
if (result == false) {
|
if (result == false) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -212,6 +227,11 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
},
|
},
|
||||||
fab: Column(
|
fab: Column(
|
||||||
children: [
|
children: [
|
||||||
|
FloatingActionButton.small(
|
||||||
|
onPressed: showSortingMethodModal,
|
||||||
|
child: Icon(Icons.sort_rounded),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
AddFiltersButton(
|
AddFiltersButton(
|
||||||
type: 'edit_custom_rule',
|
type: 'edit_custom_rule',
|
||||||
widget: (fn) => FloatingActionButton.small(
|
widget: (fn) => FloatingActionButton.small(
|
||||||
|
@ -229,7 +249,7 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
heightFabHidden: -120,
|
heightFabHidden: -180,
|
||||||
fabVisible: isVisible,
|
fabVisible: isVisible,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
@ -97,8 +98,7 @@ class _ContentState extends State<_Content> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateUrl(String value) {
|
void validateUrl(String 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 (Regexps.url.hasMatch(value)) {
|
||||||
if (urlRegex.hasMatch(value)) {
|
|
||||||
setState(() => urlError = null);
|
setState(() => urlError = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -63,8 +64,7 @@ class _ContentState extends State<_Content> {
|
||||||
Widget? resultWidget;
|
Widget? resultWidget;
|
||||||
|
|
||||||
void validateDomain(String value) {
|
void validateDomain(String value) {
|
||||||
final domainRegex = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
|
if (Regexps.domain.hasMatch(value)) {
|
||||||
if (domainRegex.hasMatch(value)) {
|
|
||||||
setState(() => domainError = null);
|
setState(() => domainError = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -94,14 +94,14 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
||||||
);
|
);
|
||||||
processModal.close();
|
processModal.close();
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listDataUpdated,
|
label: AppLocalizations.of(context)!.listDataUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listDataNotUpdated,
|
label: AppLocalizations.of(context)!.listDataNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -175,6 +175,7 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
||||||
_Content(
|
_Content(
|
||||||
isDialog: widget.dialog,
|
isDialog: widget.dialog,
|
||||||
list: list,
|
list: list,
|
||||||
|
type: widget.type,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -216,6 +217,7 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
||||||
_Content(
|
_Content(
|
||||||
isDialog: widget.dialog,
|
isDialog: widget.dialog,
|
||||||
list: list,
|
list: list,
|
||||||
|
type: widget.type,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -261,10 +263,12 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
||||||
class _Content extends StatelessWidget {
|
class _Content extends StatelessWidget {
|
||||||
final Filter list;
|
final Filter list;
|
||||||
final bool isDialog;
|
final bool isDialog;
|
||||||
|
final String type;
|
||||||
|
|
||||||
const _Content({
|
const _Content({
|
||||||
required this.list,
|
required this.list,
|
||||||
required this.isDialog
|
required this.isDialog,
|
||||||
|
required this.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -339,7 +343,7 @@ class _Content extends StatelessWidget {
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
icon: Icons.shield_rounded,
|
icon: Icons.shield_rounded,
|
||||||
title: AppLocalizations.of(context)!.listType,
|
title: AppLocalizations.of(context)!.listType,
|
||||||
subtitle: isDialog == 'whitelist'
|
subtitle: type == 'whitelist'
|
||||||
? AppLocalizations.of(context)!.whitelist
|
? AppLocalizations.of(context)!.whitelist
|
||||||
: AppLocalizations.of(context)!.blacklist,
|
: AppLocalizations.of(context)!.blacklist,
|
||||||
padding: isDialog == true
|
padding: isDialog == true
|
||||||
|
@ -431,7 +435,7 @@ class _Actions extends StatelessWidget {
|
||||||
);
|
);
|
||||||
processModal.close();
|
processModal.close();
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listDeleted,
|
label: AppLocalizations.of(context)!.listDeleted,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
|
@ -439,7 +443,7 @@ class _Actions extends StatelessWidget {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listNotDeleted,
|
label: AppLocalizations.of(context)!.listNotDeleted,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -24,7 +24,7 @@ import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/models/clients.dart';
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
|
||||||
class Filters extends StatefulWidget {
|
class Filters extends StatefulWidget {
|
||||||
const Filters({Key? key}) : super(key: key);
|
const Filters({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Filters> createState() => _FiltersState();
|
State<Filters> createState() => _FiltersState();
|
||||||
|
@ -57,14 +57,14 @@ class _FiltersState extends State<Filters> {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
processModal.close();
|
processModal.close();
|
||||||
if (result['success'] == true) {
|
if (result['success'] == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: "${result['data']['updated']} ${AppLocalizations.of(context)!.listsUpdated}",
|
label: "${result['data']['updated']} ${AppLocalizations.of(context)!.listsUpdated}",
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listsNotUpdated,
|
label: AppLocalizations.of(context)!.listsNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -109,14 +109,14 @@ class _FiltersState extends State<Filters> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.filteringStatusUpdated,
|
label: AppLocalizations.of(context)!.filteringStatusUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.filteringStatusNotUpdated,
|
label: AppLocalizations.of(context)!.filteringStatusNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -133,14 +133,14 @@ class _FiltersState extends State<Filters> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.updateFrequencyChanged,
|
label: AppLocalizations.of(context)!.updateFrequencyChanged,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.updateFrequencyNotChanged,
|
label: AppLocalizations.of(context)!.updateFrequencyNotChanged,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -163,14 +163,14 @@ class _FiltersState extends State<Filters> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.ruleRemovedSuccessfully,
|
label: AppLocalizations.of(context)!.ruleRemovedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.ruleNotRemoved,
|
label: AppLocalizations.of(context)!.ruleNotRemoved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -27,13 +27,13 @@ class FiltersList extends StatefulWidget {
|
||||||
final void Function(Filter, String) onOpenDetailsScreen;
|
final void Function(Filter, String) onOpenDetailsScreen;
|
||||||
|
|
||||||
const FiltersList({
|
const FiltersList({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.loadStatus,
|
required this.loadStatus,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.onOpenDetailsScreen
|
required this.onOpenDetailsScreen
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FiltersList> createState() => _FiltersListState();
|
State<FiltersList> createState() => _FiltersListState();
|
||||||
|
@ -133,7 +133,7 @@ class _FiltersListState extends State<FiltersList> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await filteringProvider.fetchFilters();
|
final result = await filteringProvider.fetchFilters();
|
||||||
if (result == false && mounted) {
|
if (result == false && mounted) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -173,7 +173,7 @@ class _FiltersListState extends State<FiltersList> {
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
final result = await filteringProvider.fetchFilters();
|
final result = await filteringProvider.fetchFilters();
|
||||||
if (result == false && mounted) {
|
if (result == false && mounted) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -84,7 +84,7 @@ class FiltersTripleColumn extends StatelessWidget {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await filteringProvider.fetchFilters();
|
final result = await filteringProvider.fetchFilters();
|
||||||
if (result == false && context.mounted) {
|
if (result == false && context.mounted) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -59,14 +59,14 @@ class ListOptionsMenu extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listDataUpdated,
|
label: AppLocalizations.of(context)!.listDataUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.listDataNotUpdated,
|
label: AppLocalizations.of(context)!.listDataNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -68,14 +68,14 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.blockedServicesUpdated,
|
label: AppLocalizations.of(context)!.blockedServicesUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.blockedServicesNotUpdated,
|
label: AppLocalizations.of(context)!.blockedServicesNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -119,24 +119,26 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||||
SliverOverlapInjector(
|
SliverOverlapInjector(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loading) Container(
|
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loading) SliverFillRemaining(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
child: Container(
|
||||||
width: double.maxFinite,
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Column(
|
width: double.maxFinite,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
const CircularProgressIndicator(),
|
children: [
|
||||||
const SizedBox(height: 30),
|
const CircularProgressIndicator(),
|
||||||
Text(
|
const SizedBox(height: 30),
|
||||||
AppLocalizations.of(context)!.loadingBlockedServicesList,
|
Text(
|
||||||
textAlign: TextAlign.center,
|
AppLocalizations.of(context)!.loadingBlockedServicesList,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 22,
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
fontSize: 22,
|
||||||
),
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loaded) SliverList.builder(
|
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loaded) SliverList.builder(
|
||||||
|
@ -181,28 +183,30 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.error) Container(
|
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.error) SliverFillRemaining(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
child: Container(
|
||||||
width: double.maxFinite,
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Column(
|
width: double.maxFinite,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
const Icon(
|
children: [
|
||||||
Icons.error,
|
const Icon(
|
||||||
color: Colors.red,
|
Icons.error,
|
||||||
size: 50,
|
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,
|
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 30),
|
||||||
],
|
Text(
|
||||||
|
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:segmented_button_slide/segmented_button_slide.dart';
|
import 'package:segmented_button_slide/segmented_button_slide.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -41,8 +42,7 @@ class _AddCustomRuleState extends State<AddCustomRule> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateDomain(String value) {
|
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 (Regexps.domain.hasMatch(value)) {
|
||||||
if (domainRegex.hasMatch(value)) {
|
|
||||||
setState(() => _domainError = null);
|
setState(() => _domainError = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -276,16 +276,22 @@ class _CustomRuleEditor extends StatelessWidget {
|
||||||
colors: SegmentedButtonSlideColors(
|
colors: SegmentedButtonSlideColors(
|
||||||
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
||||||
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
|
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,
|
textOverflow: TextOverflow.ellipsis,
|
||||||
fontSize: 14,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
horizontal: 24,
|
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),
|
Container(height: 20),
|
||||||
Material(
|
Material(
|
||||||
|
|
128
lib/screens/filters/modals/custom_rules/sort_rules.dart
Normal file
128
lib/screens/filters/modals/custom_rules/sort_rules.dart
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,9 +5,9 @@ class RemoveCustomRule extends StatelessWidget {
|
||||||
final void Function() onConfirm;
|
final void Function() onConfirm;
|
||||||
|
|
||||||
const RemoveCustomRule({
|
const RemoveCustomRule({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.onConfirm
|
required this.onConfirm
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -235,10 +235,10 @@ class _Content extends StatelessWidget {
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
overlayColor: MaterialStateProperty.all(
|
overlayColor: WidgetStateProperty.all(
|
||||||
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||||
),
|
),
|
||||||
foregroundColor: MaterialStateProperty.all(
|
foregroundColor: WidgetStateProperty.all(
|
||||||
selectedOption != null
|
selectedOption != null
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Colors.grey,
|
: Colors.grey,
|
||||||
|
|
|
@ -9,11 +9,11 @@ class DeleteSelectionModal extends StatefulWidget {
|
||||||
final void Function() onDelete;
|
final void Function() onDelete;
|
||||||
|
|
||||||
const DeleteSelectionModal({
|
const DeleteSelectionModal({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.selectedBlacklists,
|
required this.selectedBlacklists,
|
||||||
required this.selectedWhitelists,
|
required this.selectedWhitelists,
|
||||||
required this.onDelete,
|
required this.onDelete,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DeleteSelectionModal> createState() => _DeleteSelectionModalState();
|
State<DeleteSelectionModal> createState() => _DeleteSelectionModalState();
|
||||||
|
|
|
@ -9,11 +9,11 @@ class EnableDisableSelectionModal extends StatefulWidget {
|
||||||
final void Function() onDelete;
|
final void Function() onDelete;
|
||||||
|
|
||||||
const EnableDisableSelectionModal({
|
const EnableDisableSelectionModal({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.selectedBlacklists,
|
required this.selectedBlacklists,
|
||||||
required this.selectedWhitelists,
|
required this.selectedWhitelists,
|
||||||
required this.onDelete,
|
required this.onDelete,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EnableDisableSelectionModal> createState() => _EnableDisableSelectionModalState();
|
State<EnableDisableSelectionModal> createState() => _EnableDisableSelectionModalState();
|
||||||
|
|
|
@ -13,13 +13,13 @@ class SelectionList extends StatelessWidget {
|
||||||
final void Function() unselectAll;
|
final void Function() unselectAll;
|
||||||
|
|
||||||
const SelectionList({
|
const SelectionList({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.lists,
|
required this.lists,
|
||||||
required this.selectedLists,
|
required this.selectedLists,
|
||||||
required this.onSelect,
|
required this.onSelect,
|
||||||
required this.selectAll,
|
required this.selectAll,
|
||||||
required this.unselectAll,
|
required this.unselectAll,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -231,11 +231,10 @@ class _CheckboxTile extends StatelessWidget {
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
|
|
||||||
const _CheckboxTile({
|
const _CheckboxTile({
|
||||||
Key? key,
|
|
||||||
required this.list,
|
required this.list,
|
||||||
required this.onSelect,
|
required this.onSelect,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -82,8 +82,8 @@ class _SelectionScreenState extends State<SelectionScreen> with TickerProviderSt
|
||||||
blacklists: _selectedBlacklists,
|
blacklists: _selectedBlacklists,
|
||||||
whitelists: _selectedWhitelists
|
whitelists: _selectedWhitelists
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
if (!context.mounted) return;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => SelectionResultModal(
|
builder: (ctx) => SelectionResultModal(
|
||||||
|
@ -113,8 +113,8 @@ class _SelectionScreenState extends State<SelectionScreen> with TickerProviderSt
|
||||||
blacklists: _selectedBlacklists,
|
blacklists: _selectedBlacklists,
|
||||||
whitelists: _selectedWhitelists
|
whitelists: _selectedWhitelists
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
if (!context.mounted) return;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => SelectionResultModal(
|
builder: (ctx) => SelectionResultModal(
|
||||||
|
|
|
@ -34,6 +34,7 @@ class HomeAppBar extends StatelessWidget {
|
||||||
|
|
||||||
void navigateServers() {
|
void navigateServers() {
|
||||||
Future.delayed(const Duration(milliseconds: 0), (() {
|
Future.delayed(const Duration(milliseconds: 0), (() {
|
||||||
|
if (!context.mounted) return;
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => const Servers())
|
MaterialPageRoute(builder: (context) => const Servers())
|
||||||
);
|
);
|
||||||
|
|
|
@ -37,7 +37,7 @@ class CombinedChartItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CombinedHomeChart extends StatelessWidget {
|
class CombinedHomeChart extends StatelessWidget {
|
||||||
const CombinedHomeChart({Key? key}) : super(key: key);
|
const CombinedHomeChart({super.key});
|
||||||
|
|
||||||
List<int>? removeZero(List<int> list) {
|
List<int>? removeZero(List<int> list) {
|
||||||
final removed = list.where((i) => i > 0);
|
final removed = list.where((i) => i > 0);
|
||||||
|
@ -266,12 +266,11 @@ class _Legend extends StatelessWidget {
|
||||||
final String? secondaryValue;
|
final String? secondaryValue;
|
||||||
|
|
||||||
const _Legend({
|
const _Legend({
|
||||||
Key? key,
|
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.primaryValue,
|
required this.primaryValue,
|
||||||
this.secondaryValue
|
this.secondaryValue
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
|
||||||
class HomeFab extends StatelessWidget {
|
class HomeFab extends StatelessWidget {
|
||||||
const HomeFab({Key? key}) : super(key: key);
|
const HomeFab({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -93,7 +93,7 @@ class _HomeState extends State<Home> {
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
final result = await statusProvider.getServerStatus();
|
final result = await statusProvider.getServerStatus();
|
||||||
if (mounted && result == false) {
|
if (mounted && result == false) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.serverStatusNotRefreshed,
|
label: AppLocalizations.of(context)!.serverStatusNotRefreshed,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -82,7 +82,7 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
|
||||||
time: time
|
time: time
|
||||||
);
|
);
|
||||||
if (mounted && result == false) {
|
if (mounted && result == false) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.invalidUsernamePassword,
|
label: AppLocalizations.of(context)!.invalidUsernamePassword,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -8,13 +8,13 @@ class SmallSwitch extends StatelessWidget {
|
||||||
final bool disabled;
|
final bool disabled;
|
||||||
|
|
||||||
const SmallSwitch({
|
const SmallSwitch({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onChange,
|
required this.onChange,
|
||||||
required this.disabled,
|
required this.disabled,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -83,14 +83,14 @@ class TopItemsLists extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (rules == true) {
|
if (rules == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.userFilteringRulesUpdated,
|
label: AppLocalizations.of(context)!.userFilteringRulesUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated,
|
label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -121,21 +121,21 @@ class TopItemsLists extends StatelessWidget {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.content == 'client_another_list') {
|
else if (result.successful == false && result.content == 'client_another_list') {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAnotherList,
|
label: AppLocalizations.of(context)!.clientAnotherList,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: newList == AccessSettingsList.allowed || newList == AccessSettingsList.disallowed
|
label: newList == AccessSettingsList.allowed || newList == AccessSettingsList.disallowed
|
||||||
? AppLocalizations.of(context)!.clientNotRemoved
|
? AppLocalizations.of(context)!.clientNotRemoved
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/widgets/options_menu.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/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/models/menu_option.dart';
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
@ -16,6 +17,7 @@ import 'package:adguard_home_manager/functions/number_format.dart';
|
||||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||||
|
|
||||||
enum _SortingOptions { highestToLowest, lowestToHighest }
|
enum _SortingOptions { highestToLowest, lowestToHighest }
|
||||||
|
final GlobalKey _searchButtonKey = GlobalKey();
|
||||||
|
|
||||||
class TopItemsScreen extends StatefulWidget {
|
class TopItemsScreen extends StatefulWidget {
|
||||||
final HomeTopItems type;
|
final HomeTopItems type;
|
||||||
|
@ -47,15 +49,18 @@ class TopItemsScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _TopItemsScreenState extends State<TopItemsScreen> {
|
class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
_SortingOptions _sortingOptions = _SortingOptions.highestToLowest;
|
_SortingOptions _sortingOptions = _SortingOptions.highestToLowest;
|
||||||
bool searchActive = false;
|
|
||||||
final TextEditingController searchController = TextEditingController();
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
String? _currentSearchValue = "";
|
||||||
|
|
||||||
List<Map<String, dynamic>> data = [];
|
List<Map<String, dynamic>> data = [];
|
||||||
List<Map<String, dynamic>> screenData = [];
|
List<Map<String, dynamic>> screenData = [];
|
||||||
|
|
||||||
void search(String value) {
|
void search(String value) {
|
||||||
List<Map<String, dynamic>> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList();
|
List<Map<String, dynamic>> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList();
|
||||||
setState(() => screenData = newValues);
|
setState(() {
|
||||||
|
screenData = newValues;
|
||||||
|
_currentSearchValue = searchController.text;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -75,118 +80,132 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
final sortedValues = _sortingOptions == _SortingOptions.lowestToHighest
|
final sortedValues = _sortingOptions == _SortingOptions.lowestToHighest
|
||||||
? screenData.reversed.toList()
|
? screenData.reversed.toList()
|
||||||
: screenData.toList();
|
: screenData.toList();
|
||||||
|
|
||||||
|
void showSearchDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FloatingSearchBar(
|
||||||
|
existingSearchValue: _currentSearchValue,
|
||||||
|
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
|
||||||
|
onSearchCompleted: (v) {
|
||||||
|
List<Map<String, dynamic>> newValues = widget.data.where((item) => item.keys.toList()[0].contains(v)).toList();
|
||||||
|
setState(() {
|
||||||
|
screenData = newValues;
|
||||||
|
_currentSearchValue = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.isFullscreen == true) {
|
if (widget.isFullscreen == true) {
|
||||||
return Dialog.fullscreen(
|
return Scaffold(
|
||||||
child: Scaffold(
|
body: NestedScrollView(
|
||||||
appBar: AppBar(
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
title: searchActive == true
|
SliverOverlapAbsorber(
|
||||||
? Padding(
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
padding: const EdgeInsets.only(bottom: 3),
|
sliver: SliverAppBar.large(
|
||||||
child: TextFormField(
|
title: Text(widget.title),
|
||||||
controller: searchController,
|
actions: [
|
||||||
onChanged: search,
|
IconButton(
|
||||||
decoration: InputDecoration(
|
key: _searchButtonKey,
|
||||||
hintText: AppLocalizations.of(context)!.search,
|
onPressed: showSearchDialog,
|
||||||
hintStyle: const TextStyle(
|
icon: const Icon(Icons.search_rounded),
|
||||||
fontWeight: FontWeight.normal,
|
tooltip: AppLocalizations.of(context)!.search,
|
||||||
fontSize: 18
|
),
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
),
|
),
|
||||||
border: InputBorder.none,
|
PopupMenuItem(
|
||||||
),
|
onTap: () => setState(() => _sortingOptions = _SortingOptions.lowestToHighest),
|
||||||
style: const TextStyle(
|
child: Row(
|
||||||
fontWeight: FontWeight.normal,
|
children: [
|
||||||
fontSize: 18
|
const Icon(Icons.arrow_upward_rounded),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
autofocus: true,
|
Expanded(
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.fromLowestToHighest)
|
||||||
)
|
),
|
||||||
: Text(widget.title),
|
const SizedBox(width: 16),
|
||||||
leading: searchActive == true ?
|
Icon(
|
||||||
IconButton(
|
_sortingOptions == _SortingOptions.lowestToHighest
|
||||||
onPressed: () => setState(() {
|
? Icons.radio_button_checked_rounded
|
||||||
searchActive = false;
|
: Icons.radio_button_unchecked_rounded,
|
||||||
searchController.text = '';
|
color: _sortingOptions == _SortingOptions.lowestToHighest
|
||||||
screenData = data;
|
? Theme.of(context).colorScheme.primary
|
||||||
}),
|
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
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,
|
|
||||||
),
|
|
||||||
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(
|
const SizedBox(width: 8)
|
||||||
onTap: () => setState(() => _sortingOptions = _SortingOptions.lowestToHighest),
|
],
|
||||||
child: Row(
|
)
|
||||||
children: [
|
)
|
||||||
const Icon(Icons.arrow_upward_rounded),
|
],
|
||||||
const SizedBox(width: 8),
|
body: SafeArea(
|
||||||
Expanded(
|
top: false,
|
||||||
child: Text(AppLocalizations.of(context)!.fromLowestToHighest)
|
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,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
),
|
||||||
Icon(
|
),
|
||||||
_sortingOptions == _SortingOptions.lowestToHighest
|
),
|
||||||
? Icons.radio_button_checked_rounded
|
if (sortedValues.isNotEmpty) SliverPadding(
|
||||||
: Icons.radio_button_unchecked_rounded,
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewPadding.bottom),
|
||||||
color: _sortingOptions == _SortingOptions.lowestToHighest
|
sliver: SliverList.builder(
|
||||||
? Theme.of(context).colorScheme.primary
|
itemCount: sortedValues.length,
|
||||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
itemBuilder: (context, index) => _Item(
|
||||||
)
|
data: sortedValues[index],
|
||||||
],
|
isClient: widget.isClient,
|
||||||
)
|
options: widget.options,
|
||||||
|
total: total,
|
||||||
|
withProgressBar: widget.withProgressBar,
|
||||||
|
onTapEntry: widget.onTapEntry,
|
||||||
|
buildValue: widget.buildValue,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8)
|
)
|
||||||
],
|
)
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: _Content(
|
|
||||||
buildValue: widget.buildValue,
|
|
||||||
isClient: widget.isClient,
|
|
||||||
onTapEntry: widget.onTapEntry,
|
|
||||||
options: widget.options,
|
|
||||||
screenData: sortedValues,
|
|
||||||
total: total,
|
|
||||||
withProgressBar: widget.withProgressBar,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -242,14 +261,29 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _Content(
|
child: sortedValues.isNotEmpty ? ListView.builder(
|
||||||
buildValue: widget.buildValue,
|
itemCount: sortedValues.length,
|
||||||
isClient: widget.isClient,
|
itemBuilder: (context, index) => _Item(
|
||||||
onTapEntry: widget.onTapEntry,
|
data: sortedValues[index],
|
||||||
options: widget.options,
|
isClient: widget.isClient,
|
||||||
screenData: sortedValues,
|
options: widget.options,
|
||||||
total: total,
|
withProgressBar: widget.withProgressBar,
|
||||||
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -260,8 +294,8 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Content extends StatelessWidget {
|
class _Item extends StatelessWidget {
|
||||||
final List<Map<String, dynamic>> screenData;
|
final dynamic data;
|
||||||
final bool? isClient;
|
final bool? isClient;
|
||||||
final List<MenuOption> Function(dynamic) options;
|
final List<MenuOption> Function(dynamic) options;
|
||||||
final bool withProgressBar;
|
final bool withProgressBar;
|
||||||
|
@ -269,8 +303,8 @@ class _Content extends StatelessWidget {
|
||||||
final String Function(dynamic) buildValue;
|
final String Function(dynamic) buildValue;
|
||||||
final double total;
|
final double total;
|
||||||
|
|
||||||
const _Content({
|
const _Item({
|
||||||
required this.screenData,
|
required this.data,
|
||||||
required this.isClient,
|
required this.isClient,
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.withProgressBar,
|
required this.withProgressBar,
|
||||||
|
@ -283,98 +317,75 @@ class _Content extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
|
|
||||||
if (screenData.isNotEmpty) {
|
String? name;
|
||||||
return ListView.builder(
|
if (isClient != null && isClient == true) {
|
||||||
padding: const EdgeInsets.only(top: 0),
|
try {
|
||||||
itemCount: screenData.length,
|
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(data.keys.toList()[0])).name;
|
||||||
itemBuilder: (context, index) {
|
} catch (e) {
|
||||||
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) {
|
|
||||||
// ---- //
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return Center(
|
return OptionsMenu(
|
||||||
child: Padding(
|
options: options,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
value: data.keys.toList()[0],
|
||||||
child: Text(
|
onTap: onTapEntry != null
|
||||||
AppLocalizations.of(context)!.noItemsSearch,
|
? (v) {
|
||||||
textAlign: TextAlign.center,
|
onTapEntry!(v);
|
||||||
style: TextStyle(
|
Navigator.pop(context);
|
||||||
fontSize: 22,
|
}
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
: 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,
|
||||||
|
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((data.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: 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ 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/desktop_mode.dart';
|
||||||
import 'package:adguard_home_manager/functions/open_url.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/constants/urls.dart';
|
||||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
import 'package:adguard_home_manager/functions/get_filtered_status.dart';
|
import 'package:adguard_home_manager/functions/get_filtered_status.dart';
|
||||||
|
@ -23,11 +24,13 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
class LogDetailsScreen extends StatelessWidget {
|
class LogDetailsScreen extends StatelessWidget {
|
||||||
final Log log;
|
final Log log;
|
||||||
final bool dialog;
|
final bool dialog;
|
||||||
|
final bool twoColumns;
|
||||||
|
|
||||||
const LogDetailsScreen({
|
const LogDetailsScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.log,
|
required this.log,
|
||||||
required this.dialog
|
required this.dialog,
|
||||||
|
required this.twoColumns,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -49,14 +52,14 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (rules == true) {
|
if (rules == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.userFilteringRulesUpdated,
|
label: AppLocalizations.of(context)!.userFilteringRulesUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated,
|
label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -147,6 +150,7 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
|
automaticallyImplyLeading: twoColumns != true,
|
||||||
title: Text(AppLocalizations.of(context)!.logDetails),
|
title: Text(AppLocalizations.of(context)!.logDetails),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -275,10 +279,11 @@ class _Content extends StatelessWidget {
|
||||||
subtitle: convertTimestampLocalTimezone(log.time, 'HH:mm:ss')
|
subtitle: convertTimestampLocalTimezone(log.time, 'HH:mm:ss')
|
||||||
),
|
),
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.request),
|
SectionLabel(label: AppLocalizations.of(context)!.request),
|
||||||
LogListTile(
|
if (log.question.name != null) LogListTile(
|
||||||
icon: Icons.domain_rounded,
|
icon: Icons.domain_rounded,
|
||||||
title: AppLocalizations.of(context)!.domain,
|
title: AppLocalizations.of(context)!.domain,
|
||||||
subtitle: log.question.name
|
subtitle: log.question.name,
|
||||||
|
onTap: () => copyToClipboard(value: log.question.name!, successMessage: AppLocalizations.of(context)!.domainCopiedClipboard),
|
||||||
),
|
),
|
||||||
LogListTile(
|
LogListTile(
|
||||||
icon: Icons.category_rounded,
|
icon: Icons.category_rounded,
|
||||||
|
@ -294,7 +299,8 @@ class _Content extends StatelessWidget {
|
||||||
if (log.upstream != null && log.upstream != '') LogListTile(
|
if (log.upstream != null && log.upstream != '') LogListTile(
|
||||||
icon: Icons.dns_rounded,
|
icon: Icons.dns_rounded,
|
||||||
title: AppLocalizations.of(context)!.dnsServer,
|
title: AppLocalizations.of(context)!.dnsServer,
|
||||||
subtitle: log.upstream
|
subtitle: log.upstream,
|
||||||
|
onTap: () => copyToClipboard(value: log.upstream!, successMessage: AppLocalizations.of(context)!.dnsServerAddressCopied)
|
||||||
),
|
),
|
||||||
LogListTile(
|
LogListTile(
|
||||||
icon: Icons.timer_rounded,
|
icon: Icons.timer_rounded,
|
||||||
|
@ -310,12 +316,14 @@ class _Content extends StatelessWidget {
|
||||||
LogListTile(
|
LogListTile(
|
||||||
icon: Icons.smartphone_rounded,
|
icon: Icons.smartphone_rounded,
|
||||||
title: AppLocalizations.of(context)!.deviceIp,
|
title: AppLocalizations.of(context)!.deviceIp,
|
||||||
subtitle: log.client
|
subtitle: log.client,
|
||||||
|
onTap: () => copyToClipboard(value: log.client, successMessage: AppLocalizations.of(context)!.clientIpCopied),
|
||||||
),
|
),
|
||||||
if (log.clientInfo != null && log.clientInfo!.name != '') LogListTile(
|
if (log.clientInfo != null && log.clientInfo!.name != '') LogListTile(
|
||||||
icon: Icons.abc_rounded,
|
icon: Icons.abc_rounded,
|
||||||
title: AppLocalizations.of(context)!.deviceName,
|
title: AppLocalizations.of(context)!.deviceName,
|
||||||
subtitle: log.clientInfo!.name
|
subtitle: log.clientInfo!.name,
|
||||||
|
onTap: () => copyToClipboard(value: log.clientInfo!.name, successMessage: AppLocalizations.of(context)!.clientNameCopied),
|
||||||
),
|
),
|
||||||
if (log.rules.isNotEmpty) ...[
|
if (log.rules.isNotEmpty) ...[
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.rules),
|
SectionLabel(label: AppLocalizations.of(context)!.rules),
|
||||||
|
|
|
@ -6,63 +6,68 @@ class LogListTile extends StatelessWidget {
|
||||||
final String? subtitle;
|
final String? subtitle;
|
||||||
final Widget? subtitleWidget;
|
final Widget? subtitleWidget;
|
||||||
final Widget? trailing;
|
final Widget? trailing;
|
||||||
|
final void Function()? onTap;
|
||||||
|
|
||||||
const LogListTile({
|
const LogListTile({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.subtitleWidget,
|
this.subtitleWidget,
|
||||||
this.trailing,
|
this.trailing,
|
||||||
}) : super(key: key);
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return InkWell(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
onTap: onTap,
|
||||||
child: Row(
|
child: Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
children: [
|
child: Row(
|
||||||
Flexible(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
Flexible(
|
||||||
Icon(
|
child: Row(
|
||||||
icon,
|
children: [
|
||||||
size: 24,
|
Icon(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
icon,
|
||||||
),
|
size: 24,
|
||||||
const SizedBox(width: 16),
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
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(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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (trailing != null) ...[
|
||||||
if (trailing != null) ...[
|
const SizedBox(width: 16),
|
||||||
const SizedBox(width: 16),
|
trailing!
|
||||||
trailing!
|
]
|
||||||
]
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,13 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.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:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.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/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/status_provider.dart';
|
||||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||||
|
@ -14,10 +17,12 @@ import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||||
class _ClientLog {
|
class _ClientLog {
|
||||||
final String ip;
|
final String ip;
|
||||||
final String? name;
|
final String? name;
|
||||||
|
final List<String>? ids;
|
||||||
|
|
||||||
const _ClientLog({
|
const _ClientLog({
|
||||||
required this.ip,
|
required this.ip,
|
||||||
required this.name
|
required this.name,
|
||||||
|
this.ids,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +43,7 @@ class ClientsModal extends StatefulWidget {
|
||||||
class _ClientsModalState extends State<ClientsModal> {
|
class _ClientsModalState extends State<ClientsModal> {
|
||||||
List<_ClientLog> _filteredClients = [];
|
List<_ClientLog> _filteredClients = [];
|
||||||
final _searchController = TextEditingController();
|
final _searchController = TextEditingController();
|
||||||
|
int _selectedList = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -65,22 +71,55 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
|
|
||||||
void onSearch(String value) {
|
void onSearch({required String value, int? selectedList}) {
|
||||||
final filtered = clientsProvider.clients!.autoClients.map((e) {
|
if ((selectedList ?? _selectedList) == 1) {
|
||||||
String? name;
|
final filtered = clientsProvider.clients!.clients.map((e) {
|
||||||
try {
|
String? name;
|
||||||
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ip)).name;
|
try {
|
||||||
} catch (e) {
|
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ids[0])).name;
|
||||||
// ---- //
|
} catch (e) {
|
||||||
}
|
// ---- //
|
||||||
return _ClientLog(
|
}
|
||||||
ip: e.ip,
|
return _ClientLog(
|
||||||
name: name
|
ip: e.ids[0],
|
||||||
);
|
name: name,
|
||||||
}).where(
|
ids: e.ids
|
||||||
(c) => c.ip.contains(value.toLowerCase()) || (c.name != null && c.name!.toLowerCase().contains(value.toLowerCase()))
|
);
|
||||||
).toList();
|
}).where(
|
||||||
setState(() => _filteredClients = filtered);
|
(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.dialog == true) {
|
if (widget.dialog == true) {
|
||||||
|
@ -115,7 +154,7 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
_SearchField(
|
_SearchField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
onClear: () => setState(() => _searchController.text = ""),
|
onClear: () => setState(() => _searchController.text = ""),
|
||||||
onSearch: onSearch
|
onSearch: (v) => onSearch(value: v)
|
||||||
),
|
),
|
||||||
Card(
|
Card(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
@ -135,31 +174,43 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomCheckboxListTile(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.all(16),
|
||||||
left: 24,
|
child: SegmentedButtonSlide(
|
||||||
top: 8,
|
entries: [
|
||||||
right: 12,
|
SegmentedButtonSlideEntry(icon: Icons.devices, label: AppLocalizations.of(context)!.activeClients),
|
||||||
bottom: 8
|
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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length,
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v == true) {
|
|
||||||
logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logsProvider.setSelectedClients([]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title: AppLocalizations.of(context)!.selectAll
|
|
||||||
),
|
),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
primary: false,
|
primary: false,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: _filteredClients.length,
|
itemCount: _filteredClients.length,
|
||||||
itemBuilder: (context, index) => _ListItem(
|
itemBuilder: (context, index) => _ListItem(
|
||||||
label: _filteredClients[index].ip,
|
title: _filteredClients[index].ip,
|
||||||
|
subtitle: _selectedList == 0 ? _filteredClients[index].name : _filteredClients[index].ids?.join(", "),
|
||||||
checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip),
|
checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip),
|
||||||
|
isAddedClient: _selectedList == 0,
|
||||||
|
onSearchAddedClient: () => searchAddedClient(_filteredClients[index]),
|
||||||
onChanged: (isSelected) {
|
onChanged: (isSelected) {
|
||||||
if (isSelected == true) {
|
if (isSelected == true) {
|
||||||
logsProvider.setSelectedClients([
|
logsProvider.setSelectedClients([
|
||||||
|
@ -196,7 +247,7 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
_SearchField(
|
_SearchField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
onClear: () => setState(() => _searchController.text = ""),
|
onClear: () => setState(() => _searchController.text = ""),
|
||||||
onSearch: onSearch
|
onSearch: (v) => onSearch(value: v)
|
||||||
),
|
),
|
||||||
Card(
|
Card(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
@ -216,31 +267,44 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomCheckboxListTile(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.all(16),
|
||||||
left: 24,
|
child: SegmentedButtonSlide(
|
||||||
top: 8,
|
entries: [
|
||||||
right: 12,
|
SegmentedButtonSlideEntry(icon: Icons.devices, label: AppLocalizations.of(context)!.activeClients),
|
||||||
bottom: 8
|
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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length,
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v == true) {
|
|
||||||
logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logsProvider.setSelectedClients([]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title: AppLocalizations.of(context)!.selectAll
|
|
||||||
),
|
),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
primary: false,
|
primary: false,
|
||||||
itemCount: _filteredClients.length,
|
itemCount: _filteredClients.length,
|
||||||
itemBuilder: (context, index) => _ListItem(
|
itemBuilder: (context, index) => _ListItem(
|
||||||
label: _filteredClients[index].ip,
|
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),
|
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) {
|
onChanged: (isSelected) {
|
||||||
if (isSelected == true) {
|
if (isSelected == true) {
|
||||||
logsProvider.setSelectedClients([
|
logsProvider.setSelectedClients([
|
||||||
|
@ -257,7 +321,7 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -309,38 +373,47 @@ class _SearchField extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ListItem extends StatelessWidget {
|
class _ListItem extends StatelessWidget {
|
||||||
final String label;
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
final bool checkboxActive;
|
final bool checkboxActive;
|
||||||
final void Function(bool) onChanged;
|
final void Function(bool) onChanged;
|
||||||
|
final bool isAddedClient;
|
||||||
|
final void Function()? onSearchAddedClient;
|
||||||
|
|
||||||
const _ListItem({
|
const _ListItem({
|
||||||
required this.label,
|
required this.title,
|
||||||
|
this.subtitle,
|
||||||
required this.checkboxActive,
|
required this.checkboxActive,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
|
required this.isAddedClient,
|
||||||
|
required this.onSearchAddedClient,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
if (isAddedClient == true) {
|
||||||
|
return CustomListTile(
|
||||||
String? name;
|
title: title,
|
||||||
try {
|
subtitle: subtitle,
|
||||||
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(label)).name;
|
trailing: TextButton(
|
||||||
} catch (e) {
|
onPressed: onSearchAddedClient,
|
||||||
// ---- //
|
child: Text(AppLocalizations.of(context)!.select)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomCheckboxListTile(
|
|
||||||
value: checkboxActive,
|
|
||||||
onChanged: (v) => onChanged(v),
|
|
||||||
title: label,
|
|
||||||
subtitle: name,
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 24,
|
|
||||||
top: 8,
|
|
||||||
right: 12,
|
|
||||||
bottom: 8
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
93
lib/screens/logs/live/live_logs_list.dart
Normal file
93
lib/screens/logs/live/live_logs_list.dart
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
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<LiveLogsProvider>(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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
70
lib/screens/logs/live/live_logs_screen.dart
Normal file
70
lib/screens/logs/live/live_logs_screen.dart
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
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<LiveLogsScreen> createState() => _LiveLogsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LiveLogsScreenState extends State<LiveLogsScreen> {
|
||||||
|
Log? _selectedLog;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
Provider.of<LiveLogsProvider>(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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -98,6 +96,8 @@ class LogTile extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void blockUnblock({required String domain, required String newStatus}) async {
|
void blockUnblock({required String domain, required String newStatus}) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final ProcessModal processModal = ProcessModal();
|
final ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.savingUserFilters);
|
processModal.open(AppLocalizations.of(context)!.savingUserFilters);
|
||||||
|
|
||||||
|
@ -110,14 +110,14 @@ class LogTile extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (rules == true) {
|
if (rules == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.userFilteringRulesUpdated,
|
label: AppLocalizations.of(context)!.userFilteringRulesUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated,
|
label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -126,6 +126,8 @@ class LogTile extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void confirmAddClient(Client client) async {
|
void confirmAddClient(Client client) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ProcessModal processModal = ProcessModal();
|
ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||||
|
|
||||||
|
@ -133,15 +135,17 @@ class LogTile extends StatelessWidget {
|
||||||
|
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientNotAdded,
|
label: AppLocalizations.of(context)!.clientNotAdded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -150,6 +154,8 @@ class LogTile extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void blockUnblockRuleClient() async {
|
void blockUnblockRuleClient() async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ProcessModal processModal = ProcessModal();
|
ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.addingRule);
|
processModal.open(AppLocalizations.of(context)!.addingRule);
|
||||||
|
|
||||||
|
@ -163,7 +169,7 @@ class LogTile extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: isDomainBlocked(log.reason) == true
|
label: isDomainBlocked(log.reason) == true
|
||||||
? AppLocalizations.of(context)!.domainUnblockedThisClient(log.question.name!)
|
? AppLocalizations.of(context)!.domainUnblockedThisClient(log.question.name!)
|
||||||
|
@ -172,7 +178,7 @@ class LogTile extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.ruleNotAdded,
|
label: AppLocalizations.of(context)!.ruleNotAdded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -199,21 +205,21 @@ class LogTile extends StatelessWidget {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.content == 'client_another_list') {
|
else if (result.successful == false && result.content == 'client_another_list') {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAnotherList,
|
label: AppLocalizations.of(context)!.clientAnotherList,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.changesNotSaved,
|
label: AppLocalizations.of(context)!.changesNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -224,12 +230,15 @@ class LogTile extends StatelessWidget {
|
||||||
void openAddClient() {
|
void openAddClient() {
|
||||||
Future.delayed(
|
Future.delayed(
|
||||||
const Duration(milliseconds: 0),
|
const Duration(milliseconds: 0),
|
||||||
() => openClientFormModal(
|
() {
|
||||||
context: context,
|
if (!context.mounted) return;
|
||||||
width: MediaQuery.of(context).size.width,
|
openClientFormModal(
|
||||||
onConfirm: confirmAddClient,
|
context: context,
|
||||||
initialData: ClientInitialData(name: "Client ${log.client}", ip: log.client)
|
width: MediaQuery.of(context).size.width,
|
||||||
)
|
onConfirm: confirmAddClient,
|
||||||
|
initialData: ClientInitialData(name: "Client ${log.client}", ip: log.client)
|
||||||
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ class _LogsState extends State<Logs> {
|
||||||
? LogDetailsScreen(
|
? LogDetailsScreen(
|
||||||
log: _selectedLog!,
|
log: _selectedLog!,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
|
twoColumns: true,
|
||||||
)
|
)
|
||||||
: const SizedBox()
|
: const SizedBox()
|
||||||
)
|
)
|
||||||
|
|
|
@ -42,7 +42,7 @@ class _LogsListWidgetState extends State<LogsListWidget> {
|
||||||
|
|
||||||
final result = await statusProvider.getFilteringRules();
|
final result = await statusProvider.getFilteringRules();
|
||||||
if (mounted && result == false) {
|
if (mounted && result == false) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.couldntGetFilteringStatus,
|
label: AppLocalizations.of(context)!.couldntGetFilteringStatus,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -56,7 +56,7 @@ class _LogsListWidgetState extends State<LogsListWidget> {
|
||||||
|
|
||||||
final result = await clientsProvider.fetchClients();
|
final result = await clientsProvider.fetchClients();
|
||||||
if (mounted && result == false) {
|
if (mounted && result == false) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.couldntGetFilteringStatus,
|
label: AppLocalizations.of(context)!.couldntGetFilteringStatus,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -188,6 +188,7 @@ class _LogsListWidgetState extends State<LogsListWidget> {
|
||||||
builder: (context) => LogDetailsScreen(
|
builder: (context) => LogDetailsScreen(
|
||||||
log: log,
|
log: log,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
|
twoColumns: widget.twoColumns,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,11 +6,14 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.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/screens/logs/filters/logs_filters_modal.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/floating_search_bar.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/config/globals.dart';
|
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/functions/desktop_mode.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/models/applied_filters.dart';
|
import 'package:adguard_home_manager/models/applied_filters.dart';
|
||||||
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||||
|
|
||||||
|
@ -58,9 +61,10 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
void showSearchDialog() {
|
void showSearchDialog() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _Search(
|
builder: (context) => FloatingSearchBar(
|
||||||
|
existingSearchValue: logsProvider.appliedFilters.searchText ?? "",
|
||||||
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
|
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
|
||||||
onSearch: (v) {
|
onSearchCompleted: (v) {
|
||||||
logsProvider.setAppliedFilters(
|
logsProvider.setAppliedFilters(
|
||||||
AppliedFiters(
|
AppliedFiters(
|
||||||
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
|
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
|
||||||
|
@ -70,10 +74,37 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
);
|
);
|
||||||
logsProvider.filterLogs();
|
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<ServersProvider, LiveLogsProvider>(
|
||||||
|
create: (context) => LiveLogsProvider(),
|
||||||
|
update: (context, servers, logs) => logs!..update(servers),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const LiveLogsScreen()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final Map<String, String> translatedString = {
|
final Map<String, String> translatedString = {
|
||||||
"all": AppLocalizations.of(context)!.all,
|
"all": AppLocalizations.of(context)!.all,
|
||||||
"filtered": AppLocalizations.of(context)!.filtered,
|
"filtered": AppLocalizations.of(context)!.filtered,
|
||||||
|
@ -104,10 +135,29 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
icon: const Icon(Icons.search_rounded),
|
icon: const Icon(Icons.search_rounded),
|
||||||
tooltip: AppLocalizations.of(context)!.search,
|
tooltip: AppLocalizations.of(context)!.search,
|
||||||
),
|
),
|
||||||
if (logsProvider.loadStatus == LoadStatus.loaded) IconButton(
|
if (logsProvider.loadStatus == LoadStatus.loaded) PopupMenuButton(
|
||||||
onPressed: openFilersModal,
|
itemBuilder: (context) => [
|
||||||
icon: const Icon(Icons.filter_list_rounded),
|
PopupMenuItem(
|
||||||
tooltip: AppLocalizations.of(context)!.filters,
|
onTap: openFilersModal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.filter_list_rounded),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(AppLocalizations.of(context)!.filters)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: openLiveLogsScreen,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.stream_rounded),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(AppLocalizations.of(context)!.liveLogs)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
|
@ -241,105 +291,3 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Search extends StatefulWidget {
|
|
||||||
final void Function(String) onSearch;
|
|
||||||
final RenderBox? searchButtonRenderBox;
|
|
||||||
|
|
||||||
const _Search({
|
|
||||||
required this.onSearch,
|
|
||||||
required this.searchButtonRenderBox,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_Search> createState() => _SearchState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SearchState extends State<_Search> {
|
|
||||||
final _searchController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final logsProvider = Provider.of<LogsProvider>(context, listen: false);
|
|
||||||
_searchController.text = logsProvider.appliedFilters.searchText ?? "";
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final logsProvider = Provider.of<LogsProvider>(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: (v) {
|
|
||||||
if (v == "") {
|
|
||||||
logsProvider.setSearchText(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logsProvider.setSearchText(v);
|
|
||||||
},
|
|
||||||
onFieldSubmitted: (v) {
|
|
||||||
widget.onSearch(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: () {
|
|
||||||
_searchController.text = "";
|
|
||||||
logsProvider.setSearchText(null);
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.close_rounded,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
tooltip: AppLocalizations.of(context)!.clearSearch,
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -71,7 +71,7 @@ class _ClientsListState extends State<ClientsList> {
|
||||||
Future refetchClients() async {
|
Future refetchClients() async {
|
||||||
final result = await clientsProvider.fetchClients();
|
final result = await clientsProvider.fetchClients();
|
||||||
if (result == false && mounted) {
|
if (result == false && mounted) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientsNotLoaded,
|
label: AppLocalizations.of(context)!.clientsNotLoaded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -104,21 +104,21 @@ class _ClientsListState extends State<ClientsList> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientRemovedSuccessfully,
|
label: AppLocalizations.of(context)!.clientRemovedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.content == 'client_another_list') {
|
else if (result.successful == false && result.content == 'client_another_list') {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAnotherList,
|
label: AppLocalizations.of(context)!.clientAnotherList,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed
|
label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed
|
||||||
? AppLocalizations.of(context)!.clientNotRemoved
|
? AppLocalizations.of(context)!.clientNotRemoved
|
||||||
|
@ -137,21 +137,21 @@ class _ClientsListState extends State<ClientsList> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.content == 'client_another_list') {
|
else if (result.successful == false && result.content == 'client_another_list') {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.clientAnotherList,
|
label: AppLocalizations.of(context)!.clientAnotherList,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed
|
label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed
|
||||||
? AppLocalizations.of(context)!.clientNotRemoved
|
? AppLocalizations.of(context)!.clientNotRemoved
|
||||||
|
|
|
@ -26,14 +26,14 @@ class AdvancedSettings extends StatelessWidget {
|
||||||
final result = await function(newStatus);
|
final result = await function(newStatus);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.settingsUpdatedSuccessfully,
|
label: AppLocalizations.of(context)!.settingsUpdatedSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.cannotUpdateSettings,
|
label: AppLocalizations.of(context)!.cannotUpdateSettings,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -31,7 +31,7 @@ class ColorItem extends StatelessWidget {
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => onChanged(numericValue),
|
onTap: () => onChanged(numericValue),
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
overlayColor: const MaterialStatePropertyAll(Colors.grey),
|
overlayColor: const WidgetStatePropertyAll(Colors.grey),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
|
|
|
@ -9,14 +9,14 @@ class ThemeModeButton extends StatelessWidget {
|
||||||
final bool? disabled;
|
final bool? disabled;
|
||||||
|
|
||||||
const ThemeModeButton({
|
const ThemeModeButton({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.selected,
|
required this.selected,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
this.disabled
|
this.disabled
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -32,19 +32,19 @@ class ThemeModeButton extends StatelessWidget {
|
||||||
? () => onChanged(value)
|
? () => onChanged(value)
|
||||||
: null,
|
: null,
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
elevation: MaterialStateProperty.all(0),
|
elevation: WidgetStateProperty.all(0),
|
||||||
shape: MaterialStateProperty.all(
|
shape: WidgetStateProperty.all(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: BorderRadius.circular(30),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
backgroundColor: MaterialStateProperty.all(
|
backgroundColor: WidgetStateProperty.all(
|
||||||
value == selected
|
value == selected
|
||||||
? disabled == null || disabled == false
|
? disabled == null || disabled == false
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: greyBackgroundColor
|
: greyBackgroundColor
|
||||||
: disabled == null || disabled == false
|
: disabled == null || disabled == false
|
||||||
? Theme.of(context).colorScheme.surfaceVariant
|
? Theme.of(context).colorScheme.surfaceTint.withOpacity(0.1)
|
||||||
: greyBackgroundColor,
|
: greyBackgroundColor,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
@ -67,8 +68,7 @@ class __ContentState extends State<_Content> {
|
||||||
bool validData = false;
|
bool validData = false;
|
||||||
|
|
||||||
void validateMac(String value) {
|
void validateMac(String value) {
|
||||||
final RegExp macRegex = RegExp(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$');
|
if (Regexps.macAddress.hasMatch(value)) {
|
||||||
if (macRegex.hasMatch(value)) {
|
|
||||||
setState(() => macError = null);
|
setState(() => macError = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -78,8 +78,7 @@ class __ContentState extends State<_Content> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateIp(String value) {
|
void validateIp(String value) {
|
||||||
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
|
if (Regexps.ipv4Address.hasMatch(value) == true) {
|
||||||
if (ipAddress.hasMatch(value) == true) {
|
|
||||||
setState(() => ipError = null);
|
setState(() => ipError = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -5,9 +5,9 @@ class DeleteStaticLeaseModal extends StatelessWidget {
|
||||||
final void Function() onConfirm;
|
final void Function() onConfirm;
|
||||||
|
|
||||||
const DeleteStaticLeaseModal({
|
const DeleteStaticLeaseModal({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.onConfirm
|
required this.onConfirm
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -100,8 +101,8 @@ class _DhcpScreenState extends State<DhcpScreen> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final regex = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$');
|
|
||||||
if (regex.hasMatch(value)) {
|
if (Regexps.ipv4Address.hasMatch(value)) {
|
||||||
setValue(null);
|
setValue(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -126,8 +127,8 @@ class _DhcpScreenState extends State<DhcpScreen> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)) {
|
if (Regexps.ipv6Address.hasMatch(value)) {
|
||||||
setValue(null);
|
setValue(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -221,14 +222,14 @@ class _DhcpScreenState extends State<DhcpScreen> {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
processModal.close();
|
processModal.close();
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.settingsSaved,
|
label: AppLocalizations.of(context)!.settingsSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.settingsNotSaved,
|
label: AppLocalizations.of(context)!.settingsNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -245,14 +246,14 @@ class _DhcpScreenState extends State<DhcpScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
clearAll();
|
clearAll();
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.configRestored,
|
label: AppLocalizations.of(context)!.configRestored,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.configNotRestored,
|
label: AppLocalizations.of(context)!.configNotRestored,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -276,14 +277,14 @@ class _DhcpScreenState extends State<DhcpScreen> {
|
||||||
data.dhcpStatus!.leases = [];
|
data.dhcpStatus!.leases = [];
|
||||||
dhcpProvider.setDhcpData(data);
|
dhcpProvider.setDhcpData(data);
|
||||||
|
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.leasesRestored,
|
label: AppLocalizations.of(context)!.leasesRestored,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.leasesNotRestored,
|
label: AppLocalizations.of(context)!.leasesNotRestored,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -43,14 +43,14 @@ class DhcpLeases extends StatelessWidget {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.staticLeaseDeleted,
|
label: AppLocalizations.of(context)!.staticLeaseDeleted,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.staticLeaseNotDeleted,
|
label: AppLocalizations.of(context)!.staticLeaseNotDeleted,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -67,28 +67,28 @@ class DhcpLeases extends StatelessWidget {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.staticLeaseCreated,
|
label: AppLocalizations.of(context)!.staticLeaseCreated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.content == "already_exists") {
|
else if (result.successful == false && result.content == "already_exists") {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.staticLeaseExists,
|
label: AppLocalizations.of(context)!.staticLeaseExists,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.content == "server_not_configured") {
|
else if (result.successful == false && result.content == "server_not_configured") {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.serverNotConfigured,
|
label: AppLocalizations.of(context)!.serverNotConfigured,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.staticLeaseNotCreated,
|
label: AppLocalizations.of(context)!.staticLeaseNotCreated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// 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:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -23,8 +24,7 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
||||||
bool validValues = false;
|
bool validValues = false;
|
||||||
|
|
||||||
void validateIp(Map<String, dynamic> field, String value) {
|
void validateIp(Map<String, dynamic> field, String value) {
|
||||||
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 (Regexps.ipv4Address.hasMatch(value) == true || Regexps.ipv6Address.hasMatch(value) == true) {
|
||||||
if (ipAddress.hasMatch(value) == true) {
|
|
||||||
setState(() => field['error'] = null);
|
setState(() => field['error'] = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -80,21 +80,21 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.statusCode == 400) {
|
else if (result.successful == false && result.statusCode == 400) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.someValueNotValid,
|
label: AppLocalizations.of(context)!.someValueNotValid,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -197,7 +197,7 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)).toList(),
|
)),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
|
@ -86,21 +86,21 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful== false && result.statusCode == 400) {
|
else if (result.successful== false && result.statusCode == 400) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.someValueNotValid,
|
label: AppLocalizations.of(context)!.someValueNotValid,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -139,14 +139,14 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
|
||||||
void clearCache() async {
|
void clearCache() async {
|
||||||
final result = await clearDnsCache(context, serversProvider.selectedServer!);
|
final result = await clearDnsCache(context, serversProvider.selectedServer!);
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsCacheCleared,
|
label: AppLocalizations.of(context)!.dnsCacheCleared,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsCacheNotCleared,
|
label: AppLocalizations.of(context)!.dnsCacheNotCleared,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -66,14 +66,14 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
void clearCache() async {
|
void clearCache() async {
|
||||||
final result = await clearDnsCache(context, serversProvider.selectedServer!);
|
final result = await clearDnsCache(context, serversProvider.selectedServer!);
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsCacheCleared,
|
label: AppLocalizations.of(context)!.dnsCacheCleared,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsCacheNotCleared,
|
label: AppLocalizations.of(context)!.dnsCacheNotCleared,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -183,21 +183,21 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.statusCode == 400) {
|
else if (result.successful == false && result.statusCode == 400) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.someValueNotValid,
|
label: AppLocalizations.of(context)!.someValueNotValid,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dns/comment_modal.dart';
|
import 'package:adguard_home_manager/screens/settings/dns/comment_modal.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -24,9 +25,7 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
|
||||||
bool validValues = false;
|
bool validValues = false;
|
||||||
|
|
||||||
void validateIp(Map<String, dynamic> field, String value) {
|
void validateIp(Map<String, dynamic> field, String value) {
|
||||||
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 (Regexps.ipv4Address.hasMatch(value) == true || Regexps.ipv6Address.hasMatch(value) || Regexps.url.hasMatch(value) == true) {
|
||||||
RegExp url = RegExp(r'(https?|tls):\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)');
|
|
||||||
if (ipAddress.hasMatch(value) == true || url.hasMatch(value) == true) {
|
|
||||||
setState(() => field['error'] = null);
|
setState(() => field['error'] = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -91,21 +90,21 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.statusCode == 400) {
|
else if (result.successful == false && result.statusCode == 400) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.someValueNotValid,
|
label: AppLocalizations.of(context)!.someValueNotValid,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -112,21 +112,21 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.statusCode == 400) {
|
else if (result.successful == false && result.statusCode == 400) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.someValueNotValid,
|
label: AppLocalizations.of(context)!.someValueNotValid,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -33,8 +34,7 @@ class _RateLimitAllowlistModalState extends State<RateLimitAllowlistModal> {
|
||||||
List<_IpListItemController> _controllersList = [];
|
List<_IpListItemController> _controllersList = [];
|
||||||
|
|
||||||
void _validateIp(String value, _IpListItemController item) {
|
void _validateIp(String value, _IpListItemController item) {
|
||||||
final regexp = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
|
if (Regexps.ipv4Address.hasMatch(value)) {
|
||||||
if (regexp.hasMatch(value)) {
|
|
||||||
setState(() => _controllersList = _controllersList.map((e) {
|
setState(() => _controllersList = _controllersList.map((e) {
|
||||||
if (e.id == item.id) {
|
if (e.id == item.id) {
|
||||||
return _IpListItemController(
|
return _IpListItemController(
|
||||||
|
|
|
@ -29,6 +29,10 @@ class _TestUpstreamDnsModalState extends State<TestUpstreamDnsModal> {
|
||||||
|
|
||||||
void checkDns() async {
|
void checkDns() async {
|
||||||
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
|
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
|
||||||
|
if (dnsProvider.dnsInfo == null) {
|
||||||
|
setState(() => loadStatus = LoadStatus.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final result = await Provider.of<ServersProvider>(context, listen: false).apiClient2!.testUpstreamDns(
|
final result = await Provider.of<ServersProvider>(context, listen: false).apiClient2!.testUpstreamDns(
|
||||||
body: {
|
body: {
|
||||||
"bootstrap_dns": dnsProvider.dnsInfo!.bootstrapDns,
|
"bootstrap_dns": dnsProvider.dnsInfo!.bootstrapDns,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -30,6 +28,9 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
|
|
||||||
bool validValues = false;
|
bool validValues = false;
|
||||||
|
|
||||||
|
final upstreamTimeoutController = TextEditingController();
|
||||||
|
String? upstreamTimeoutError = null;
|
||||||
|
|
||||||
checkValidValues() {
|
checkValidValues() {
|
||||||
if (
|
if (
|
||||||
dnsServers.isNotEmpty &&
|
dnsServers.isNotEmpty &&
|
||||||
|
@ -61,6 +62,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
upstreamMode = dnsProvider.dnsInfo!.upstreamMode ?? "";
|
upstreamMode = dnsProvider.dnsInfo!.upstreamMode ?? "";
|
||||||
|
upstreamTimeoutController.text = dnsProvider.dnsInfo!.upstreamTimeout != null ? dnsProvider.dnsInfo!.upstreamTimeout.toString() : "";
|
||||||
validValues = true;
|
validValues = true;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -72,6 +74,23 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
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() {
|
void openAddCommentModal() {
|
||||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -146,27 +165,29 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
|
|
||||||
final result = await dnsProvider.saveUpstreamDnsConfig({
|
final result = await dnsProvider.saveUpstreamDnsConfig({
|
||||||
"upstream_dns": dnsServers.map((e) => e['controller'] != null ? e['controller'].text : e['comment']).toList(),
|
"upstream_dns": dnsServers.map((e) => e['controller'] != null ? e['controller'].text : e['comment']).toList(),
|
||||||
"upstream_mode": upstreamMode
|
"upstream_mode": upstreamMode,
|
||||||
|
"upstream_timeout": int.tryParse(upstreamTimeoutController.text)
|
||||||
});
|
});
|
||||||
|
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
label: AppLocalizations.of(context)!.dnsConfigSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (result.successful == false && result.statusCode == 400) {
|
else if (result.successful == false && result.statusCode == 400) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.someValueNotValid,
|
label: AppLocalizations.of(context)!.someValueNotValid,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -312,6 +333,27 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription,
|
subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription,
|
||||||
onChanged: (value) => setState(() => upstreamMode = value),
|
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)!.upstreamTimeout,
|
||||||
|
helperText: AppLocalizations.of(context)!.upstreamTimeoutHelper,
|
||||||
|
helperMaxLines: 2,
|
||||||
|
errorText: upstreamTimeoutError
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,9 +5,9 @@ class DeleteDnsRewrite extends StatelessWidget {
|
||||||
final void Function() onConfirm;
|
final void Function() onConfirm;
|
||||||
|
|
||||||
const DeleteDnsRewrite({
|
const DeleteDnsRewrite({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.onConfirm
|
required this.onConfirm
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
@ -82,8 +83,7 @@ class _ContentState extends State<_Content> {
|
||||||
bool validData = false;
|
bool validData = false;
|
||||||
|
|
||||||
void validateDomain(String value) {
|
void validateDomain(String value) {
|
||||||
final domainRegex = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
|
if (Regexps.wildcardDomain.hasMatch(value)) {
|
||||||
if (domainRegex.hasMatch(value)) {
|
|
||||||
setState(() => domainError = null);
|
setState(() => domainError = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -67,14 +67,14 @@ class _DnsRewritesScreenState extends State<DnsRewritesScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsRewriteRuleDeleted,
|
label: AppLocalizations.of(context)!.dnsRewriteRuleDeleted,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsRewriteRuleNotDeleted,
|
label: AppLocalizations.of(context)!.dnsRewriteRuleNotDeleted,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -91,14 +91,14 @@ class _DnsRewritesScreenState extends State<DnsRewritesScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsRewriteRuleAdded,
|
label: AppLocalizations.of(context)!.dnsRewriteRuleAdded,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsRewriteRuleNotAdded,
|
label: AppLocalizations.of(context)!.dnsRewriteRuleNotAdded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -115,14 +115,14 @@ class _DnsRewritesScreenState extends State<DnsRewritesScreen> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsRewriteRuleUpdated,
|
label: AppLocalizations.of(context)!.dnsRewriteRuleUpdated,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.dnsRewriteRuleNotUpdated,
|
label: AppLocalizations.of(context)!.dnsRewriteRuleNotUpdated,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -169,7 +169,7 @@ class _DnsRewritesScreenState extends State<DnsRewritesScreen> {
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
final result = await rewriteRulesProvider.fetchRules();
|
final result = await rewriteRulesProvider.fetchRules();
|
||||||
if (result == false) {
|
if (result == false) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.rewriteRulesNotLoaded,
|
label: AppLocalizations.of(context)!.rewriteRulesNotLoaded,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -5,9 +5,9 @@ class EncryptionErrorModal extends StatelessWidget {
|
||||||
final String error;
|
final String error;
|
||||||
|
|
||||||
const EncryptionErrorModal({
|
const EncryptionErrorModal({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.error,
|
required this.error,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ class EncryptionTextField extends StatelessWidget {
|
||||||
final String? helperText;
|
final String? helperText;
|
||||||
|
|
||||||
const EncryptionTextField({
|
const EncryptionTextField({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.enabled,
|
required this.enabled,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
|
@ -22,7 +22,7 @@ class EncryptionTextField extends StatelessWidget {
|
||||||
this.keyboardType,
|
this.keyboardType,
|
||||||
this.multiline,
|
this.multiline,
|
||||||
this.helperText,
|
this.helperText,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -233,14 +233,14 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.encryptionConfigSaved,
|
label: AppLocalizations.of(context)!.encryptionConfigSaved,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.encryptionConfigNotSaved,
|
label: AppLocalizations.of(context)!.encryptionConfigNotSaved,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
@ -282,14 +282,14 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
||||||
processModal.close();
|
processModal.close();
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.configurationResetSuccessfully,
|
label: AppLocalizations.of(context)!.configurationResetSuccessfully,
|
||||||
color: Colors.green
|
color: Colors.green
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showSnacbkar(
|
showSnackbar(
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
label: AppLocalizations.of(context)!.configurationResetError,
|
label: AppLocalizations.of(context)!.configurationResetError,
|
||||||
color: Colors.red
|
color: Colors.red
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
|
||||||
String? validateDomain(BuildContext context, String domain) {
|
String? validateDomain(BuildContext context, String domain) {
|
||||||
RegExp regExp = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
|
if (Regexps.domain.hasMatch(domain)) {
|
||||||
if (regExp.hasMatch(domain)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -23,8 +23,7 @@ String? validatePort(BuildContext context, String value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String? validateCertificate(BuildContext context, String cert) {
|
String? validateCertificate(BuildContext context, String cert) {
|
||||||
final regExp = RegExp(r'(-{3,}(\bBEGIN CERTIFICATE\b))|(-{3,}-{3,}(\END CERTIFICATE\b)-{3,})', multiLine: true);
|
if (Regexps.certificate.hasMatch(cert.replaceAll('\n', ''))) {
|
||||||
if (regExp.hasMatch(cert.replaceAll('\n', ''))) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -33,8 +32,7 @@ String? validateCertificate(BuildContext context, String cert) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String? validatePrivateKey(BuildContext context, String cert) {
|
String? validatePrivateKey(BuildContext context, String cert) {
|
||||||
final regExp = RegExp(r'(-{3,}(\bBEGIN\b).*(PRIVATE KEY\b))|(-{3,}-{3,}(\bEND\b).*(PRIVATE KEY\b)-{3,})', multiLine: true);
|
if (Regexps.privateKey.hasMatch(cert.replaceAll('\n', ''))) {
|
||||||
if (regExp.hasMatch(cert.replaceAll('\n', ''))) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -43,8 +41,7 @@ String? validatePrivateKey(BuildContext context, String cert) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String? validatePath(BuildContext context, String cert) {
|
String? validatePath(BuildContext context, String cert) {
|
||||||
final regExp = RegExp(r'^(\/{0,1}(?!\/))[A-Za-z0-9\/\-_]+(\.([a-zA-Z]+))?$');
|
if (Regexps.path.hasMatch(cert)) {
|
||||||
if (regExp.hasMatch(cert)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -5,9 +5,9 @@ class ErrorMessageEncryption extends StatelessWidget {
|
||||||
final String errorMessage;
|
final String errorMessage;
|
||||||
|
|
||||||
const ErrorMessageEncryption({
|
const ErrorMessageEncryption({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.errorMessage,
|
required this.errorMessage,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue