mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-06-30 12:59:52 +00:00
Compare commits
154 commits
2.16.1_(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 | ||
|
b56234be91 | ||
|
d48687bfa0 | ||
|
6ab603d459 | ||
|
7e13285b7e | ||
|
999da666b4 | ||
|
fac793a3a2 | ||
|
5be5377110 | ||
|
ecc9cf1073 | ||
|
fd25088791 | ||
|
e013b1496e | ||
|
b270ca2b8c | ||
|
a3c63ffd9a | ||
|
e88b61eb90 | ||
|
ac5be8b500 | ||
|
4b3129656b | ||
|
a8d0bd95a6 | ||
|
5fdeaa80b7 | ||
|
806e74ca9f | ||
|
31ad3fcf6a | ||
|
d65ea6520c | ||
|
ce8d38958e | ||
|
ad6cb92d4b | ||
|
442c7d9264 | ||
|
a0b566446b | ||
|
4d8660d363 | ||
|
a58b97a1bd | ||
|
2a075816b3 | ||
|
59a7a31a4f | ||
|
654284b46a | ||
|
019367ca93 | ||
|
7c0b592715 | ||
|
133d29aa91 | ||
|
dac07edd89 | ||
|
ba2a27fef0 | ||
|
9de9b0afec | ||
|
0821fd4e0e | ||
|
8859468a66 | ||
|
19ae91905f | ||
|
7087bd5aee | ||
|
f624fdbc43 | ||
|
0a2a86ef81 | ||
|
693e66e125 | ||
|
e603814d42 | ||
|
7a89aea3a5 | ||
|
dcad63fe5c | ||
|
bce93fa5ca | ||
|
e21e34668d | ||
|
6629237548 | ||
|
923fb97f03 | ||
|
568b879054 |
160 changed files with 4293 additions and 2728 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
|
||||||
|
|
20
.vscode/settings.json
vendored
Normal file
20
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
"editor.formatOnPaste": false,
|
||||||
|
"editor.formatOnType": false,
|
||||||
|
"editor.defaultFormatter": "Dart-Code.flutter",
|
||||||
|
"dart.lineLength": 120,
|
||||||
|
"[dart]": {
|
||||||
|
"editor.rulers": [
|
||||||
|
120
|
||||||
|
],
|
||||||
|
"editor.defaultFormatter": "Dart-Code.dart-code",
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
"editor.formatOnPaste": false,
|
||||||
|
"editor.formatOnType": false
|
||||||
|
},
|
||||||
|
"cSpell.ignorePaths": [
|
||||||
|
"/pubspec.yaml",
|
||||||
|
"/.github/workflows"
|
||||||
|
],
|
||||||
|
}
|
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 33
|
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 33
|
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
|
|
||||||
);
|
|
||||||
print(update);
|
|
||||||
if (update == true) {
|
|
||||||
setUpdateAvailable(gitHubRelease);
|
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
if (
|
|
||||||
installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE ||
|
|
||||||
installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER ||
|
|
||||||
installationSource == Source.UNKNOWN
|
|
||||||
) {
|
|
||||||
return gitHubRelease;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Platform.isIOS) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return gitHubRelease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setUpdateAvailable(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
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,
|
||||||
|
|
|
@ -754,6 +754,8 @@
|
||||||
"statisticsSettingsDescription": "Configure data collection for statistics",
|
"statisticsSettingsDescription": "Configure data collection for statistics",
|
||||||
"loadingStatisticsSettings": "Loading statistics settings...",
|
"loadingStatisticsSettings": "Loading statistics settings...",
|
||||||
"statisticsSettingsLoadError": "An error occured when loading statistics settings.",
|
"statisticsSettingsLoadError": "An error occured when loading statistics settings.",
|
||||||
|
"statisticsConfigUpdated": "Statistics settings updated successfully",
|
||||||
|
"statisticsConfigNotUpdated": "Statistics settings couldn't be updated",
|
||||||
"customTimeInHours": "Custom time (in hours)",
|
"customTimeInHours": "Custom time (in hours)",
|
||||||
"invalidTime": "Invalid time",
|
"invalidTime": "Invalid time",
|
||||||
"removeDomain": "Remove domain",
|
"removeDomain": "Remove domain",
|
||||||
|
@ -774,5 +776,35 @@
|
||||||
"showHide": "Show/hide",
|
"showHide": "Show/hide",
|
||||||
"noElementsReorderMessage": "Enable some elements on the show/hide tab to reorder them here.",
|
"noElementsReorderMessage": "Enable some elements on the show/hide tab to reorder them here.",
|
||||||
"enablePlainDns": "Enable plain DNS",
|
"enablePlainDns": "Enable plain DNS",
|
||||||
"enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol."
|
"enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol.",
|
||||||
|
"date": "Date",
|
||||||
|
"loadingChangelog": "Loading changelog...",
|
||||||
|
"invalidIpOrUrl": "Invalid IP address or URL",
|
||||||
|
"addPersistentClient": "Add as a persistent client",
|
||||||
|
"blockThisClientOnly": "Block for this client only",
|
||||||
|
"unblockThisClientOnly": "Unblock for this client only",
|
||||||
|
"domainBlockedThisClient": "{domain} blocked for this client",
|
||||||
|
"domainUnblockedThisClient": "{domain} unblocked for this client",
|
||||||
|
"disallowThisClient": "Disallow this client",
|
||||||
|
"allowThisClient": "Allow this client",
|
||||||
|
"clientAllowedSuccessfully": "Client allowed successfully",
|
||||||
|
"clientDisallowedSuccessfully": "Client disallowed successfully",
|
||||||
|
"changesNotSaved": "Changes could not be saved",
|
||||||
|
"allowingClient": "Allowing client...",
|
||||||
|
"disallowingClient": "Disallowing client...",
|
||||||
|
"clientIpCopied": "Client IP copied to the clipboard",
|
||||||
|
"clientNameCopied": "Client name copied to the clipboard",
|
||||||
|
"dnsServerAddressCopied": "DNS server address copied to the clipboard",
|
||||||
|
"select": "Select",
|
||||||
|
"liveLogs": "Live logs",
|
||||||
|
"hereWillAppearRealtimeLogs": "Here there will appear the logs on realtime.",
|
||||||
|
"applicationDetails": "Application details",
|
||||||
|
"applicationDetailsDescription": "App repository, stores where it's available, and more",
|
||||||
|
"myOtherApps": "My other apps",
|
||||||
|
"myOtherAppsDescription": "Check my other apps, make a donation, contact support, and more",
|
||||||
|
"topToBottom": "From top to bottom",
|
||||||
|
"bottomToTop": "From bottom to top",
|
||||||
|
"upstreamTimeout": "Upstream timeout",
|
||||||
|
"upstreamTimeoutHelper": "Specifies the number of seconds to wait for a response from the upstream server",
|
||||||
|
"fieldCannotBeEmpty": "This field cannot be empty"
|
||||||
}
|
}
|
|
@ -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",
|
||||||
|
@ -754,6 +754,8 @@
|
||||||
"statisticsSettingsDescription": "Configura la recolección de datos para estadísticas",
|
"statisticsSettingsDescription": "Configura la recolección de datos para estadísticas",
|
||||||
"loadingStatisticsSettings": "Cargando ajustes de estadísticas...",
|
"loadingStatisticsSettings": "Cargando ajustes de estadísticas...",
|
||||||
"statisticsSettingsLoadError": "Ocurrió un error al cargar los ajustes de estadísticas.",
|
"statisticsSettingsLoadError": "Ocurrió un error al cargar los ajustes de estadísticas.",
|
||||||
|
"statisticsConfigNotUpdated": "La configuración de estadísticas no pudo ser actualizada.",
|
||||||
|
"statisticsConfigUpdated": "Configuración de estadísticas actualizada correctamente.",
|
||||||
"customTimeInHours": "Tiempo personalizado (en horas)",
|
"customTimeInHours": "Tiempo personalizado (en horas)",
|
||||||
"invalidTime": "Tiempo no válido",
|
"invalidTime": "Tiempo no válido",
|
||||||
"removeDomain": "Eliminar dominio",
|
"removeDomain": "Eliminar dominio",
|
||||||
|
@ -774,5 +776,35 @@
|
||||||
"showHide": "Mostrar/ocultar",
|
"showHide": "Mostrar/ocultar",
|
||||||
"noElementsReorderMessage": "Activa algunos elementos en la pestaña de mostrar/ocultar para reordenarlos aquí.",
|
"noElementsReorderMessage": "Activa algunos elementos en la pestaña de mostrar/ocultar para reordenarlos aquí.",
|
||||||
"enablePlainDns": "Activar DNS simple (sin cifrado)",
|
"enablePlainDns": "Activar DNS simple (sin cifrado)",
|
||||||
"enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado."
|
"enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado.",
|
||||||
|
"date": "Fecha",
|
||||||
|
"loadingChangelog": "Cargando registro de cambios...",
|
||||||
|
"invalidIpOrUrl": "Dirección IP o URL no válida",
|
||||||
|
"addPersistentClient": "Añadir como cliente persistente",
|
||||||
|
"blockThisClientOnly": "Bloquear sólo para este cliente",
|
||||||
|
"unblockThisClientOnly": "Desbloquear sólo para este cliente",
|
||||||
|
"domainBlockedThisClient": "{domain} bloqueado para este cliente",
|
||||||
|
"domainUnblockedThisClient": "{domain} desbloqueado para este cliente",
|
||||||
|
"disallowThisClient": "No permitir este cliente",
|
||||||
|
"allowThisClient": "Permitir este cliente",
|
||||||
|
"clientAllowedSuccessfully": "Cliente permitido correctamente",
|
||||||
|
"clientDisallowedSuccessfully": "Cliente no permitido correctamente",
|
||||||
|
"changesNotSaved": "Los cambios no han podido ser guardados",
|
||||||
|
"allowingClient": "Permitiendo cliente...",
|
||||||
|
"disallowingClient": "No permitiendo cliente...",
|
||||||
|
"clientIpCopied": "Dirección IP del cliente copiada al portapapeles",
|
||||||
|
"clientNameCopied": "Nombre del cliente copiado al portapapeles",
|
||||||
|
"dnsServerAddressCopied": "Dirección del servidor DNS copiada al portapapeles",
|
||||||
|
"select": "Seleccionar",
|
||||||
|
"liveLogs": "Registros en directo",
|
||||||
|
"hereWillAppearRealtimeLogs": "Aquí aparecerán los registros en tiempo real.",
|
||||||
|
"applicationDetails": "Detalles de la aplicación",
|
||||||
|
"applicationDetailsDescription": "Repositorio de la app, tiendas donde está disponible, y más",
|
||||||
|
"myOtherApps": "Mis otras apps",
|
||||||
|
"myOtherAppsDescription": "Comprueba mis otras apps, hacer una donación, contactar al soporte, y más",
|
||||||
|
"topToBottom": "Desde arriba hacia abajo",
|
||||||
|
"bottomToTop": "Desde abajo hacia arriba",
|
||||||
|
"upstreamTimeout": "Tiempo de espera del upstream",
|
||||||
|
"upstreamTimeoutHelper": "Especifica el número de segundos que se debe esperar para recibir una respuesta del servidor upstream",
|
||||||
|
"fieldCannotBeEmpty": "El campo no puede estar vacío"
|
||||||
}
|
}
|
|
@ -75,7 +75,7 @@
|
||||||
"userNotEmpty": "Kullanıcı adı boş bırakılamaz",
|
"userNotEmpty": "Kullanıcı adı boş bırakılamaz",
|
||||||
"passwordNotEmpty": "Şifre boş bırakılamaz",
|
"passwordNotEmpty": "Şifre boş bırakılamaz",
|
||||||
"examplePath": "Örnek: /adguard",
|
"examplePath": "Örnek: /adguard",
|
||||||
"helperPath": "Ters proxy kullanıyorsanız",
|
"helperPath": "Eğer ters proxy kullanıyorsanız",
|
||||||
"aboutApp": "Uygulama hakkında",
|
"aboutApp": "Uygulama hakkında",
|
||||||
"appVersion": "Uygulama sürümü",
|
"appVersion": "Uygulama sürümü",
|
||||||
"createdBy": "Geliştirici",
|
"createdBy": "Geliştirici",
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
"logsCopiedClipboard": "Günlükler panoya kopyalandı",
|
"logsCopiedClipboard": "Günlükler panoya kopyalandı",
|
||||||
"advancedSettings": "Gelişmiş ayarlar",
|
"advancedSettings": "Gelişmiş ayarlar",
|
||||||
"dontCheckCertificate": "SSL sertifikasını kontrol etme",
|
"dontCheckCertificate": "SSL sertifikasını kontrol etme",
|
||||||
"dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar",
|
"dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar.",
|
||||||
"advancedSetupDescription": "Gelişmiş seçenekleri yönet",
|
"advancedSetupDescription": "Gelişmiş seçenekleri yönet",
|
||||||
"settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.",
|
"settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.",
|
||||||
"cannotUpdateSettings": "Ayarlar güncellenemiyor.",
|
"cannotUpdateSettings": "Ayarlar güncellenemiyor.",
|
||||||
|
@ -131,11 +131,11 @@
|
||||||
"rewrite": "Yeniden Yaz",
|
"rewrite": "Yeniden Yaz",
|
||||||
"status": "Durum",
|
"status": "Durum",
|
||||||
"result": "Sonuç",
|
"result": "Sonuç",
|
||||||
"time": "Zaman",
|
"time": "Saat",
|
||||||
"blocklist": "Engelleme Listesi",
|
"blocklist": "Engelleme Listesi",
|
||||||
"request": "İstek",
|
"request": "İstek",
|
||||||
"domain": "Alan adı",
|
"domain": "Alan adı",
|
||||||
"type": "Tip",
|
"type": "Tür",
|
||||||
"clas": "Sınıf",
|
"clas": "Sınıf",
|
||||||
"response": "Yanıt",
|
"response": "Yanıt",
|
||||||
"dnsServer": "DNS sunucusu",
|
"dnsServer": "DNS sunucusu",
|
||||||
|
@ -204,7 +204,7 @@
|
||||||
"noUpstreamServers": "Üst sunucu yok.",
|
"noUpstreamServers": "Üst sunucu yok.",
|
||||||
"willBeUsedGeneralServers": "Genel üst sunucular kullanılacak.",
|
"willBeUsedGeneralServers": "Genel üst sunucular kullanılacak.",
|
||||||
"added": "Eklenenler",
|
"added": "Eklenenler",
|
||||||
"clientUpdatedSuccessfully": "İstemci başarıyla güncellendi",
|
"clientUpdatedSuccessfully": "İstemci ayarları başarıyla güncellendi",
|
||||||
"clientNotUpdated": "İstemci güncellenemedi",
|
"clientNotUpdated": "İstemci güncellenemedi",
|
||||||
"clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı",
|
"clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı",
|
||||||
"clientNotDeleted": "İstemci silinemedi",
|
"clientNotDeleted": "İstemci silinemedi",
|
||||||
|
@ -308,8 +308,8 @@
|
||||||
"addImportant": "Başına $important ekle",
|
"addImportant": "Başına $important ekle",
|
||||||
"howCreateRules": "Özel kurallar nasıl oluşturulur?",
|
"howCreateRules": "Özel kurallar nasıl oluşturulur?",
|
||||||
"examples": "Örnekler",
|
"examples": "Örnekler",
|
||||||
"example1": "example.org (ornek.org) ve tüm alt alan adlarına erişimi engeller.",
|
"example1": "example.org ve tüm alt alan adlarına erişimi engeller.",
|
||||||
"example2": "example.org (ornek.org) ve tüm alt alan adlarına erişimi engellemeyi kaldırır.",
|
"example2": "example.org ve tüm alt alan adlarına erişimi engellemeyi kaldırır.",
|
||||||
"example3": "Yorum ekler.",
|
"example3": "Yorum ekler.",
|
||||||
"example4": "Belirtilen düzenli ifadeye uyan alan adlarına erişimi engeller.",
|
"example4": "Belirtilen düzenli ifadeye uyan alan adlarına erişimi engeller.",
|
||||||
"moreInformation": "Daha fazla bilgi",
|
"moreInformation": "Daha fazla bilgi",
|
||||||
|
@ -418,7 +418,7 @@
|
||||||
"logSettingsNotLoaded": "Günlük ayarları yüklenemedi.",
|
"logSettingsNotLoaded": "Günlük ayarları yüklenemedi.",
|
||||||
"updatingSettings": "Ayarlar güncelleniyor...",
|
"updatingSettings": "Ayarlar güncelleniyor...",
|
||||||
"logsConfigUpdated": "Günlük ayarları başarıyla güncellendi",
|
"logsConfigUpdated": "Günlük ayarları başarıyla güncellendi",
|
||||||
"logsConfigNotUpdated": "Günlük ayarları başarıyla güncellendi",
|
"logsConfigNotUpdated": "Günlük ayarları güncellenemedi",
|
||||||
"deletingLogs": "Günlükler temizleniyor...",
|
"deletingLogs": "Günlükler temizleniyor...",
|
||||||
"logsCleared": "Günlükler başarıyla temizlendi",
|
"logsCleared": "Günlükler başarıyla temizlendi",
|
||||||
"logsNotCleared": "Günlükler temizlenemedi",
|
"logsNotCleared": "Günlükler temizlenemedi",
|
||||||
|
@ -436,7 +436,7 @@
|
||||||
"parallelRequests": "Paralel istekler",
|
"parallelRequests": "Paralel istekler",
|
||||||
"fastestIpAddress": "En hızlı IP adresi",
|
"fastestIpAddress": "En hızlı IP adresi",
|
||||||
"loadBalancingDescription": "Her seferinde bir üst sunucuya sorgu yapar. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.",
|
"loadBalancingDescription": "Her seferinde bir üst sunucuya sorgu yapar. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.",
|
||||||
"parallelRequestsDescription": "Tüm üst sunucuları aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanır.",
|
"parallelRequestsDescription": "Tüm üst sunucuları aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanılır.",
|
||||||
"fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürür. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
|
"fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürür. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
|
||||||
"noBootstrapDns": "Önyükleme DNS sunucuları eklenmedi.",
|
"noBootstrapDns": "Önyükleme DNS sunucuları eklenmedi.",
|
||||||
"bootstrapDnsServersInfo": "Önyükleme DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.",
|
"bootstrapDnsServersInfo": "Önyükleme DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.",
|
||||||
|
@ -469,7 +469,7 @@
|
||||||
"customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verir.",
|
"customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verir.",
|
||||||
"dnsCacheConfig": "DNS önbellek yapılandırması",
|
"dnsCacheConfig": "DNS önbellek yapılandırması",
|
||||||
"cacheSize": "Önbellek boyutu",
|
"cacheSize": "Önbellek boyutu",
|
||||||
"inBytes": "Alınacak önbelleğin boyutunu ayarla (bayt olarak)",
|
"inBytes": "Alınacak önbelleğin boyutunu ayarla (Bayt olarak)",
|
||||||
"overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl",
|
"overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl",
|
||||||
"overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.",
|
"overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.",
|
||||||
"overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl",
|
"overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl",
|
||||||
|
@ -494,7 +494,7 @@
|
||||||
"dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır",
|
"dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır",
|
||||||
"comment": "Yorum",
|
"comment": "Yorum",
|
||||||
"address": "Adres",
|
"address": "Adres",
|
||||||
"commentsDescription": "Yorumlar her zaman # işareti ile başlar. Onu eklemenize gerek yok, otomatik olarak eklenir.",
|
"commentsDescription": "Yorumlar her zaman # işareti ile başlar. Eklemenize gerek yok, otomatik olarak eklenecektir.",
|
||||||
"encryptionSettings": "Şifreleme ayarları",
|
"encryptionSettings": "Şifreleme ayarları",
|
||||||
"encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği",
|
"encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği",
|
||||||
"loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...",
|
"loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...",
|
||||||
|
@ -560,8 +560,8 @@
|
||||||
"validPrivateKey": "Geçerli özel anahtar",
|
"validPrivateKey": "Geçerli özel anahtar",
|
||||||
"expirationDate": "Son kullanma tarihi",
|
"expirationDate": "Son kullanma tarihi",
|
||||||
"keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.",
|
"keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.",
|
||||||
"timeLogs": "Günlüklerde işlem süresi göster",
|
"timeLogs": "Günlüklerde işlem süresini göster",
|
||||||
"timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini göster.",
|
"timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini gösterir.",
|
||||||
"hostNames": "Ana bilgisayar adları",
|
"hostNames": "Ana bilgisayar adları",
|
||||||
"keyType": "Anahtar türü",
|
"keyType": "Anahtar türü",
|
||||||
"updateAvailable": "Güncelleme mevcut",
|
"updateAvailable": "Güncelleme mevcut",
|
||||||
|
@ -626,7 +626,7 @@
|
||||||
"appUpdates": "Uygulama güncellemeleri",
|
"appUpdates": "Uygulama güncellemeleri",
|
||||||
"usingLatestVersion": "En son sürümü kullanıyorsunuz",
|
"usingLatestVersion": "En son sürümü kullanıyorsunuz",
|
||||||
"ipLogs": "Günlüklerde IP adresini göster",
|
"ipLogs": "Günlüklerde IP adresini göster",
|
||||||
"ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster.",
|
"ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini gösterir.",
|
||||||
"application": "Uygulama",
|
"application": "Uygulama",
|
||||||
"combinedChart": "Birleştirilmiş grafik",
|
"combinedChart": "Birleştirilmiş grafik",
|
||||||
"combinedChartDescription": "Tüm grafikleri bir araya getirir.",
|
"combinedChartDescription": "Tüm grafikleri bir araya getirir.",
|
||||||
|
@ -688,7 +688,7 @@
|
||||||
"yourVersion": "Yüklü sürüm: {version}",
|
"yourVersion": "Yüklü sürüm: {version}",
|
||||||
"minimumRequiredVersion": "Gerekli minimum sürüm: {version}",
|
"minimumRequiredVersion": "Gerekli minimum sürüm: {version}",
|
||||||
"topUpstreams": "Öne çıkan DNS sunucuları",
|
"topUpstreams": "Öne çıkan DNS sunucuları",
|
||||||
"averageUpstreamResponseTime": "DNS sunucusu ortalama işlem süresi" ,
|
"averageUpstreamResponseTime": "DNS sunucuları işlem süresi" ,
|
||||||
"dhcpNotAvailable": "DHCP sunucusu kullanılamıyor.",
|
"dhcpNotAvailable": "DHCP sunucusu kullanılamıyor.",
|
||||||
"osServerInstalledIncompatible": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor.",
|
"osServerInstalledIncompatible": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor.",
|
||||||
"resetSettings": "Ayarları sıfırla",
|
"resetSettings": "Ayarları sıfırla",
|
||||||
|
@ -721,7 +721,7 @@
|
||||||
"unblockingClient": "İstemci engeli kaldırılıyor...",
|
"unblockingClient": "İstemci engeli kaldırılıyor...",
|
||||||
"upstreamDnsCacheConfiguration": "DNS önbellek yapılandırması",
|
"upstreamDnsCacheConfiguration": "DNS önbellek yapılandırması",
|
||||||
"enableDnsCachingClient": "Bu istemci için DNS önbelleğe almayı etkinleştir",
|
"enableDnsCachingClient": "Bu istemci için DNS önbelleğe almayı etkinleştir",
|
||||||
"dnsCacheSize": "DNS önbellek boyutu (bayt cinsinden)",
|
"dnsCacheSize": "DNS önbellek boyutu (Bayt cinsinden)",
|
||||||
"nameInvalid": "Ad gereklidir",
|
"nameInvalid": "Ad gereklidir",
|
||||||
"oneIdentifierRequired": "En az bir tanımlayıcı gereklidir",
|
"oneIdentifierRequired": "En az bir tanımlayıcı gereklidir",
|
||||||
"dnsCacheNumber": "DNS önbellek boyutu bir rakam içermelidir",
|
"dnsCacheNumber": "DNS önbellek boyutu bir rakam içermelidir",
|
||||||
|
@ -754,7 +754,9 @@
|
||||||
"statisticsSettingsDescription": "İstatistikler için veri toplamayı yapılandır",
|
"statisticsSettingsDescription": "İstatistikler için veri toplamayı yapılandır",
|
||||||
"loadingStatisticsSettings": "İstatistik ayarları yükleniyor...",
|
"loadingStatisticsSettings": "İstatistik ayarları yükleniyor...",
|
||||||
"statisticsSettingsLoadError": "İstatistik ayarları yüklenirken bir hata oluştu.",
|
"statisticsSettingsLoadError": "İstatistik ayarları yüklenirken bir hata oluştu.",
|
||||||
"customTimeInHours": "Özel zaman (saat olarak)",
|
"statisticsConfigUpdated": "İstatistik ayarları başarıyla güncellendi",
|
||||||
|
"statisticsConfigNotUpdated": "İstatistik ayarları güncellenemedi",
|
||||||
|
"customTimeInHours": "Özel zaman (Saat olarak)",
|
||||||
"invalidTime": "Geçersiz zaman",
|
"invalidTime": "Geçersiz zaman",
|
||||||
"removeDomain": "Alan adını kaldır",
|
"removeDomain": "Alan adını kaldır",
|
||||||
"addDomain": "Alan adı ekle",
|
"addDomain": "Alan adı ekle",
|
||||||
|
@ -774,5 +776,35 @@
|
||||||
"showHide": "Göster/gizle",
|
"showHide": "Göster/gizle",
|
||||||
"noElementsReorderMessage": "Burada yeniden sıralamak için göster/gizle sekmesindeki bazı öğeleri etkinleştirin.",
|
"noElementsReorderMessage": "Burada yeniden sıralamak için göster/gizle sekmesindeki bazı öğeleri etkinleştirin.",
|
||||||
"enablePlainDns": "Düz DNS'i etkinleştir",
|
"enablePlainDns": "Düz DNS'i etkinleştir",
|
||||||
"enablePlainDnsDescription": "Düz DNS varsayılan olarak etkindir. Tüm aygıtları şifrelenmiş DNS kullanmaya zorlamak için bunu devre dışı bırakabilirsiniz. Bunu yapmak için en az bir şifrelenmiş DNS protokolünü etkinleştirmeniz gerekir."
|
"enablePlainDnsDescription": "Düz DNS varsayılan olarak etkindir. Tüm aygıtları şifrelenmiş DNS kullanmaya zorlamak için bunu devre dışı bırakabilirsiniz. Bunu yapmak için en az bir şifrelenmiş DNS protokolünü etkinleştirmeniz gerekir.",
|
||||||
}
|
"date": "Tarih",
|
||||||
|
"loadingChangelog": "Değişiklikler yükleniyor...",
|
||||||
|
"invalidIpOrUrl": "Geçersiz IP adresi veya URL",
|
||||||
|
"addPersistentClient": "Kalıcı istemci olarak ekle",
|
||||||
|
"blockThisClientOnly": "Yalnızca bu istemci için engelle",
|
||||||
|
"unblockThisClientOnly": "Yalnızca bu istemci için engeli kaldır",
|
||||||
|
"domainBlockedThisClient": "Bu istemci için {domain} engellendi",
|
||||||
|
"domainUnblockedThisClient": "Bu istemci için {domain} engeli kaldırıldı",
|
||||||
|
"disallowThisClient": "Bu istemciye izin verme",
|
||||||
|
"allowThisClient": "Bu istemciye izin ver",
|
||||||
|
"clientAllowedSuccessfully": "İstemciye başarıyla izin verildi",
|
||||||
|
"clientDisallowedSuccessfully": "İstemci başarıyla reddedildi",
|
||||||
|
"changesNotSaved": "Değişiklikler kaydedilemedi",
|
||||||
|
"allowingClient": "İstemciye izin veriliyor...",
|
||||||
|
"disallowingClient": "İstemci reddediliyor...",
|
||||||
|
"clientIpCopied": "İstemci IP'si panoya kopyalandı",
|
||||||
|
"clientNameCopied": "İstemci adı panoya kopyalandı",
|
||||||
|
"dnsServerAddressCopied": "DNS sunucu adresi panoya kopyalandı",
|
||||||
|
"select": "Seç",
|
||||||
|
"liveLogs": "Canlı günlükler",
|
||||||
|
"hereWillAppearRealtimeLogs": "Burada gerçek zamanlı günlükler görünecek.",
|
||||||
|
"applicationDetails": "Uygulama detayları",
|
||||||
|
"applicationDetailsDescription": "Uygulama deposu, mevcut olduğu mağazalar ve daha fazlası",
|
||||||
|
"myOtherApps": "Diğer uygulamalarım",
|
||||||
|
"myOtherAppsDescription": "Diğer uygulamalarımı kontrol et, bağış yap, destekle iletişime geç ve daha fazlası",
|
||||||
|
"topToBottom": "Yukarıdan aşağıya",
|
||||||
|
"bottomToTop": "Aşağıdan yukarıya",
|
||||||
|
"upstreamTimeout": "Üst sunucu zaman aşımı",
|
||||||
|
"upstreamTimeoutHelper": "Üst DNS sunucusundan yanıt bekleme süresini saniye cinsinden belirtir",
|
||||||
|
"fieldCannotBeEmpty": "Bu alan boş olamaz"
|
||||||
|
}
|
||||||
|
|
|
@ -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,15 +5,13 @@ 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_size/window_size.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';
|
||||||
|
|
||||||
|
@ -40,7 +38,8 @@ void main() async {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
|
|
||||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||||
setWindowMinSize(const Size(500, 500));
|
await windowManager.ensureInitialized();
|
||||||
|
WindowManager.instance.setMinimumSize(const Size(500, 700));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isWindows || Platform.isLinux) {
|
if (Platform.isWindows || Platform.isLinux) {
|
||||||
|
@ -78,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']);
|
||||||
|
@ -168,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()
|
||||||
);
|
);
|
||||||
|
@ -177,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,6 @@ class FilteringProvider with ChangeNotifier {
|
||||||
if (result2.successful == true) {
|
if (result2.successful == true) {
|
||||||
_filtering = result2.content as Filtering;
|
_filtering = result2.content as Filtering;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print(result.content);
|
|
||||||
return {
|
return {
|
||||||
"success": true,
|
"success": true,
|
||||||
"data": result.content
|
"data": result.content
|
||||||
|
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_split_view/flutter_split_view.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/clients/clients.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart';
|
import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart';
|
import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart';
|
||||||
|
@ -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
|
||||||
|
@ -107,16 +107,16 @@ class _AddedListState extends State<AddedList> {
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
if (widget.splitView == true) {
|
if (widget.splitView == true) {
|
||||||
SplitView.of(context).popUntil(0);
|
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
|
||||||
|
|
|
@ -81,7 +81,7 @@ class _AddedClientTileState extends State<AddedClientTile> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
widget.client.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
|
@ -89,6 +89,15 @@ class _AddedClientTileState extends State<AddedClientTile> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
@ -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"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,16 @@ import 'package:adguard_home_manager/models/safe_search.dart';
|
||||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||||
import 'package:adguard_home_manager/models/clients.dart';
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
|
||||||
|
class ClientInitialData {
|
||||||
|
final String name;
|
||||||
|
final String ip;
|
||||||
|
|
||||||
|
const ClientInitialData({
|
||||||
|
required this.name,
|
||||||
|
required this.ip,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class ControllerListItem {
|
class ControllerListItem {
|
||||||
final String id;
|
final String id;
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
|
@ -25,13 +35,15 @@ class ClientScreen extends StatefulWidget {
|
||||||
final void Function(Client) onConfirm;
|
final void Function(Client) onConfirm;
|
||||||
final void Function(Client)? onDelete;
|
final void Function(Client)? onDelete;
|
||||||
final bool fullScreen;
|
final bool fullScreen;
|
||||||
|
final ClientInitialData? initialData;
|
||||||
|
|
||||||
const ClientScreen({
|
const ClientScreen({
|
||||||
super.key,
|
super.key,
|
||||||
this.client,
|
this.client,
|
||||||
required this.onConfirm,
|
required this.onConfirm,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
required this.fullScreen
|
required this.fullScreen,
|
||||||
|
this.initialData,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -146,6 +158,13 @@ class _ClientScreenState extends State<ClientScreen> {
|
||||||
_blockedServicesSchedule = widget.client!.blockedServicesSchedule!;
|
_blockedServicesSchedule = widget.client!.blockedServicesSchedule!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (widget.initialData != null) {
|
||||||
|
nameController.text = widget.initialData!.name;
|
||||||
|
identifiersControllers[0] = ControllerListItem(
|
||||||
|
id: uuid.v4(),
|
||||||
|
controller: TextEditingController(text: widget.initialData!.ip)
|
||||||
|
);
|
||||||
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,71 +235,90 @@ class _ClientScreenState extends State<ClientScreen> {
|
||||||
|
|
||||||
|
|
||||||
if (widget.fullScreen == true) {
|
if (widget.fullScreen == true) {
|
||||||
return Dialog.fullscreen(
|
return Material(
|
||||||
child: Scaffold(
|
child: NestedScrollView(
|
||||||
appBar: AppBar(
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
leading: IconButton(
|
SliverOverlapAbsorber(
|
||||||
onPressed: () => Navigator.pop(context),
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
icon: const Icon(Icons.close)
|
sliver: SliverAppBar.large(
|
||||||
),
|
pinned: true,
|
||||||
title: Text(
|
floating: true,
|
||||||
widget.client != null
|
centerTitle: false,
|
||||||
? AppLocalizations.of(context)!.client
|
forceElevated: innerBoxIsScrolled,
|
||||||
: AppLocalizations.of(context)!.addClient
|
leading: IconButton(
|
||||||
),
|
onPressed: () => Navigator.pop(context),
|
||||||
actions: actions(),
|
icon: const Icon(Icons.close)
|
||||||
),
|
),
|
||||||
|
title: Text(
|
||||||
|
widget.client != null
|
||||||
|
? AppLocalizations.of(context)!.client
|
||||||
|
: AppLocalizations.of(context)!.addClient
|
||||||
|
),
|
||||||
|
actions: actions(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
top: false,
|
||||||
controller: _scrollController,
|
bottom: false,
|
||||||
children: [
|
child: Builder(
|
||||||
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
|
builder: (context) => CustomScrollView(
|
||||||
nameValid: _nameValid,
|
slivers: [
|
||||||
identifiersValid: _identifiersValid,
|
SliverOverlapInjector(
|
||||||
dnsCacheValid: _dnsCacheValid
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
ClientForm(
|
SliverList.list(
|
||||||
isFullScreen: true,
|
children: [
|
||||||
client: widget.client,
|
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
|
||||||
nameController: nameController,
|
nameValid: _nameValid,
|
||||||
identifiersControllers: identifiersControllers,
|
identifiersValid: _identifiersValid,
|
||||||
selectedTags: selectedTags,
|
dnsCacheValid: _dnsCacheValid
|
||||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
),
|
||||||
enableFiltering: enableFiltering,
|
ClientForm(
|
||||||
enableParentalControl: enableParentalControl,
|
isFullScreen: true,
|
||||||
enableSafeBrowsing: enableSafeBrowsing,
|
client: widget.client,
|
||||||
enableSafeSearch: enableSafeSearch,
|
nameController: nameController,
|
||||||
safeSearch: safeSearch,
|
identifiersControllers: identifiersControllers,
|
||||||
blockedServices: blockedServices,
|
selectedTags: selectedTags,
|
||||||
updateBlockedServices: (v) => setState(() => blockedServices = v),
|
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||||
upstreamServers: upstreamServers,
|
enableFiltering: enableFiltering,
|
||||||
updateUpstreamServers: (v) => setState(() => upstreamServers = v),
|
enableParentalControl: enableParentalControl,
|
||||||
defaultSafeSearch: defaultSafeSearch,
|
enableSafeBrowsing: enableSafeBrowsing,
|
||||||
useGlobalSettingsServices: useGlobalSettingsServices,
|
enableSafeSearch: enableSafeSearch,
|
||||||
updateSelectedTags: (v) => setState(() => selectedTags = v),
|
safeSearch: safeSearch,
|
||||||
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
|
blockedServices: blockedServices,
|
||||||
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
|
updateBlockedServices: (v) => setState(() => blockedServices = v),
|
||||||
updateEnableFiltering: (v) => setState(() => enableFiltering = v),
|
upstreamServers: upstreamServers,
|
||||||
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
|
updateUpstreamServers: (v) => setState(() => upstreamServers = v),
|
||||||
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v),
|
defaultSafeSearch: defaultSafeSearch,
|
||||||
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v),
|
useGlobalSettingsServices: useGlobalSettingsServices,
|
||||||
updateSafeSearch: (v) => setState(() => safeSearch = v),
|
updateSelectedTags: (v) => setState(() => selectedTags = v),
|
||||||
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v),
|
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
|
||||||
ignoreClientQueryLog: _ignoreClientQueryLog,
|
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
|
||||||
ignoreClientStatistics: _ignoreClientStatistics,
|
updateEnableFiltering: (v) => setState(() => enableFiltering = v),
|
||||||
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
|
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
|
||||||
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
|
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v),
|
||||||
enableDnsCache: _enableDnsCache,
|
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v),
|
||||||
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
|
updateSafeSearch: (v) => setState(() => safeSearch = v),
|
||||||
dnsCacheField: _dnsCacheField,
|
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v),
|
||||||
dnsCacheError: _dnsCacheError,
|
ignoreClientQueryLog: _ignoreClientQueryLog,
|
||||||
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v),
|
ignoreClientStatistics: _ignoreClientStatistics,
|
||||||
blockedServicesSchedule: _blockedServicesSchedule,
|
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
|
||||||
setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v),
|
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
|
||||||
),
|
enableDnsCache: _enableDnsCache,
|
||||||
],
|
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
|
||||||
),
|
dnsCacheField: _dnsCacheField,
|
||||||
),
|
dnsCacheError: _dnsCacheError,
|
||||||
|
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v),
|
||||||
|
blockedServicesSchedule: _blockedServicesSchedule,
|
||||||
|
setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ void openClientFormModal({
|
||||||
Client? client,
|
Client? client,
|
||||||
required void Function(Client) onConfirm,
|
required void Function(Client) onConfirm,
|
||||||
void Function(Client)? onDelete,
|
void Function(Client)? onDelete,
|
||||||
|
ClientInitialData? initialData,
|
||||||
}) {
|
}) {
|
||||||
showGeneralDialog(
|
showGeneralDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -105,6 +106,7 @@ void openClientFormModal({
|
||||||
client: client,
|
client: client,
|
||||||
onConfirm: onConfirm,
|
onConfirm: onConfirm,
|
||||||
onDelete: onDelete,
|
onDelete: onDelete,
|
||||||
|
initialData: initialData,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,146 +131,177 @@ class _LogsListClientState extends State<LogsListClient> {
|
||||||
setState(() => previousIp = widget.ip);
|
setState(() => previousIp = widget.ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Material(
|
||||||
appBar: AppBar(
|
child: NestedScrollView(
|
||||||
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip),
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
centerTitle: true,
|
SliverOverlapAbsorber(
|
||||||
surfaceTintColor: isDesktop(MediaQuery.of(context).size.width)
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
? Colors.transparent
|
sliver: SliverAppBar.large(
|
||||||
: null,
|
pinned: true,
|
||||||
actions: [
|
floating: true,
|
||||||
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
centerTitle: false,
|
||||||
IconButton(
|
forceElevated: innerBoxIsScrolled,
|
||||||
onPressed: fetchLogs,
|
title: SafeArea(
|
||||||
icon: const Icon(Icons.refresh_rounded),
|
child: Column(
|
||||||
tooltip: AppLocalizations.of(context)!.refresh,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
const SizedBox(width: 8)
|
Text(
|
||||||
]
|
AppLocalizations.of(context)!.client,
|
||||||
],
|
style: const TextStyle(
|
||||||
),
|
fontSize: 24
|
||||||
body: SafeArea(
|
),
|
||||||
child: Builder(
|
),
|
||||||
builder: (context) {
|
const SizedBox(height: 4),
|
||||||
switch (loadStatus) {
|
Text(
|
||||||
case 0:
|
widget.name != null && widget.name != '' ? widget.name! : widget.ip,
|
||||||
return SizedBox(
|
style: TextStyle(
|
||||||
width: double.maxFinite,
|
fontSize: 14,
|
||||||
child: Column(
|
fontWeight: FontWeight.w500,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
color: Theme.of(context).colorScheme.secondary
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
),
|
||||||
children: [
|
)
|
||||||
const CircularProgressIndicator(),
|
],
|
||||||
const SizedBox(height: 30),
|
),
|
||||||
Text(
|
),
|
||||||
AppLocalizations.of(context)!.loadingLogs,
|
surfaceTintColor: isDesktop(MediaQuery.of(context).size.width)
|
||||||
textAlign: TextAlign.center,
|
? Colors.transparent
|
||||||
style: TextStyle(
|
: null,
|
||||||
fontSize: 22,
|
actions: [
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||||
),
|
IconButton(
|
||||||
)
|
onPressed: fetchLogs,
|
||||||
],
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.refresh,
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(width: 8)
|
||||||
|
]
|
||||||
case 1:
|
],
|
||||||
if (logsData!.data.isNotEmpty) {
|
)
|
||||||
return RefreshIndicator(
|
)
|
||||||
onRefresh: fetchLogs,
|
],
|
||||||
child: ListView.builder(
|
body: SafeArea(
|
||||||
controller: scrollController,
|
top: false,
|
||||||
padding: const EdgeInsets.only(top: 0),
|
bottom: false,
|
||||||
itemCount: isLoadingMore == true
|
child: Builder(
|
||||||
? logsData!.data.length+1
|
builder: (context) => RefreshIndicator(
|
||||||
: logsData!.data.length,
|
onRefresh: fetchLogs,
|
||||||
itemBuilder: (context, index) {
|
displacement: 95,
|
||||||
if (isLoadingMore == true && index == logsData!.data.length) {
|
child: CustomScrollView(
|
||||||
return const Padding(
|
slivers: [
|
||||||
padding: EdgeInsets.symmetric(vertical: 20),
|
SliverOverlapInjector(
|
||||||
child: Center(
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
child: CircularProgressIndicator(),
|
),
|
||||||
|
if (loadStatus == 0) SliverFillRemaining(
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.loadingLogs,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
}
|
],
|
||||||
else {
|
),
|
||||||
return LogTile(
|
)
|
||||||
log: logsData!.data[index],
|
),
|
||||||
index: index,
|
if (loadStatus == 1 && logsData!.data.isNotEmpty) SliverList.builder(
|
||||||
length: logsData!.data.length,
|
itemCount: isLoadingMore == true
|
||||||
useAlwaysNormalTile: true,
|
? logsData!.data.length+1
|
||||||
onLogTap: (log) => {
|
: logsData!.data.length,
|
||||||
if (width > 700) {
|
itemBuilder: (context, index) {
|
||||||
showDialog(
|
if (isLoadingMore == true && index == logsData!.data.length) {
|
||||||
context: context,
|
return const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 20),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return LogTile(
|
||||||
|
log: logsData!.data[index],
|
||||||
|
index: index,
|
||||||
|
length: logsData!.data.length,
|
||||||
|
useAlwaysNormalTile: true,
|
||||||
|
onLogTap: (log) => {
|
||||||
|
if (width > 700) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => LogDetailsScreen(
|
||||||
|
log: log,
|
||||||
|
dialog: true,
|
||||||
|
twoColumns: widget.splitView,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) => LogDetailsScreen(
|
builder: (context) => LogDetailsScreen(
|
||||||
log: log,
|
log: log,
|
||||||
dialog: true
|
dialog: false,
|
||||||
|
twoColumns: widget.splitView,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
else {
|
}
|
||||||
Navigator.of(context).push(
|
},
|
||||||
MaterialPageRoute(
|
twoColumns: widget.splitView,
|
||||||
builder: (context) => LogDetailsScreen(
|
);
|
||||||
log: log,
|
|
||||||
dialog: false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
twoColumns: widget.splitView,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
),
|
}
|
||||||
);
|
),
|
||||||
}
|
if (loadStatus == 1 && logsData!.data.isEmpty) SliverFillRemaining(
|
||||||
else {
|
child: Center(
|
||||||
return Center(
|
child: Text(
|
||||||
child: Text(
|
AppLocalizations.of(context)!.noLogsDisplay,
|
||||||
AppLocalizations.of(context)!.noLogsDisplay,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
return SizedBox(
|
|
||||||
width: double.maxFinite,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.error,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 50,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.logsNotLoaded,
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
if (loadStatus == 2) SliverFillRemaining(
|
||||||
|
child: SizedBox(
|
||||||
default:
|
width: double.maxFinite,
|
||||||
return const SizedBox();
|
child: Column(
|
||||||
}
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
},
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
),
|
children: [
|
||||||
)
|
const Icon(
|
||||||
|
Icons.error,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.logsNotLoaded,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/clients/clients_lists.dart';
|
import 'package:adguard_home_manager/screens/clients/clients_lists.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/models/clients.dart';
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
|
||||||
|
final clientsNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
class Clients extends StatefulWidget {
|
class Clients extends StatefulWidget {
|
||||||
const Clients({super.key});
|
const Clients({super.key});
|
||||||
|
|
||||||
|
@ -24,26 +24,23 @@ class _ClientsState extends State<Clients> with TickerProviderStateMixin {
|
||||||
body: LayoutBuilder(
|
body: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
if (constraints.maxWidth > 900) {
|
if (constraints.maxWidth > 900) {
|
||||||
return SplitView.material(
|
return Row(
|
||||||
hideDivider: true,
|
children: [
|
||||||
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2),
|
const Expanded(
|
||||||
placeholder: Center(
|
flex: 1,
|
||||||
child: Padding(
|
child: ClientsLists(
|
||||||
padding: const EdgeInsets.all(24),
|
splitView: true,
|
||||||
child: Text(
|
)
|
||||||
AppLocalizations.of(context)!.selectClientLeftColumn,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: const ClientsLists(
|
Expanded(
|
||||||
splitView: true,
|
flex: 2,
|
||||||
|
child: Navigator(
|
||||||
|
key: clientsNavigatorKey,
|
||||||
|
onGenerateRoute: (settings) => MaterialPageRoute(builder: (ctx) => const SizedBox()),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
);
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return const ClientsLists(
|
return const ClientsLists(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_split_view/flutter_split_view.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/clients/clients.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/added_list.dart';
|
import 'package:adguard_home_manager/screens/clients/added_list.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart';
|
import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
||||||
|
@ -68,7 +68,13 @@ class _ClientsListsState extends State<ClientsLists> with TickerProviderStateMix
|
||||||
splitView: widget.splitView,
|
splitView: widget.splitView,
|
||||||
);
|
);
|
||||||
if (widget.splitView) {
|
if (widget.splitView) {
|
||||||
SplitView.of(context).push(w);
|
Navigator.of(clientsNavigatorKey.currentContext!).pushReplacement(
|
||||||
|
PageRouteBuilder(
|
||||||
|
pageBuilder: (context, animation1, animation2) => w,
|
||||||
|
transitionDuration: Duration.zero,
|
||||||
|
reverseTransitionDuration: Duration.zero,
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -88,7 +94,13 @@ class _ClientsListsState extends State<ClientsLists> with TickerProviderStateMix
|
||||||
splitView: widget.splitView,
|
splitView: widget.splitView,
|
||||||
);
|
);
|
||||||
if (widget.splitView) {
|
if (widget.splitView) {
|
||||||
SplitView.of(context).push(w);
|
Navigator.of(clientsNavigatorKey.currentContext!).pushReplacement(
|
||||||
|
PageRouteBuilder(
|
||||||
|
pageBuilder: (context, animation1, animation2) => w,
|
||||||
|
transitionDuration: Duration.zero,
|
||||||
|
reverseTransitionDuration: Duration.zero,
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
|
|
@ -14,7 +14,7 @@ import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
|
||||||
class ClientsFab extends StatelessWidget {
|
class ClientsFab extends StatelessWidget {
|
||||||
const ClientsFab({Key? key}) : super(key: key);
|
const ClientsFab({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -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
|
||||||
|
@ -85,38 +85,134 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||||
|
|
||||||
if (widget.fullScreen == true) {
|
if (widget.fullScreen == true) {
|
||||||
return Dialog.fullscreen(
|
return Dialog.fullscreen(
|
||||||
child: Scaffold(
|
child: Material(
|
||||||
appBar: AppBar(
|
child: NestedScrollView(
|
||||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
title: Text(AppLocalizations.of(context)!.blockedServices),
|
SliverOverlapAbsorber(
|
||||||
actions: [
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
IconButton(
|
sliver: SliverAppBar.large(
|
||||||
onPressed: updateBlockedServices,
|
pinned: true,
|
||||||
icon: const Icon(
|
floating: true,
|
||||||
Icons.save_rounded
|
centerTitle: false,
|
||||||
),
|
forceElevated: innerBoxIsScrolled,
|
||||||
tooltip: AppLocalizations.of(context)!.save,
|
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||||
),
|
title: Text(AppLocalizations.of(context)!.blockedServices),
|
||||||
const SizedBox(width: 10)
|
actions: [
|
||||||
],
|
IconButton(
|
||||||
),
|
onPressed: updateBlockedServices,
|
||||||
body: SafeArea(
|
icon: const Icon(
|
||||||
child: RefreshIndicator(
|
Icons.save_rounded
|
||||||
onRefresh: () async {
|
),
|
||||||
final result = await filteringProvider.loadBlockedServices();
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
if (result == false) {
|
),
|
||||||
showSnacbkar(
|
const SizedBox(width: 10)
|
||||||
appConfigProvider: appConfigProvider,
|
],
|
||||||
label: AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
)
|
||||||
color: Colors.red
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: _Content(
|
|
||||||
values: values,
|
|
||||||
updateValues: updateValues,
|
|
||||||
)
|
)
|
||||||
),
|
],
|
||||||
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: true,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loading) SliverFillRemaining(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.loadingBlockedServicesList,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loaded) SliverList.builder(
|
||||||
|
itemCount: filteringProvider.blockedServices!.services.length,
|
||||||
|
itemBuilder: (context, index) => Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => updateValues(
|
||||||
|
values.contains(filteringProvider.blockedServices!.services[index].id),
|
||||||
|
filteringProvider.blockedServices!.services[index]
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 6,
|
||||||
|
bottom: 6,
|
||||||
|
right: 12,
|
||||||
|
left: 24
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
filteringProvider.blockedServices!.services[index].name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Checkbox(
|
||||||
|
value: values.contains(filteringProvider.blockedServices!.services[index].id),
|
||||||
|
onChanged: (value) => updateValues(
|
||||||
|
value!,
|
||||||
|
filteringProvider.blockedServices!.services[index]
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.error) SliverFillRemaining(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.error,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -162,9 +258,105 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _Content(
|
child: Builder(
|
||||||
values: values,
|
builder: (ctx) {
|
||||||
updateValues: updateValues,
|
switch (filteringProvider.blockedServicesLoadStatus) {
|
||||||
|
case LoadStatus.loading:
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.loadingBlockedServicesList,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
case LoadStatus.loaded:
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: filteringProvider.blockedServices!.services.length,
|
||||||
|
itemBuilder: (context, index) => Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => updateValues(
|
||||||
|
values.contains(filteringProvider.blockedServices!.services[index].id),
|
||||||
|
filteringProvider.blockedServices!.services[index]
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 6,
|
||||||
|
bottom: 6,
|
||||||
|
right: 12,
|
||||||
|
left: 24
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
filteringProvider.blockedServices!.services[index].name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Checkbox(
|
||||||
|
value: values.contains(filteringProvider.blockedServices!.services[index].id),
|
||||||
|
onChanged: (value) => updateValues(
|
||||||
|
value!,
|
||||||
|
filteringProvider.blockedServices!.services[index]
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
case LoadStatus.error:
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.error,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -175,117 +367,6 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Content extends StatelessWidget {
|
|
||||||
final List<String> values;
|
|
||||||
final void Function(bool value, BlockedService item) updateValues;
|
|
||||||
|
|
||||||
const _Content({
|
|
||||||
required this.values,
|
|
||||||
required this.updateValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
|
||||||
|
|
||||||
switch (filteringProvider.blockedServicesLoadStatus) {
|
|
||||||
case LoadStatus.loading:
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
width: double.maxFinite,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const CircularProgressIndicator(),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.loadingBlockedServicesList,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
case LoadStatus.loaded:
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: filteringProvider.blockedServices!.services.length,
|
|
||||||
itemBuilder: (context, index) => Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => updateValues(
|
|
||||||
values.contains(filteringProvider.blockedServices!.services[index].id),
|
|
||||||
filteringProvider.blockedServices!.services[index]
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 6,
|
|
||||||
bottom: 6,
|
|
||||||
right: 12,
|
|
||||||
left: 24
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
filteringProvider.blockedServices!.services[index].name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Checkbox(
|
|
||||||
value: values.contains(filteringProvider.blockedServices!.services[index].id),
|
|
||||||
onChanged: (value) => updateValues(
|
|
||||||
value!,
|
|
||||||
filteringProvider.blockedServices!.services[index]
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(5)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
case LoadStatus.error:
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
width: double.maxFinite,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.error,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 50,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void openBlockedServicesModal({
|
void openBlockedServicesModal({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -55,44 +55,66 @@ class _AddCustomRuleState extends State<AddCustomRule> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.fullScreen == true) {
|
if (widget.fullScreen == true) {
|
||||||
return Dialog.fullscreen(
|
return Dialog.fullscreen(
|
||||||
child: Scaffold(
|
child: Material(
|
||||||
appBar: AppBar(
|
child: NestedScrollView(
|
||||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
SliverOverlapAbsorber(
|
||||||
actions: [
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
IconButton(
|
sliver: SliverAppBar.large(
|
||||||
onPressed: _checkValidValues() == true
|
pinned: true,
|
||||||
? () {
|
floating: true,
|
||||||
Navigator.pop(context);
|
centerTitle: false,
|
||||||
widget.onConfirm(
|
forceElevated: innerBoxIsScrolled,
|
||||||
_buildRule(
|
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||||
domainController: _domainController,
|
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
||||||
important: _addImportant,
|
actions: [
|
||||||
preset: _preset
|
IconButton(
|
||||||
)
|
onPressed: _checkValidValues() == true
|
||||||
);
|
? () {
|
||||||
}
|
Navigator.pop(context);
|
||||||
: null,
|
widget.onConfirm(
|
||||||
icon: const Icon(Icons.check)
|
_buildRule(
|
||||||
),
|
domainController: _domainController,
|
||||||
const SizedBox(width: 10)
|
important: _addImportant,
|
||||||
],
|
preset: _preset
|
||||||
),
|
)
|
||||||
body: SafeArea(
|
);
|
||||||
child: ListView(
|
}
|
||||||
children: [
|
: null,
|
||||||
_CustomRuleEditor(
|
icon: const Icon(Icons.check)
|
||||||
domainController: _domainController,
|
),
|
||||||
domainError: _domainError,
|
const SizedBox(width: 10)
|
||||||
important: _addImportant,
|
],
|
||||||
preset: _preset,
|
)
|
||||||
setImportant: (v) => setState(() => _addImportant = v),
|
|
||||||
setPreset: (v) => setState(() => _preset = v),
|
|
||||||
validateDomain: validateDomain
|
|
||||||
)
|
)
|
||||||
]
|
],
|
||||||
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: true,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
SliverList.list(
|
||||||
|
children: [
|
||||||
|
_CustomRuleEditor(
|
||||||
|
domainController: _domainController,
|
||||||
|
domainError: _domainError,
|
||||||
|
important: _addImportant,
|
||||||
|
preset: _preset,
|
||||||
|
setImportant: (v) => setState(() => _addImportant = v),
|
||||||
|
setPreset: (v) => setState(() => _preset = v),
|
||||||
|
validateDomain: validateDomain
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
)
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -254,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(
|
||||||
|
|
|
@ -37,29 +37,51 @@ class _EditCustomRulesState extends State<EditCustomRules> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.fullScreen == true) {
|
if (widget.fullScreen == true) {
|
||||||
return Dialog.fullscreen(
|
return Dialog.fullscreen(
|
||||||
child: Scaffold(
|
child: Material(
|
||||||
appBar: AppBar(
|
child: NestedScrollView(
|
||||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
title: Text(AppLocalizations.of(context)!.editCustomRules),
|
SliverOverlapAbsorber(
|
||||||
actions: [
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
IconButton(
|
sliver: SliverAppBar.large(
|
||||||
onPressed: () {
|
pinned: true,
|
||||||
Navigator.pop(context);
|
floating: true,
|
||||||
widget.onConfirm(_fieldController.text.split("\n"));
|
centerTitle: false,
|
||||||
},
|
forceElevated: innerBoxIsScrolled,
|
||||||
icon: const Icon(Icons.save_rounded),
|
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||||
tooltip: AppLocalizations.of(context)!.save,
|
title: Text(AppLocalizations.of(context)!.editCustomRules),
|
||||||
),
|
actions: [
|
||||||
const SizedBox(width: 10)
|
IconButton(
|
||||||
],
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.onConfirm(_fieldController.text.split("\n"));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.save_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: true,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
SliverList.list(
|
||||||
|
children: [
|
||||||
|
_CustomRulesRawEditor(fieldController: _fieldController)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
_CustomRulesRawEditor(fieldController: _fieldController)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
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())
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,7 +43,7 @@ class _HomeChartState extends State<HomeChart> {
|
||||||
|
|
||||||
if (!(appConfigProvider.hideZeroValues == true && isEmpty == true)) {
|
if (!(appConfigProvider.hideZeroValues == true && isEmpty == true)) {
|
||||||
List<DateTime> dateTimes = [];
|
List<DateTime> dateTimes = [];
|
||||||
DateTime currentDate = DateTime.now().subtract(Duration(hours: widget.hoursInterval*widget.data.length+1));
|
DateTime currentDate = DateTime.now().subtract(Duration(hours: widget.hoursInterval*widget.data.length));
|
||||||
for (var i = 0; i < widget.data.length; i++) {
|
for (var i = 0; i < widget.data.length; i++) {
|
||||||
currentDate = currentDate.add(Duration(hours: widget.hoursInterval));
|
currentDate = currentDate.add(Duration(hours: widget.hoursInterval));
|
||||||
dateTimes.add(currentDate);
|
dateTimes.add(currentDate);
|
||||||
|
|
|
@ -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
|
||||||
|
@ -37,27 +40,6 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Filter? getList(int id) {
|
|
||||||
try {
|
|
||||||
return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () {
|
|
||||||
return statusProvider.filteringStatus!.whitelistFilters.firstWhere((filter) => filter.id == id);
|
|
||||||
});
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getResult() {
|
|
||||||
final filter = getFilteredStatus(context, appConfigProvider, log.reason, true);
|
|
||||||
return Text(
|
|
||||||
filter['label'],
|
|
||||||
style: TextStyle(
|
|
||||||
color: filter['color'],
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void blockUnblock(String domain, String newStatus) async {
|
void blockUnblock(String domain, String newStatus) async {
|
||||||
final ProcessModal processModal = ProcessModal();
|
final ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.savingUserFilters);
|
processModal.open(AppLocalizations.of(context)!.savingUserFilters);
|
||||||
|
@ -70,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
|
||||||
|
@ -85,132 +67,6 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> content() {
|
|
||||||
return [
|
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.status),
|
|
||||||
LogListTile(
|
|
||||||
icon: Icons.shield_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.result,
|
|
||||||
subtitleWidget: getResult(),
|
|
||||||
trailing: log.cached == true
|
|
||||||
? Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10,
|
|
||||||
vertical: 5
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
|
||||||
borderRadius: BorderRadius.circular(30)
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
"CACHE",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
if (log.rule != null) LogListTile(
|
|
||||||
icon: Icons.block,
|
|
||||||
title: AppLocalizations.of(context)!.blockingRule,
|
|
||||||
subtitle: log.rule
|
|
||||||
),
|
|
||||||
LogListTile(
|
|
||||||
icon: Icons.schedule,
|
|
||||||
title: AppLocalizations.of(context)!.time,
|
|
||||||
subtitle: convertTimestampLocalTimezone(log.time, 'HH:mm:ss')
|
|
||||||
),
|
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.request),
|
|
||||||
LogListTile(
|
|
||||||
icon: Icons.domain_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.domain,
|
|
||||||
subtitle: log.question.name
|
|
||||||
),
|
|
||||||
LogListTile(
|
|
||||||
icon: Icons.category_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.type,
|
|
||||||
subtitle: log.question.type
|
|
||||||
),
|
|
||||||
LogListTile(
|
|
||||||
icon: Icons.class_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.clas,
|
|
||||||
subtitle: log.question.questionClass
|
|
||||||
),
|
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.response),
|
|
||||||
if (log.upstream != null && log.upstream != '') LogListTile(
|
|
||||||
icon: Icons.dns_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.dnsServer,
|
|
||||||
subtitle: log.upstream
|
|
||||||
),
|
|
||||||
LogListTile(
|
|
||||||
icon: Icons.timer_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.elapsedTime,
|
|
||||||
subtitle: "${double.parse(log.elapsedMs).toStringAsFixed(2)} ms"
|
|
||||||
),
|
|
||||||
if (log.status != null) LogListTile(
|
|
||||||
icon: Icons.system_update_alt_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.responseCode,
|
|
||||||
subtitle: log.status
|
|
||||||
),
|
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.client),
|
|
||||||
LogListTile(
|
|
||||||
icon: Icons.smartphone_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.deviceIp,
|
|
||||||
subtitle: log.client
|
|
||||||
),
|
|
||||||
if (log.clientInfo != null && log.clientInfo!.name != '') LogListTile(
|
|
||||||
icon: Icons.abc_rounded,
|
|
||||||
title: AppLocalizations.of(context)!.deviceName,
|
|
||||||
subtitle: log.clientInfo!.name
|
|
||||||
),
|
|
||||||
if (log.rules.isNotEmpty) ...[
|
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.rules),
|
|
||||||
...log.rules.map((rule) {
|
|
||||||
final Filter? list = getList(rule.filterListId);
|
|
||||||
if (list != null) {
|
|
||||||
return LogListTile(
|
|
||||||
icon: Icons.rule_rounded,
|
|
||||||
title: rule.text,
|
|
||||||
subtitle: list.name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
}).toList()
|
|
||||||
],
|
|
||||||
if (log.answer.isNotEmpty) ...[
|
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.answers),
|
|
||||||
...log.answer.map((a) => LogListTile(
|
|
||||||
icon: Icons.download_rounded,
|
|
||||||
title: a.value,
|
|
||||||
subtitle: "TTL: ${a.ttl.toString()}",
|
|
||||||
trailing: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10,
|
|
||||||
vertical: 5
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
|
||||||
borderRadius: BorderRadius.circular(30)
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
a.type,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)).toList()
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialog) {
|
if (dialog) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
|
@ -271,7 +127,9 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: content(),
|
children: [
|
||||||
|
_Content(log: log)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -292,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(
|
||||||
|
@ -329,7 +188,9 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
SliverList.list(
|
SliverList.list(
|
||||||
children: content()
|
children: [
|
||||||
|
_Content(log: log)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -339,4 +200,174 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Content extends StatelessWidget {
|
||||||
|
final Log log;
|
||||||
|
|
||||||
|
const _Content({
|
||||||
|
required this.log,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
|
|
||||||
|
Widget getResult() {
|
||||||
|
final filter = getFilteredStatus(context, appConfigProvider, log.reason, true);
|
||||||
|
return Text(
|
||||||
|
filter['label'],
|
||||||
|
style: TextStyle(
|
||||||
|
color: filter['color'],
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter? getList(int id) {
|
||||||
|
try {
|
||||||
|
return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () {
|
||||||
|
return statusProvider.filteringStatus!.whitelistFilters.firstWhere((filter) => filter.id == id);
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SectionLabel(label: AppLocalizations.of(context)!.status),
|
||||||
|
LogListTile(
|
||||||
|
icon: Icons.shield_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.result,
|
||||||
|
subtitleWidget: getResult(),
|
||||||
|
trailing: log.cached == true
|
||||||
|
? Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(30)
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"CACHE",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
if (log.rule != null) LogListTile(
|
||||||
|
icon: Icons.block,
|
||||||
|
title: AppLocalizations.of(context)!.blockingRule,
|
||||||
|
subtitle: log.rule
|
||||||
|
),
|
||||||
|
LogListTile(
|
||||||
|
icon: Icons.calendar_month_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.date,
|
||||||
|
subtitle: convertTimestampLocalTimezone(log.time, 'dd-MM-yyyy')
|
||||||
|
),
|
||||||
|
LogListTile(
|
||||||
|
icon: Icons.schedule,
|
||||||
|
title: AppLocalizations.of(context)!.time,
|
||||||
|
subtitle: convertTimestampLocalTimezone(log.time, 'HH:mm:ss')
|
||||||
|
),
|
||||||
|
SectionLabel(label: AppLocalizations.of(context)!.request),
|
||||||
|
if (log.question.name != null) LogListTile(
|
||||||
|
icon: Icons.domain_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.domain,
|
||||||
|
subtitle: log.question.name,
|
||||||
|
onTap: () => copyToClipboard(value: log.question.name!, successMessage: AppLocalizations.of(context)!.domainCopiedClipboard),
|
||||||
|
),
|
||||||
|
LogListTile(
|
||||||
|
icon: Icons.category_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.type,
|
||||||
|
subtitle: log.question.type
|
||||||
|
),
|
||||||
|
LogListTile(
|
||||||
|
icon: Icons.class_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.clas,
|
||||||
|
subtitle: log.question.questionClass
|
||||||
|
),
|
||||||
|
SectionLabel(label: AppLocalizations.of(context)!.response),
|
||||||
|
if (log.upstream != null && log.upstream != '') LogListTile(
|
||||||
|
icon: Icons.dns_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.dnsServer,
|
||||||
|
subtitle: log.upstream,
|
||||||
|
onTap: () => copyToClipboard(value: log.upstream!, successMessage: AppLocalizations.of(context)!.dnsServerAddressCopied)
|
||||||
|
),
|
||||||
|
LogListTile(
|
||||||
|
icon: Icons.timer_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.elapsedTime,
|
||||||
|
subtitle: "${double.parse(log.elapsedMs).toStringAsFixed(2)} ms"
|
||||||
|
),
|
||||||
|
if (log.status != null) LogListTile(
|
||||||
|
icon: Icons.system_update_alt_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.responseCode,
|
||||||
|
subtitle: log.status
|
||||||
|
),
|
||||||
|
SectionLabel(label: AppLocalizations.of(context)!.client),
|
||||||
|
LogListTile(
|
||||||
|
icon: Icons.smartphone_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.deviceIp,
|
||||||
|
subtitle: log.client,
|
||||||
|
onTap: () => copyToClipboard(value: log.client, successMessage: AppLocalizations.of(context)!.clientIpCopied),
|
||||||
|
),
|
||||||
|
if (log.clientInfo != null && log.clientInfo!.name != '') LogListTile(
|
||||||
|
icon: Icons.abc_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.deviceName,
|
||||||
|
subtitle: log.clientInfo!.name,
|
||||||
|
onTap: () => copyToClipboard(value: log.clientInfo!.name, successMessage: AppLocalizations.of(context)!.clientNameCopied),
|
||||||
|
),
|
||||||
|
if (log.rules.isNotEmpty) ...[
|
||||||
|
SectionLabel(label: AppLocalizations.of(context)!.rules),
|
||||||
|
...log.rules.map((rule) {
|
||||||
|
final Filter? list = getList(rule.filterListId);
|
||||||
|
if (list != null) {
|
||||||
|
return LogListTile(
|
||||||
|
icon: Icons.rule_rounded,
|
||||||
|
title: rule.text,
|
||||||
|
subtitle: list.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
if (log.answer.isNotEmpty) ...[
|
||||||
|
SectionLabel(label: AppLocalizations.of(context)!.answers),
|
||||||
|
...log.answer.map((a) => LogListTile(
|
||||||
|
icon: Icons.download_rounded,
|
||||||
|
title: a.value,
|
||||||
|
subtitle: "TTL: ${a.ttl.toString()}",
|
||||||
|
trailing: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(30)
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
a.type,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
]
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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,15 +1,18 @@
|
||||||
// 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';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/clients/client/client_screen.dart';
|
||||||
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
import 'package:adguard_home_manager/widgets/options_menu.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/filtering_provider.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/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
||||||
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||||
import 'package:adguard_home_manager/models/menu_option.dart';
|
import 'package:adguard_home_manager/models/menu_option.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/functions/get_filtered_status.dart';
|
import 'package:adguard_home_manager/functions/get_filtered_status.dart';
|
||||||
|
@ -40,6 +43,8 @@ class LogTile extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
|
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||||
|
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||||
|
|
||||||
Widget logStatusWidget({
|
Widget logStatusWidget({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
|
@ -91,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);
|
||||||
|
|
||||||
|
@ -103,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
|
||||||
|
@ -118,6 +125,123 @@ class LogTile extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void confirmAddClient(Client client) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
ProcessModal processModal = ProcessModal();
|
||||||
|
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||||
|
|
||||||
|
final result = await clientsProvider.addClient(client);
|
||||||
|
|
||||||
|
processModal.close();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
showSnackbar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
||||||
|
color: Colors.green
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showSnackbar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.clientNotAdded,
|
||||||
|
color: Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void blockUnblockRuleClient() async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
ProcessModal processModal = ProcessModal();
|
||||||
|
processModal.open(AppLocalizations.of(context)!.addingRule);
|
||||||
|
|
||||||
|
final rule = isDomainBlocked(log.reason) == true
|
||||||
|
? "@@||${log.question.name}^\$client='${log.client}'"
|
||||||
|
: "||${log.question.name}^\$client='${log.client}'";
|
||||||
|
|
||||||
|
final result = await filteringProvider.addCustomRule(rule);
|
||||||
|
|
||||||
|
processModal.close();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (result == true) {
|
||||||
|
showSnackbar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: isDomainBlocked(log.reason) == true
|
||||||
|
? AppLocalizations.of(context)!.domainUnblockedThisClient(log.question.name!)
|
||||||
|
: AppLocalizations.of(context)!.domainBlockedThisClient(log.question.name!),
|
||||||
|
color: Colors.green
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showSnackbar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.ruleNotAdded,
|
||||||
|
color: Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void allowDisallowClient() async {
|
||||||
|
ProcessModal processModal = ProcessModal();
|
||||||
|
processModal.open(
|
||||||
|
log.clientInfo!.disallowed == true
|
||||||
|
? AppLocalizations.of(context)!.allowingClient
|
||||||
|
: AppLocalizations.of(context)!.disallowingClient
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await clientsProvider.addClientList(
|
||||||
|
log.client,
|
||||||
|
log.clientInfo!.disallowed == true
|
||||||
|
? AccessSettingsList.allowed
|
||||||
|
: AccessSettingsList.disallowed
|
||||||
|
);
|
||||||
|
|
||||||
|
processModal.close();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (result.successful == true) {
|
||||||
|
showSnackbar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
|
||||||
|
color: Colors.green
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (result.successful == false && result.content == 'client_another_list') {
|
||||||
|
showSnackbar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.clientAnotherList,
|
||||||
|
color: Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showSnackbar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.changesNotSaved,
|
||||||
|
color: Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void openAddClient() {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(milliseconds: 0),
|
||||||
|
() {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
openClientFormModal(
|
||||||
|
context: context,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
onConfirm: confirmAddClient,
|
||||||
|
initialData: ClientInitialData(name: "Client ${log.client}", ip: log.client)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final domainBlocked = isDomainBlocked(log.reason);
|
final domainBlocked = isDomainBlocked(log.reason);
|
||||||
|
|
||||||
if (twoColumns && !(useAlwaysNormalTile == true)) {
|
if (twoColumns && !(useAlwaysNormalTile == true)) {
|
||||||
|
@ -141,6 +265,29 @@ class LogTile extends StatelessWidget {
|
||||||
newStatus: domainBlocked == true ? 'unblock' : 'block'
|
newStatus: domainBlocked == true ? 'unblock' : 'block'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
if (filteringProvider.filtering != null) MenuOption(
|
||||||
|
title: domainBlocked == true
|
||||||
|
? AppLocalizations.of(context)!.unblockThisClientOnly
|
||||||
|
: AppLocalizations.of(context)!.blockThisClientOnly,
|
||||||
|
icon: domainBlocked == true
|
||||||
|
? Icons.check_rounded
|
||||||
|
: Icons.block_rounded,
|
||||||
|
action: blockUnblockRuleClient
|
||||||
|
),
|
||||||
|
if (log.clientInfo?.name == "") MenuOption(
|
||||||
|
title: AppLocalizations.of(context)!.addPersistentClient,
|
||||||
|
icon: Icons.add_rounded,
|
||||||
|
action: openAddClient
|
||||||
|
),
|
||||||
|
MenuOption(
|
||||||
|
title: log.clientInfo!.disallowed == true
|
||||||
|
? AppLocalizations.of(context)!.allowThisClient
|
||||||
|
: AppLocalizations.of(context)!.disallowThisClient,
|
||||||
|
icon: log.clientInfo!.disallowed == true
|
||||||
|
? Icons.check_rounded
|
||||||
|
: Icons.block_rounded,
|
||||||
|
action: allowDisallowClient
|
||||||
|
),
|
||||||
if (log.question.name != null) MenuOption(
|
if (log.question.name != null) MenuOption(
|
||||||
title: AppLocalizations.of(context)!.copyClipboard,
|
title: AppLocalizations.of(context)!.copyClipboard,
|
||||||
icon: Icons.copy_rounded,
|
icon: Icons.copy_rounded,
|
||||||
|
@ -319,6 +466,29 @@ class LogTile extends StatelessWidget {
|
||||||
newStatus: domainBlocked == true ? 'unblock' : 'block'
|
newStatus: domainBlocked == true ? 'unblock' : 'block'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
if (filteringProvider.filtering != null) MenuOption(
|
||||||
|
title: domainBlocked == true
|
||||||
|
? AppLocalizations.of(context)!.unblockThisClientOnly
|
||||||
|
: AppLocalizations.of(context)!.blockThisClientOnly,
|
||||||
|
icon: domainBlocked == true
|
||||||
|
? Icons.check_rounded
|
||||||
|
: Icons.block_rounded,
|
||||||
|
action: blockUnblockRuleClient
|
||||||
|
),
|
||||||
|
if (log.clientInfo?.name == "") MenuOption(
|
||||||
|
title: AppLocalizations.of(context)!.addPersistentClient,
|
||||||
|
icon: Icons.add_rounded,
|
||||||
|
action: openAddClient
|
||||||
|
),
|
||||||
|
MenuOption(
|
||||||
|
title: log.clientInfo!.disallowed == true
|
||||||
|
? AppLocalizations.of(context)!.allowThisClient
|
||||||
|
: AppLocalizations.of(context)!.disallowThisClient,
|
||||||
|
icon: log.clientInfo!.disallowed == true
|
||||||
|
? Icons.check_rounded
|
||||||
|
: Icons.block_rounded,
|
||||||
|
action: allowDisallowClient
|
||||||
|
),
|
||||||
if (log.question.name != null) MenuOption(
|
if (log.question.name != null) MenuOption(
|
||||||
title: AppLocalizations.of(context)!.copyClipboard,
|
title: AppLocalizations.of(context)!.copyClipboard,
|
||||||
icon: Icons.copy_rounded,
|
icon: Icons.copy_rounded,
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/logs/logs_list.dart';
|
import 'package:adguard_home_manager/screens/logs/logs_list.dart';
|
||||||
import 'package:adguard_home_manager/screens/logs/details/log_details_screen.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/models/logs.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||||
|
|
||||||
class Logs extends StatefulWidget {
|
class Logs extends StatefulWidget {
|
||||||
const Logs({Key? key}) : super(key: key);
|
const Logs({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Logs> createState() => _LogsState();
|
State<Logs> createState() => _LogsState();
|
||||||
|
@ -17,6 +19,12 @@ class Logs extends StatefulWidget {
|
||||||
class _LogsState extends State<Logs> {
|
class _LogsState extends State<Logs> {
|
||||||
Log? _selectedLog;
|
Log? _selectedLog;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
Provider.of<FilteringProvider>(context, listen: false).fetchFilters();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
|
@ -40,6 +48,7 @@ class _LogsState extends State<Logs> {
|
||||||
? LogDetailsScreen(
|
? LogDetailsScreen(
|
||||||
log: _selectedLog!,
|
log: _selectedLog!,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
|
twoColumns: true,
|
||||||
)
|
)
|
||||||
: const SizedBox()
|
: const SizedBox()
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ class LogsListWidget extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LogsListWidgetState extends State<LogsListWidget> {
|
class _LogsListWidgetState extends State<LogsListWidget> {
|
||||||
|
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||||
bool showDivider = true;
|
bool showDivider = true;
|
||||||
|
|
||||||
void fetchFilteringRules() async {
|
void fetchFilteringRules() async {
|
||||||
|
@ -41,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
|
||||||
|
@ -55,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
|
||||||
|
@ -93,194 +94,198 @@ class _LogsListWidgetState extends State<LogsListWidget> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
return Scaffold(
|
return ScaffoldMessenger(
|
||||||
body: NestedScrollView(
|
key: widget.twoColumns ? _scaffoldMessengerKey : null,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
child: Scaffold(
|
||||||
SliverOverlapAbsorber(
|
body: NestedScrollView(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
sliver: LogsListAppBar(
|
SliverOverlapAbsorber(
|
||||||
innerBoxIsScrolled: innerBoxIsScrolled,
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
showDivider: showDivider,
|
sliver: LogsListAppBar(
|
||||||
|
innerBoxIsScrolled: innerBoxIsScrolled,
|
||||||
|
showDivider: showDivider,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
],
|
||||||
],
|
body: Builder(
|
||||||
body: Builder(
|
builder: (context) {
|
||||||
builder: (context) {
|
switch (logsProvider.loadStatus) {
|
||||||
switch (logsProvider.loadStatus) {
|
case LoadStatus.loading:
|
||||||
case LoadStatus.loading:
|
return SafeArea(
|
||||||
return SafeArea(
|
top: false,
|
||||||
top: false,
|
bottom: false,
|
||||||
bottom: false,
|
child: Builder(
|
||||||
child: Builder(
|
builder: (context) => CustomScrollView(
|
||||||
builder: (context) => CustomScrollView(
|
slivers: [
|
||||||
slivers: [
|
SliverOverlapInjector(
|
||||||
SliverOverlapInjector(
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
),
|
||||||
),
|
SliverFillRemaining(
|
||||||
SliverFillRemaining(
|
child: SizedBox(
|
||||||
child: SizedBox(
|
width: double.maxFinite,
|
||||||
width: double.maxFinite,
|
child: Column(
|
||||||
child: Column(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: [
|
const CircularProgressIndicator(),
|
||||||
const CircularProgressIndicator(),
|
const SizedBox(height: 30),
|
||||||
const SizedBox(height: 30),
|
Text(
|
||||||
Text(
|
AppLocalizations.of(context)!.loadingLogs,
|
||||||
AppLocalizations.of(context)!.loadingLogs,
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 22,
|
||||||
fontSize: 22,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
)
|
||||||
)
|
);
|
||||||
);
|
|
||||||
|
case LoadStatus.loaded:
|
||||||
case LoadStatus.loaded:
|
return SafeArea(
|
||||||
return SafeArea(
|
top: false,
|
||||||
top: false,
|
bottom: false,
|
||||||
bottom: false,
|
child: Builder(
|
||||||
child: Builder(
|
builder: (context) => RefreshIndicator(
|
||||||
builder: (context) => RefreshIndicator(
|
onRefresh: () async {
|
||||||
onRefresh: () async {
|
await logsProvider.fetchLogs(inOffset: 0);
|
||||||
await logsProvider.fetchLogs(inOffset: 0);
|
},
|
||||||
},
|
displacement: 95,
|
||||||
displacement: 95,
|
child: NotificationListener(
|
||||||
child: NotificationListener(
|
onNotification: scrollListener,
|
||||||
onNotification: scrollListener,
|
child: CustomScrollView(
|
||||||
child: CustomScrollView(
|
slivers: [
|
||||||
slivers: [
|
SliverOverlapInjector(
|
||||||
SliverOverlapInjector(
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
),
|
||||||
),
|
if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder(
|
||||||
if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder(
|
itemCount: logsProvider.isLoadingMore
|
||||||
itemCount: logsProvider.isLoadingMore
|
? logsProvider.logsData!.data.length + 1
|
||||||
? logsProvider.logsData!.data.length + 1
|
: logsProvider.logsData!.data.length,
|
||||||
: logsProvider.logsData!.data.length,
|
itemBuilder: (context, index) {
|
||||||
itemBuilder: (context, index) {
|
if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) {
|
||||||
if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) {
|
return const Padding(
|
||||||
return const Padding(
|
padding: EdgeInsets.symmetric(vertical: 20),
|
||||||
padding: EdgeInsets.symmetric(vertical: 20),
|
child: Center(
|
||||||
child: Center(
|
child: CircularProgressIndicator(),
|
||||||
child: CircularProgressIndicator(),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
else if (logsProvider.logsData!.data[index].question.name != null) {
|
||||||
else if (logsProvider.logsData!.data[index].question.name != null) {
|
return LogTile(
|
||||||
return LogTile(
|
log: logsProvider.logsData!.data[index],
|
||||||
log: logsProvider.logsData!.data[index],
|
index: index,
|
||||||
index: index,
|
length: logsProvider.logsData!.data.length,
|
||||||
length: logsProvider.logsData!.data.length,
|
isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index],
|
||||||
isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index],
|
onLogTap: (log) {
|
||||||
onLogTap: (log) {
|
if (!widget.twoColumns) {
|
||||||
if (!widget.twoColumns) {
|
Navigator.of(context).push(
|
||||||
Navigator.of(context).push(
|
MaterialPageRoute(
|
||||||
MaterialPageRoute(
|
builder: (context) => LogDetailsScreen(
|
||||||
builder: (context) => LogDetailsScreen(
|
log: log,
|
||||||
log: log,
|
dialog: false,
|
||||||
dialog: false,
|
twoColumns: widget.twoColumns,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
widget.onLogSelected(log);
|
||||||
widget.onLogSelected(log);
|
},
|
||||||
},
|
twoColumns: widget.twoColumns,
|
||||||
twoColumns: widget.twoColumns,
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
),
|
||||||
return null;
|
if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining(
|
||||||
}
|
child: Center(
|
||||||
}
|
child: Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining(
|
children: [
|
||||||
child: Center(
|
Text(
|
||||||
child: Column(
|
AppLocalizations.of(context)!.noLogsDisplay,
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.noLogsDisplay,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (logsProvider.logsOlderThan != null) Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 30,
|
|
||||||
left: 20,
|
|
||||||
right: 20
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.noLogsThatOld,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 24,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (logsProvider.logsOlderThan != null) Padding(
|
||||||
]
|
padding: const EdgeInsets.only(
|
||||||
|
top: 30,
|
||||||
|
left: 20,
|
||||||
|
right: 20
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.noLogsThatOld,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
);
|
||||||
);
|
|
||||||
|
case LoadStatus.error:
|
||||||
case LoadStatus.error:
|
return SafeArea(
|
||||||
return SafeArea(
|
top: false,
|
||||||
top: false,
|
bottom: false,
|
||||||
bottom: false,
|
child: Builder(
|
||||||
child: Builder(
|
builder: (context) => CustomScrollView(
|
||||||
builder: (context) => CustomScrollView(
|
slivers: [
|
||||||
slivers: [
|
SliverOverlapInjector(
|
||||||
SliverOverlapInjector(
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
),
|
||||||
),
|
SliverFillRemaining(
|
||||||
SliverFillRemaining(
|
child: SizedBox(
|
||||||
child: SizedBox(
|
width: double.maxFinite,
|
||||||
width: double.maxFinite,
|
child: Column(
|
||||||
child: Column(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: [
|
const Icon(
|
||||||
const Icon(
|
Icons.error,
|
||||||
Icons.error,
|
color: Colors.red,
|
||||||
color: Colors.red,
|
size: 50,
|
||||||
size: 50,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.logsNotLoaded,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 30),
|
||||||
],
|
Text(
|
||||||
),
|
AppLocalizations.of(context)!.logsNotLoaded,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
)
|
||||||
)
|
);
|
||||||
);
|
|
||||||
|
default:
|
||||||
default:
|
return const SizedBox();
|
||||||
return const SizedBox();
|
}
|
||||||
}
|
},
|
||||||
},
|
)
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,19 @@ 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/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';
|
||||||
|
|
||||||
|
final GlobalKey _searchButtonKey = GlobalKey();
|
||||||
|
|
||||||
class LogsListAppBar extends StatelessWidget {
|
class LogsListAppBar extends StatelessWidget {
|
||||||
final bool innerBoxIsScrolled;
|
final bool innerBoxIsScrolled;
|
||||||
final bool showDivider;
|
final bool showDivider;
|
||||||
|
@ -52,6 +58,53 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showSearchDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FloatingSearchBar(
|
||||||
|
existingSearchValue: logsProvider.appliedFilters.searchText ?? "",
|
||||||
|
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
|
||||||
|
onSearchCompleted: (v) {
|
||||||
|
logsProvider.setAppliedFilters(
|
||||||
|
AppliedFiters(
|
||||||
|
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
|
||||||
|
searchText: v != "" ? v : null,
|
||||||
|
clients: logsProvider.appliedFilters.clients
|
||||||
|
)
|
||||||
|
);
|
||||||
|
logsProvider.filterLogs();
|
||||||
|
},
|
||||||
|
onSearchFieldUpdated: (v) {
|
||||||
|
if (v == "") {
|
||||||
|
logsProvider.setSearchText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logsProvider.setSearchText(v);
|
||||||
|
},
|
||||||
|
onSearchFieldCleared: () {
|
||||||
|
logsProvider.setSearchText(null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void openLiveLogsScreen() {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProxyProvider<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,
|
||||||
|
@ -62,7 +115,7 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
"blocked_parental": AppLocalizations.of(context)!.blockedParentalRow,
|
"blocked_parental": AppLocalizations.of(context)!.blockedParentalRow,
|
||||||
"safe_search": AppLocalizations.of(context)!.safeSearch,
|
"safe_search": AppLocalizations.of(context)!.safeSearch,
|
||||||
};
|
};
|
||||||
print(MediaQuery.of(context).viewPadding.top);
|
|
||||||
return SliverAppBar.large(
|
return SliverAppBar.large(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: true,
|
floating: true,
|
||||||
|
@ -77,29 +130,34 @@ print(MediaQuery.of(context).viewPadding.top);
|
||||||
tooltip: AppLocalizations.of(context)!.refresh,
|
tooltip: AppLocalizations.of(context)!.refresh,
|
||||||
),
|
),
|
||||||
if (logsProvider.loadStatus == LoadStatus.loaded) IconButton(
|
if (logsProvider.loadStatus == LoadStatus.loaded) IconButton(
|
||||||
onPressed: () => showDialog(
|
key: _searchButtonKey,
|
||||||
context: context,
|
onPressed: showSearchDialog,
|
||||||
builder: (context) => _Search(
|
|
||||||
hasTopBar: MediaQuery.of(context).viewPadding.top > 0,
|
|
||||||
onSearch: (v) {
|
|
||||||
logsProvider.setAppliedFilters(
|
|
||||||
AppliedFiters(
|
|
||||||
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
|
|
||||||
searchText: v != "" ? v : null,
|
|
||||||
clients: logsProvider.appliedFilters.clients
|
|
||||||
)
|
|
||||||
);
|
|
||||||
logsProvider.filterLogs();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
||||||
],
|
],
|
||||||
|
@ -233,97 +291,3 @@ print(MediaQuery.of(context).viewPadding.top);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Search extends StatefulWidget {
|
|
||||||
final void Function(String) onSearch;
|
|
||||||
final bool hasTopBar;
|
|
||||||
|
|
||||||
const _Search({
|
|
||||||
required this.onSearch,
|
|
||||||
required this.hasTopBar,
|
|
||||||
});
|
|
||||||
|
|
||||||
@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);
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => Navigator.pop(context),
|
|
||||||
child: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => {},
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
|
||||||
child: Container(
|
|
||||||
margin: widget.hasTopBar
|
|
||||||
? const EdgeInsets.symmetric(horizontal: 16)
|
|
||||||
: const EdgeInsets.all(16),
|
|
||||||
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,11 +2,12 @@
|
||||||
|
|
||||||
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_split_view/flutter_split_view.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/settings/settings.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_not_available.dart';
|
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_not_available.dart';
|
||||||
import 'package:adguard_home_manager/widgets/confirm_action_modal.dart';
|
import 'package:adguard_home_manager/widgets/confirm_action_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_main_button.dart';
|
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_main_button.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
|
||||||
|
@ -610,11 +611,11 @@ class _DhcpScreenState extends State<DhcpScreen> {
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (!(Platform.isAndroid || Platform.isIOS)) {
|
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||||
SplitView.of(context).push(
|
Navigator.of(settingsNavigatorKey.currentContext!).push(
|
||||||
DhcpLeases(
|
MaterialPageRoute(builder: (ctx) => DhcpLeases(
|
||||||
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
|
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
|
||||||
staticLeases: false,
|
staticLeases: false,
|
||||||
)
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -639,11 +640,11 @@ class _DhcpScreenState extends State<DhcpScreen> {
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (!(Platform.isAndroid || Platform.isIOS)) {
|
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||||
SplitView.of(context).push(
|
Navigator.of(settingsNavigatorKey.currentContext!).push(
|
||||||
DhcpLeases(
|
MaterialPageRoute(builder: (ctx) => DhcpLeases(
|
||||||
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
|
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
|
||||||
staticLeases: true,
|
staticLeases: true,
|
||||||
)
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_split_view/flutter_split_view.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/settings/settings.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dns/fallback_dns.dart';
|
import 'package:adguard_home_manager/screens/settings/dns/fallback_dns.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dns/test_upstream_dns_modal.dart';
|
import 'package:adguard_home_manager/screens/settings/dns/test_upstream_dns_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart';
|
import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart';
|
||||||
|
@ -52,7 +52,7 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
|
|
||||||
void navigate(Widget w) {
|
void navigate(Widget w) {
|
||||||
if (widget.splitView) {
|
if (widget.splitView) {
|
||||||
SplitView.of(context).push(w);
|
Navigator.of(settingsNavigatorKey.currentContext!).push(MaterialPageRoute(builder: (ctx) => w));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -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
|
||||||
|
|
|
@ -165,7 +165,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
||||||
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
||||||
|
|
||||||
final result = await dnsProvider.saveDnsServerConfig({
|
final result = await dnsProvider.saveDnsServerConfig({
|
||||||
"ratelimit": int.parse(_limitRequestsController.text),
|
"ratelimit": int.tryParse(_limitRequestsController.text),
|
||||||
"edns_cs_enabled": _enableEdns,
|
"edns_cs_enabled": _enableEdns,
|
||||||
"edns_cs_use_custom": _useCustomIpEdns,
|
"edns_cs_use_custom": _useCustomIpEdns,
|
||||||
"edns_cs_custom_ip": _customIpEdnsController.text,
|
"edns_cs_custom_ip": _customIpEdnsController.text,
|
||||||
|
@ -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,7 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/regexps.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/settings/dns/comment_modal.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package: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';
|
||||||
|
@ -23,12 +25,11 @@ 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) {
|
||||||
if (ipAddress.hasMatch(value) == true) {
|
|
||||||
setState(() => field['error'] = null);
|
setState(() => field['error'] = null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setState(() => field['error'] = AppLocalizations.of(context)!.invalidIp);
|
setState(() => field['error'] = AppLocalizations.of(context)!.invalidIpOrUrl);
|
||||||
}
|
}
|
||||||
checkValidValues();
|
checkValidValues();
|
||||||
}
|
}
|
||||||
|
@ -47,12 +48,20 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
|
||||||
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
|
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
|
||||||
|
|
||||||
for (var item in dnsProvider.dnsInfo!.fallbackDns!) {
|
for (var item in dnsProvider.dnsInfo!.fallbackDns!) {
|
||||||
final controller = TextEditingController();
|
if (item.contains("#")) {
|
||||||
controller.text = item;
|
fallbackControllers.add({
|
||||||
fallbackControllers.add({
|
'comment': item
|
||||||
'controller': controller,
|
});
|
||||||
'error': null
|
}
|
||||||
});
|
else {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
controller.text = item;
|
||||||
|
fallbackControllers.add({
|
||||||
|
'controller': controller,
|
||||||
|
'error': null,
|
||||||
|
'isComment': item.contains("#")
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
validValues = true;
|
validValues = true;
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -70,27 +79,32 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
|
||||||
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
||||||
|
|
||||||
final result = await dnsProvider.saveFallbackDnsConfig({
|
final result = await dnsProvider.saveFallbackDnsConfig({
|
||||||
"fallback_dns": fallbackControllers.map((e) => e['controller'].text).toList(),
|
"fallback_dns": fallbackControllers.map(
|
||||||
|
(e) => e['controller'] != null
|
||||||
|
? e['controller'].text
|
||||||
|
: e['comment']
|
||||||
|
).toList(),
|
||||||
});
|
});
|
||||||
|
|
||||||
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
|
||||||
|
@ -98,6 +112,75 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void openAddCommentModal() {
|
||||||
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CommentModal(
|
||||||
|
onConfirm: (value) {
|
||||||
|
setState(() {
|
||||||
|
fallbackControllers.add({
|
||||||
|
'comment': value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dialog: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder: (context) => CommentModal(
|
||||||
|
onConfirm: (value) {
|
||||||
|
setState(() {
|
||||||
|
fallbackControllers.add({
|
||||||
|
'comment': value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void openEditCommentModal(Map<String, dynamic> item, int position) {
|
||||||
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CommentModal(
|
||||||
|
comment: item['comment'],
|
||||||
|
onConfirm: (value) {
|
||||||
|
setState(() => fallbackControllers[position] = { 'comment': value });
|
||||||
|
},
|
||||||
|
dialog: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder: (context) => CommentModal(
|
||||||
|
comment: item['comment'],
|
||||||
|
onConfirm: (value) {
|
||||||
|
setState(() => fallbackControllers[position] = { 'comment': value });
|
||||||
|
},
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.fallbackDnsServers),
|
title: Text(AppLocalizations.of(context)!.fallbackDnsServers),
|
||||||
|
@ -167,37 +250,68 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
if (c['controller'] != null) Expanded(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: c['controller'],
|
controller: c['controller'],
|
||||||
onChanged: (value) => validateIp(c, value),
|
onChanged: (value) => validateIp(c, value),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: const Icon(Icons.dns_rounded),
|
prefixIcon: Icon(
|
||||||
|
c['isComment'] == true
|
||||||
|
? Icons.comment_rounded
|
||||||
|
: Icons.dns_rounded
|
||||||
|
),
|
||||||
border: const OutlineInputBorder(
|
border: const OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: BorderRadius.all(
|
||||||
Radius.circular(10)
|
Radius.circular(10)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
errorText: c['error'],
|
errorText: c['error'],
|
||||||
labelText: AppLocalizations.of(context)!.dnsServer,
|
labelText: c['isComment'] == true
|
||||||
|
? AppLocalizations.of(context)!.comment
|
||||||
|
: AppLocalizations.of(context)!.dnsServer,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
if (c['comment'] != null) Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
c['comment'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => openEditCommentModal(c, fallbackControllers.indexOf(c)),
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: AppLocalizations.of(context)!.edit,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList());
|
setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList());
|
||||||
checkValidValues();
|
checkValidValues();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.remove_circle_outline)
|
icon: const Icon(Icons.remove_circle_outline),
|
||||||
|
tooltip: AppLocalizations.of(context)!.remove,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: openAddCommentModal,
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(AppLocalizations.of(context)!.comment)
|
||||||
|
),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => fallbackControllers.add({
|
setState(() => fallbackControllers.add({
|
||||||
|
@ -207,7 +321,7 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
|
||||||
checkValidValues();
|
checkValidValues();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: Text(AppLocalizations.of(context)!.addItem)
|
label: Text(AppLocalizations.of(context)!.address)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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 &&
|
||||||
|
@ -47,7 +48,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
|
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
|
||||||
|
|
||||||
for (var item in dnsProvider.dnsInfo!.upstreamDns) {
|
for (var item in dnsProvider.dnsInfo!.upstreamDns) {
|
||||||
if (item == '#') {
|
if (item.contains("#")) {
|
||||||
dnsServers.add({
|
dnsServers.add({
|
||||||
'comment': item
|
'comment': item
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
|
@ -263,7 +284,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)).toList(),
|
)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
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