diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..9a8f2f6e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** + +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 9aeac923..e5641f18 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -12,6 +12,7 @@ concurrency: jobs: changes: + name: 'Detect changes' runs-on: ubuntu-latest # Required permissions permissions: @@ -45,40 +46,33 @@ jobs: - '*.yaml' windows: - 'windows/**' - - android-linux-build: - name: 'Android / Linux Build' + + flutter-test: + name: 'Flutter Analyze + Test' needs: changes - if: ${{ needs.changes.outputs.lib == 'true' || needs.changes.outputs.test == 'true' || needs.changes.outputs.android == 'true' || needs.changes.outputs.linux == 'true' || needs.changes.outputs.yaml == 'true' }} + if: ${{ needs.changes.outputs.lib == 'true' || needs.changes.outputs.test == 'true' || needs.changes.outputs.yaml == 'true' || needs.changes.outputs.android == 'true' || needs.changes.outputs.linux == 'true' || needs.changes.outputs.macos == 'true' || needs.changes.outputs.windows == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4.1.1 - name: Setup Java JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4.2.1 with: distribution: temurin java-version: '17' - name: Flutter action - uses: subosito/flutter-action@v2.8.0 + uses: subosito/flutter-action@v2.16.0 with: channel: stable + cache: true + cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" + cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" + pub-cache-key: "flutter-pub-:os:-:channel:-:version:-:arch:-:hash:" + pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" - name: Flutter version run: flutter --version - - name: Cache pubspec dependencies - uses: actions/cache@v3.0.7 - with: - path: | - ${{ env.FLUTTER_HOME }}/.pub-cache - **/.packages - **/.flutter-plugins - **/.flutter-plugin-dependencies - **/.dart_tool/package_config.json - key: build-pubspec-${{ hashFiles('**/pubspec.lock') }} - restore-keys: | - build-pubspec- - name: Cache build runner - uses: actions/cache@v2 + uses: actions/cache@v4.0.2 with: path: | **/.dart_tool @@ -87,17 +81,59 @@ jobs: **/*.config.dart key: build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} restore-keys: | + build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} build-runner- - name: Download pub dependencies run: flutter pub get - name: Upgrade pub dependencies run: flutter pub upgrade - name: Run build_runner - run: flutter pub run build_runner build --delete-conflicting-outputs + run: flutter pub run build_runner build - name: Run analyzer run: flutter analyze - name: Run tests run: flutter test + + android-linux-build: + name: 'Build Android + Linux' + needs: [changes, flutter-test] + if: ${{ needs.changes.outputs.lib == 'true' || needs.changes.outputs.test == 'true' || needs.changes.outputs.android == 'true' || needs.changes.outputs.linux == 'true' || needs.changes.outputs.yaml == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + - name: Setup Java JDK + uses: actions/setup-java@v4.2.1 + with: + distribution: temurin + java-version: '17' + - name: Flutter action + uses: subosito/flutter-action@v2.16.0 + with: + channel: stable + cache: true + cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" + cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" + pub-cache-key: "flutter-pub-:os:-:channel:-:version:-:arch:-:hash:" + pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" + - name: Cache build runner + uses: actions/cache@v4.0.2 + with: + path: | + **/.dart_tool + **/*.g.dart + **/*.mocks.dart + **/*.config.dart + key: build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} + restore-keys: | + build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} + build-runner- + - name: Download pub dependencies + run: flutter pub get + - name: Upgrade pub dependencies + run: flutter pub upgrade + - name: Run build_runner + run: flutter pub run build_runner build - name: Build Android if: ${{ needs.changes.outputs.lib == 'true' || needs.changes.outputs.test == 'true' || needs.changes.outputs.android == 'true' || needs.changes.outputs.yaml == 'true' }} run: | @@ -112,36 +148,24 @@ jobs: run: flutter build linux macos-build: - name: 'Macos Build' - needs: changes + name: 'Build Macos' + needs: [changes, flutter-test] if: ${{ needs.changes.outputs.lib == 'true' || needs.changes.outputs.test == 'true' || needs.changes.outputs.macos == 'true' || needs.changes.outputs.yaml == 'true' }} runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v3 - + uses: actions/checkout@v4.1.1 - name: Flutter action - uses: subosito/flutter-action@v2.8.0 + uses: subosito/flutter-action@v2.16.0 with: channel: stable - - - name: Flutter version - run: flutter --version - - - name: Cache pubspec dependencies - uses: actions/cache@v3.0.7 - with: - path: | - ${{ env.FLUTTER_HOME }}/.pub-cache - **/.packages - **/.flutter-plugins - **/.flutter-plugin-dependencies - **/.dart_tool/package_config.json - key: build-pubspec-${{ hashFiles('**/pubspec.lock') }} - restore-keys: | - build-pubspec- + cache: true + cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" + cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" + pub-cache-key: "flutter-pub-:os:-:channel:-:version:-:arch:-:hash:" + pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" - name: Cache build runner - uses: actions/cache@v2 + uses: actions/cache@v4.0.2 with: path: | **/.dart_tool @@ -150,54 +174,59 @@ jobs: **/*.config.dart key: build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} restore-keys: | + build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} build-runner- - + - name: Cache pods + uses: actions/cache@v3 + with: + path: macos/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + ${{ runner.os }}-pods- - name: Download pub dependencies run: flutter pub get - - name: Upgrade pub dependencies run: flutter pub upgrade - - name: Run build_runner - run: flutter pub run build_runner build --delete-conflicting-outputs - - - name: Run analyzer - run: flutter analyze - - - name: Run tests - run: flutter test - + run: flutter pub run build_runner build - name: Build macos run: flutter build macos windows-build: - name: 'Windows Build' - needs: changes + name: 'Build Windows' + needs: [changes, flutter-test] if: ${{ needs.changes.outputs.lib == 'true' || needs.changes.outputs.test == 'true' || needs.changes.outputs.windows == 'true' || needs.changes.outputs.yaml == 'true' }} runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 - + uses: actions/checkout@v4.1.1 - name: Flutter action - uses: subosito/flutter-action@v2.8.0 + uses: subosito/flutter-action@v2.16.0 with: channel: stable - + cache: true + cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" + cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" + pub-cache-key: "flutter-pub-:os:-:channel:-:version:-:arch:-:hash:" + pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" + - name: Cache build runner + uses: actions/cache@v4.0.2 + with: + path: | + **/.dart_tool + **/*.g.dart + **/*.mocks.dart + **/*.config.dart + key: build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} + restore-keys: | + build-runner-${{ hashFiles('**/asset_graph.json', '**/*.dart', '**/pubspec.lock', '**/outputs.json') }} + build-runner- - name: Download pub dependencies run: flutter pub get - - name: Upgrade pub dependencies run: flutter pub upgrade - - name: Run build_runner - run: flutter pub run build_runner build --delete-conflicting-outputs - - - name: Run analyzer - run: flutter analyze - - - name: Run tests - run: flutter test - + run: flutter pub run build_runner build - name: Build windows run: flutter build windows \ No newline at end of file diff --git a/README.md b/README.md index 4d123e64..2267f342 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ Note: macOS build hasn't been notarized yet. Drop mail at fs0c19ty@protonmail.com +## Publishing to F-droid +You can follow this guide to publish your app to f-droid - https://op3nsoc13ty.blogspot.com/2021/06/publish-your-first-flutter-app-to-fdroid.html ## How to Contribute 1. Found bug? Open an [issue](https://github.com/git-elliot/vernet/issues) @@ -62,12 +64,15 @@ Drop mail at fs0c19ty@protonmail.com ## Support and Donate 1. Support this project by becoming stargazer of this project. -2. Buy me a coffee. +2. Rate our app on [Playstore](https://play.google.com/store/apps/details?id=org.fsociety.vernet.store) + +3. Buy me a coffee. | Librepay | |----------| |[![Librepay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/OpenSociety/donate) -3. Support me on Ko-Fi +4. Support me on Ko-Fi [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/fs0c13ty) + diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index 94a7267c..396ffc84 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -31,6 +31,15 @@ platform :android do desc "Deploy a new version to the Google Play" lane :deploy do + upload_to_play_store(skip_upload_metadata: true, + skip_upload_changelogs: true, + skip_upload_images: true, + skip_upload_screenshots: true, + aab: '../build/app/outputs/bundle/storeRelease/app-store-release.aab') + end + + desc "Deploy a new version to the Google Play" + lane :deploy_full do upload_to_play_store(aab: '../build/app/outputs/bundle/storeRelease/app-store-release.aab') end end diff --git a/android/fastlane/README.md b/android/fastlane/README.md index 7ec1207f..93320f61 100644 --- a/android/fastlane/README.md +++ b/android/fastlane/README.md @@ -39,6 +39,14 @@ Submit a new Beta Build to Crashlytics Beta Deploy a new version to the Google Play +### android deploy_full + +```sh +[bundle exec] fastlane android deploy_full +``` + +Deploy a new version to the Google Play + ---- This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. diff --git a/android/fastlane/metadata/android/ru/changelogs/24.txt b/android/fastlane/metadata/android/ru/changelogs/24.txt new file mode 100644 index 00000000..e24d1ff7 --- /dev/null +++ b/android/fastlane/metadata/android/ru/changelogs/24.txt @@ -0,0 +1,3 @@ +Исправление заставки для всех устройств Android. +Кнопка повторного сканирования добавлена ​​рядом с количеством устройств. +Обновлен значок для игрового магазина. \ No newline at end of file diff --git a/android/fastlane/metadata/android/ru/full_description.txt b/android/fastlane/metadata/android/ru/full_description.txt new file mode 100644 index 00000000..17ac0725 --- /dev/null +++ b/android/fastlane/metadata/android/ru/full_description.txt @@ -0,0 +1,9 @@ +Vernet - сетевой анализатор и инструмент мониторинга. + +Возможности +1. Показывает детали Wi-Fi +2. Сканирование устройств (или хостов) в сети +3. Сканирование открытых портов конкретного IP +4. Показывает подробности провайдера + +Vernet - это проект с открытым исходным кодом размещенный на GitHub https://github.com/git-elliot/vernet \ No newline at end of file diff --git a/android/fastlane/metadata/android/ru/images/featureGraphic.png b/android/fastlane/metadata/android/ru/images/featureGraphic.png new file mode 100644 index 00000000..a2861ed1 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/featureGraphic.png differ diff --git a/android/fastlane/metadata/android/ru/images/icon.png b/android/fastlane/metadata/android/ru/images/icon.png new file mode 100644 index 00000000..0351861c Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/icon.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/1.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/1.png new file mode 100644 index 00000000..6cda6a70 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/1.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/2.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/2.png new file mode 100644 index 00000000..534bc198 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/2.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/3.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/3.png new file mode 100644 index 00000000..abf8b7ec Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/3.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/4.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/4.png new file mode 100644 index 00000000..20a4d229 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/4.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/5.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/5.png new file mode 100644 index 00000000..765bf163 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/5.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/6.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/6.png new file mode 100644 index 00000000..a3e1c988 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/6.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/7.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/7.png new file mode 100644 index 00000000..205202f7 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/7.png differ diff --git a/android/fastlane/metadata/android/ru/images/phoneScreenshots/8.png b/android/fastlane/metadata/android/ru/images/phoneScreenshots/8.png new file mode 100644 index 00000000..44d5c3e2 Binary files /dev/null and b/android/fastlane/metadata/android/ru/images/phoneScreenshots/8.png differ diff --git a/android/fastlane/metadata/android/ru/short_description.txt b/android/fastlane/metadata/android/ru/short_description.txt new file mode 100644 index 00000000..e9e380ff --- /dev/null +++ b/android/fastlane/metadata/android/ru/short_description.txt @@ -0,0 +1 @@ +Сканер хостов и портов. Пинг IP или домена. \ No newline at end of file diff --git a/android/fastlane/metadata/android/ru/title.txt b/android/fastlane/metadata/android/ru/title.txt new file mode 100644 index 00000000..8a8d9920 --- /dev/null +++ b/android/fastlane/metadata/android/ru/title.txt @@ -0,0 +1 @@ +Vernet - Анализатор Сети \ No newline at end of file diff --git a/android/fastlane/metadata/android/ru/video.txt b/android/fastlane/metadata/android/ru/video.txt new file mode 100644 index 00000000..e69de29b diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index f4628ccb..63e4ed14 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip #distributionSha256Sum=22449f5231796abd892c98b2a07c9ceebe4688d192cd2d6763f8e3bf8acbedeb diff --git a/android/settings.gradle b/android/settings.gradle index 440aafa1..271daa92 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.1.0' apply false + id "com.android.application" version '8.5.2' apply false id "org.jetbrains.kotlin.android" version "1.9.10" apply false } diff --git a/lib/helper/utils_helper.dart b/lib/helper/utils_helper.dart index b6500cd2..fac875cb 100644 --- a/lib/helper/utils_helper.dart +++ b/lib/helper/utils_helper.dart @@ -7,7 +7,7 @@ Future launchURL(String url) async => await canLaunchUrlString(url) : throw 'Could not launch $url'; Future launchURLWithWarning(BuildContext context, String url) { - return showDialog( + return showAdaptiveDialog( context: context, builder: (context) => ExternalLinkWarningDialog( link: url, diff --git a/lib/pages/base_page.dart b/lib/pages/base_page.dart index 60e0dc27..95c4a3df 100644 --- a/lib/pages/base_page.dart +++ b/lib/pages/base_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; import 'package:vernet/ui/popular_chip.dart'; abstract class BasePage extends State { @@ -26,7 +27,7 @@ abstract class BasePage extends State { Widget buildPopularChips() { return Card( - child: ListTile( + child: AdaptiveListTile( title: const Text('Popular targets'), subtitle: Wrap( children: [ diff --git a/lib/pages/dns/dns_page.dart b/lib/pages/dns/dns_page.dart index 351f4eb5..ca09ab26 100644 --- a/lib/pages/dns/dns_page.dart +++ b/lib/pages/dns/dns_page.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:vernet/pages/base_page.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; class DNSPage extends StatefulWidget { const DNSPage({super.key}); @@ -26,7 +27,7 @@ class _DNSPageState extends BasePage { : ListView.builder( itemCount: _addresses.length, itemBuilder: (context, index) { - return ListTile( + return AdaptiveListTile( onTap: () { Clipboard.setData( ClipboardData(text: _addresses[index].address), diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 32ed1032..e7f83f60 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -13,6 +13,7 @@ import 'package:vernet/pages/dns/reverse_dns_page.dart'; import 'package:vernet/pages/host_scan_page/host_scan_page.dart'; import 'package:vernet/pages/network_troubleshoot/port_scan_page.dart'; import 'package:vernet/pages/ping_page/ping_page.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; import 'package:vernet/ui/custom_tile.dart'; class HomePage extends StatefulWidget { @@ -61,7 +62,7 @@ class _WifiDetailState extends State { Card( child: _wifiInfo == null ? const CircularProgressIndicator.adaptive() - : ListTile( + : AdaptiveListTile( minVerticalPadding: 10, leading: const Icon(Icons.router), title: Text(_wifiInfo!.name), @@ -107,7 +108,7 @@ class _WifiDetailState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( leading: const Icon(Icons.network_check), title: const Text('Network Troubleshooting'), minVerticalPadding: 10, @@ -148,7 +149,7 @@ class _WifiDetailState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( leading: const Icon(Icons.dns), title: const Text('Domain Name System (DNS)'), minVerticalPadding: 10, @@ -189,7 +190,7 @@ class _WifiDetailState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( leading: const Icon(Icons.signal_cellular_alt), title: const Text('Internet Service Provider (ISP)'), subtitle: Column( diff --git a/lib/pages/host_scan_page/widgets/host_scan_widget.dart b/lib/pages/host_scan_page/widgets/host_scan_widget.dart index 2308a097..11eb93f4 100644 --- a/lib/pages/host_scan_page/widgets/host_scan_widget.dart +++ b/lib/pages/host_scan_page/widgets/host_scan_widget.dart @@ -5,6 +5,7 @@ import 'package:vernet/main.dart'; import 'package:vernet/pages/host_scan_page/device_in_the_network.dart'; import 'package:vernet/pages/host_scan_page/host_scan_bloc/host_scan_bloc.dart'; import 'package:vernet/pages/network_troubleshoot/port_scan_page.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; class HostScanWidget extends StatelessWidget { @override @@ -60,7 +61,7 @@ class HostScanWidget extends StatelessWidget { return Flex( direction: Axis.vertical, children: [ - ListTile( + AdaptiveListTile( title: Text( "Found ${activeHostList.length} devices", textAlign: TextAlign.center, @@ -88,7 +89,7 @@ class HostScanWidget extends StatelessWidget { itemCount: activeHostList.length, itemBuilder: (context, index) { final DeviceInTheNetwork host = activeHostList[index]; - return ListTile( + return AdaptiveListTile( leading: Icon(host.iconData), title: FutureBuilder( future: host.make, diff --git a/lib/pages/network_troubleshoot/port_scan_page.dart b/lib/pages/network_troubleshoot/port_scan_page.dart index a3534fe1..6143685c 100644 --- a/lib/pages/network_troubleshoot/port_scan_page.dart +++ b/lib/pages/network_troubleshoot/port_scan_page.dart @@ -6,6 +6,7 @@ import 'package:percent_indicator/percent_indicator.dart'; import 'package:vernet/helper/port_desc_loader.dart'; import 'package:vernet/main.dart'; import 'package:vernet/models/port.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; import 'package:vernet/ui/custom_tile.dart'; import 'package:vernet/ui/popular_chip.dart'; @@ -442,7 +443,7 @@ class _PortScanPageState extends State return Column( children: [ - ListTile( + AdaptiveListTile( dense: true, contentPadding: const EdgeInsets.only( left: 10.0, diff --git a/lib/pages/ping_page/ping_page.dart b/lib/pages/ping_page/ping_page.dart index 6d35d2f6..00a04844 100644 --- a/lib/pages/ping_page/ping_page.dart +++ b/lib/pages/ping_page/ping_page.dart @@ -5,6 +5,7 @@ import 'package:dart_ping/dart_ping.dart'; import 'package:flutter/material.dart'; import 'package:vernet/main.dart'; import 'package:vernet/pages/base_page.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; class PingPage extends StatefulWidget { const PingPage({super.key}); @@ -81,7 +82,7 @@ class _PingPageState extends BasePage { Widget buildResults(BuildContext context) { return Column( children: [ - ListTile(title: _getPingSummary()), + AdaptiveListTile(title: _getPingSummary()), if (_pingPackets.isEmpty) const Center( child: Text('Ping results will appear here'), @@ -100,7 +101,7 @@ class _PingPageState extends BasePage { } return Column( children: [ - ListTile( + AdaptiveListTile( dense: true, contentPadding: const EdgeInsets.only(left: 10.0, right: 10.0), diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index fe55062d..95a4b635 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -5,6 +5,7 @@ import 'package:vernet/api/update_checker.dart'; import 'package:vernet/helper/utils_helper.dart'; import 'package:vernet/main.dart'; import 'package:vernet/models/dark_theme_provider.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; import 'package:vernet/ui/settings_dialog/custom_subnet_dialog.dart'; import 'package:vernet/ui/settings_dialog/first_subnet_dialog.dart'; import 'package:vernet/ui/settings_dialog/last_subnet_dialog.dart'; @@ -28,11 +29,11 @@ class _SettingsPageState extends State { child: Column( children: [ Card( - child: ListTile( + child: AdaptiveListTile( title: const Text('Theme'), subtitle: Text(themeChange.themePref.name), onTap: () async { - await showDialog( + await showAdaptiveDialog( context: context, builder: (context) => const ThemeDialog(), ); @@ -42,7 +43,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text('In-App Internet'), trailing: Switch( value: appSettings.inAppInternet, @@ -55,7 +56,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text(StringValue.firstSubnet), subtitle: const Text(StringValue.firstSubnetDesc), trailing: Text( @@ -66,7 +67,7 @@ class _SettingsPageState extends State { ?.copyWith(color: Theme.of(context).colorScheme.secondary), ), onTap: () async { - await showDialog( + await showAdaptiveDialog( context: context, builder: (context) => const FirstSubnetDialog(), ); @@ -76,7 +77,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text(StringValue.lastSubnet), subtitle: const Text(StringValue.lastSubnetDesc), trailing: Text( @@ -87,7 +88,7 @@ class _SettingsPageState extends State { ?.copyWith(color: Theme.of(context).colorScheme.secondary), ), onTap: () async { - await showDialog( + await showAdaptiveDialog( context: context, builder: (context) => const LastSubnetDialog(), ); @@ -97,7 +98,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text(StringValue.socketTimeout), subtitle: const Text(StringValue.socketTimeoutdesc), trailing: Text( @@ -108,7 +109,7 @@ class _SettingsPageState extends State { ?.copyWith(color: Theme.of(context).colorScheme.secondary), ), onTap: () async { - await showDialog( + await showAdaptiveDialog( context: context, builder: (context) => const SocketTimeoutDialog(), ); @@ -118,7 +119,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text(StringValue.pingCount), subtitle: const Text(StringValue.pingCountDesc), trailing: Text( @@ -129,7 +130,7 @@ class _SettingsPageState extends State { ?.copyWith(color: Theme.of(context).colorScheme.secondary), ), onTap: () async { - await showDialog( + await showAdaptiveDialog( context: context, builder: (context) => const PingCountDialog(), ); @@ -139,7 +140,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text(StringValue.customSubnet), subtitle: const Text(StringValue.customSubnetDesc), trailing: Text( @@ -150,7 +151,7 @@ class _SettingsPageState extends State { ?.copyWith(color: Theme.of(context).colorScheme.secondary), ), onTap: () async { - await showDialog( + await showAdaptiveDialog( context: context, builder: (context) => const CustomSubnetDialog(), ); @@ -160,7 +161,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text('Check for Updates'), trailing: IconButton( icon: const Icon(Icons.refresh), @@ -171,7 +172,7 @@ class _SettingsPageState extends State { ), ), Card( - child: ListTile( + child: AdaptiveListTile( title: const Text('About'), onTap: () async { final info = await PackageInfo.fromPlatform(); @@ -181,28 +182,28 @@ class _SettingsPageState extends State { applicationVersion: '${info.version}+${info.buildNumber}', applicationIcon: const Icon(Icons.radar), children: [ - ListTile( + AdaptiveListTile( leading: const Icon(Icons.bug_report), title: const Text('Report Issues'), onTap: () { launchURLWithWarning(context, _issueUrl); }, ), - ListTile( + AdaptiveListTile( leading: const Icon(Icons.favorite), title: const Text('Donate'), onTap: () { launchURLWithWarning(context, _donateUrl); }, ), - ListTile( + AdaptiveListTile( leading: const Icon(Icons.code), title: const Text('Source Code'), onTap: () { launchURLWithWarning(context, _srcUrl); }, ), - const ListTile( + const AdaptiveListTile( title: Text( 'Made with ❤️ in India', textAlign: TextAlign.center, diff --git a/lib/ui/adaptive/adaptive_dialog.dart b/lib/ui/adaptive/adaptive_dialog.dart new file mode 100644 index 00000000..118ced84 --- /dev/null +++ b/lib/ui/adaptive/adaptive_dialog.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:vernet/models/dark_theme_provider.dart'; + +class AdaptiveDialog extends StatelessWidget { + const AdaptiveDialog({ + super.key, + this.title, + this.content, + required this.actions, + }); + + final Widget? title; + final Widget? content; + final List actions; + + @override + Widget build(BuildContext context) { + final themeChange = Provider.of(context); + return Platform.isIOS || Platform.isMacOS + ? CupertinoTheme( + data: CupertinoThemeData( + brightness: Theme.of(context).brightness, + primaryColor: + themeChange.darkTheme ? Colors.white54 : Colors.black54, + ), + child: CupertinoAlertDialog( + title: title, + content: content, + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + onPressed: () { + Navigator.pop(context); + }, + child: const Text( + "Close", + style: TextStyle(color: Colors.blueAccent), + ), + ), + ...actions, + ], + ), + ) + : AlertDialog( + title: title, + content: content, + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text("Close"), + ), + ...actions, + ], + ); + } +} diff --git a/lib/ui/adaptive/adaptive_dialog_action.dart b/lib/ui/adaptive/adaptive_dialog_action.dart new file mode 100644 index 00000000..f1ec7257 --- /dev/null +++ b/lib/ui/adaptive/adaptive_dialog_action.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class AdaptiveDialogAction extends StatelessWidget { + const AdaptiveDialogAction({ + super.key, + required this.child, + required this.onPressed, + this.isDefaultAction = false, + this.isDestructiveAction = false, + }); + final Widget child; + final VoidCallback onPressed; + final bool isDefaultAction; + final bool isDestructiveAction; + + @override + Widget build(BuildContext context) { + return Platform.isIOS || Platform.isMacOS + ? CupertinoDialogAction( + onPressed: onPressed, + isDefaultAction: isDefaultAction, + isDestructiveAction: isDestructiveAction, + child: child, + ) + : TextButton( + onPressed: onPressed, + child: child, + ); + } +} diff --git a/lib/ui/adaptive/adaptive_list.dart b/lib/ui/adaptive/adaptive_list.dart new file mode 100644 index 00000000..41e9cf46 --- /dev/null +++ b/lib/ui/adaptive/adaptive_list.dart @@ -0,0 +1,68 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:vernet/models/dark_theme_provider.dart'; + +class AdaptiveListTile extends StatelessWidget { + const AdaptiveListTile({ + super.key, + required this.title, + this.minVerticalPadding, + this.leading, + this.subtitle, + this.trailing, + this.onTap, + this.dense, + this.onLongPress, + this.contentPadding, + }); + + final Widget title; + final Widget? leading; + final Widget? trailing; + final Widget? subtitle; + final GestureTapCallback? onTap; + final GestureLongPressCallback? onLongPress; + final double? minVerticalPadding; + final bool? dense; + final EdgeInsetsGeometry? contentPadding; + + @override + Widget build(BuildContext context) { + final themeChange = Provider.of(context); + return Platform.isIOS || Platform.isMacOS + ? CupertinoTheme( + data: CupertinoThemeData( + brightness: Theme.of(context).brightness, + primaryColor: + themeChange.darkTheme ? Colors.white54 : Colors.black54, + ), + child: Padding( + padding: contentPadding ?? const EdgeInsets.all(10), + child: CupertinoListTile( + leading: leading, + title: title, + subtitle: subtitle, + trailing: trailing, + onTap: onTap, + padding: EdgeInsets.symmetric( + vertical: minVerticalPadding ?? (dense ?? false ? 10 : 5), + ), + ), + ), + ) + : ListTile( + minVerticalPadding: minVerticalPadding, + leading: leading, + title: title, + subtitle: subtitle, + trailing: trailing, + onTap: onTap, + dense: dense, + onLongPress: onLongPress, + contentPadding: contentPadding, + ); + } +} diff --git a/lib/ui/adaptive/adaptive_radio.dart b/lib/ui/adaptive/adaptive_radio.dart new file mode 100644 index 00000000..7fa41679 --- /dev/null +++ b/lib/ui/adaptive/adaptive_radio.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class AdaptiveRadioButton extends StatelessWidget { + const AdaptiveRadioButton({ + super.key, + required this.value, + required this.groupValue, + this.onChanged, + }); + + final T value; + final T groupValue; + final ValueChanged? onChanged; + + @override + Widget build(BuildContext context) { + return Platform.isIOS || Platform.isMacOS + ? CupertinoRadio( + value: value, + groupValue: groupValue, + onChanged: onChanged, + ) + : Radio( + value: value, + groupValue: groupValue, + onChanged: onChanged, + ); + } +} diff --git a/lib/ui/base_settings_dialog.dart b/lib/ui/base_settings_dialog.dart index eb1563bc..5e3f78dc 100644 --- a/lib/ui/base_settings_dialog.dart +++ b/lib/ui/base_settings_dialog.dart @@ -1,4 +1,9 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:vernet/ui/adaptive/adaptive_dialog.dart'; +import 'package:vernet/ui/adaptive/adaptive_dialog_action.dart'; abstract class BaseSettingsDialog extends State { final _formKey = GlobalKey(); @@ -27,32 +32,54 @@ abstract class BaseSettingsDialog extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(getDialogTitle()), - content: Form( + return AdaptiveDialog( + title: title, + content: content, + actions: actions(context), + ); + } + + Widget get title => Text(getDialogTitle()); + Widget get content => Form( key: _formKey, - child: TextFormField( - key: _fieldKey, - controller: _controller, - validator: validate, - keyboardType: getKeyBoardType(), - decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: getHintText(), - ), - ), + child: Platform.isIOS || Platform.isMacOS + ? CupertinoTextFormFieldRow( + key: _fieldKey, + controller: _controller, + validator: validate, + keyboardType: getKeyBoardType(), + placeholder: getHintText(), + decoration: BoxDecoration( + border: Border.all( + width: 2.0, + color: CupertinoColors.inactiveGray, + ), + borderRadius: BorderRadius.circular(32.0), + ), + ) + : TextFormField( + key: _fieldKey, + controller: _controller, + validator: validate, + keyboardType: getKeyBoardType(), + decoration: InputDecoration( + border: const OutlineInputBorder(), + hintText: getHintText(), + ), + ), + ); + List actions(BuildContext context) { + return [ + AdaptiveDialogAction( + onPressed: () { + if (_formKey.currentState!.validate()) { + onSubmit(_controller.text); + Navigator.pop(context); + } + }, + isDestructiveAction: true, + child: const Text('Submit'), ), - actions: [ - ElevatedButton( - onPressed: () { - if (_formKey.currentState!.validate()) { - onSubmit(_controller.text); - Navigator.pop(context); - } - }, - child: const Text('Submit'), - ), - ], - ); + ]; } } diff --git a/lib/ui/external_link_dialog.dart b/lib/ui/external_link_dialog.dart index aea870eb..425ff08a 100644 --- a/lib/ui/external_link_dialog.dart +++ b/lib/ui/external_link_dialog.dart @@ -1,31 +1,33 @@ import 'package:flutter/material.dart'; import 'package:vernet/helper/utils_helper.dart'; +import 'package:vernet/ui/adaptive/adaptive_dialog.dart'; +import 'package:vernet/ui/adaptive/adaptive_dialog_action.dart'; -class ExternalLinkWarningDialog extends StatelessWidget { +class ExternalLinkWarningDialog extends StatelessWidget { const ExternalLinkWarningDialog({super.key, required this.link}); final String link; @override Widget build(BuildContext context) { - return AlertDialog( - title: const Text("Confirm to open external link"), - content: Text(link), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Cancel'), - ), - TextButton.icon( - onPressed: () { - launchURL(link); - }, - icon: const Icon(Icons.link), - label: const Text('Open Link'), - ), - ], + return AdaptiveDialog( + title: title, + content: content, + actions: actions(context), ); } + + Widget get title => const Text("Confirm external link"); + Widget get content => Text(link); + List actions(BuildContext context) { + return [ + AdaptiveDialogAction( + isDestructiveAction: true, + child: const Text('Open Link'), + onPressed: () { + launchURL(link); + }, + ), + ]; + } } diff --git a/lib/ui/settings_dialog/theme_dialog.dart b/lib/ui/settings_dialog/theme_dialog.dart index 7b19b7b0..4573ebd6 100644 --- a/lib/ui/settings_dialog/theme_dialog.dart +++ b/lib/ui/settings_dialog/theme_dialog.dart @@ -1,6 +1,10 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:vernet/models/dark_theme_provider.dart'; +import 'package:vernet/ui/adaptive/adaptive_dialog.dart'; +import 'package:vernet/ui/adaptive/adaptive_list.dart'; +import 'package:vernet/ui/adaptive/adaptive_radio.dart'; class ThemeDialog extends StatefulWidget { const ThemeDialog({super.key}); @@ -18,14 +22,14 @@ class _ThemeDialogState extends State { @override Widget build(BuildContext context) { final themeChange = Provider.of(context); - return AlertDialog( + return AdaptiveDialog( title: const Text("Choose theme"), content: Column( mainAxisSize: MainAxisSize.min, children: [ - ListTile( + AdaptiveListTile( title: const Text('Follow system'), - leading: Radio( + leading: AdaptiveRadioButton( value: ThemePreference.system, groupValue: themeChange.themePref, onChanged: (value) { @@ -33,9 +37,9 @@ class _ThemeDialogState extends State { }, ), ), - ListTile( + AdaptiveListTile( title: const Text('Dark'), - leading: Radio( + leading: AdaptiveRadioButton( value: ThemePreference.dark, groupValue: themeChange.themePref, onChanged: (value) { @@ -43,9 +47,9 @@ class _ThemeDialogState extends State { }, ), ), - ListTile( + AdaptiveListTile( title: const Text('Light'), - leading: Radio( + leading: AdaptiveRadioButton( value: ThemePreference.light, groupValue: themeChange.themePref, onChanged: (value) { @@ -55,6 +59,7 @@ class _ThemeDialogState extends State { ), ], ), + actions: const [], ); } } diff --git a/lib/values/strings.dart b/lib/values/strings.dart index d8607c3c..bf16cf26 100644 --- a/lib/values/strings.dart +++ b/lib/values/strings.dart @@ -18,5 +18,5 @@ class StringValue { static const String customSubnet = 'Custom Subnet'; static const String customSubnetDesc = 'Scan a custom subnet instead of local one.'; - static const String customSubnetHint = 'Enter Gateway IP e.g., 10.102.200.1'; + static const String customSubnetHint = 'e.g., 10.102.200.1'; } diff --git a/pubspec.yaml b/pubspec.yaml index ec3770f5..83c2dc59 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A Network Analyzer publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.6+24 +version: 1.0.7+25 environment: sdk: ">=2.17.0 <3.0.0"