Showing preview only (641K chars total). Download the full file or copy to clipboard to get everything.
Repository: pichillilorenzo/flutter_browser_app
Branch: master
Commit: 2377098d015c
Files: 129
Total size: 600.0 KB
Directory structure:
gitextract_n9elqeox/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── BUG_REPORT.md
│ │ └── FEATURE_REQUEST.md
│ └── workflows/
│ └── main.yml
├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android/
│ ├── .gitignore
│ ├── app/
│ │ ├── build.gradle
│ │ └── src/
│ │ ├── debug/
│ │ │ └── AndroidManifest.xml
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── pichillilorenzo/
│ │ │ │ └── flutter_browser/
│ │ │ │ └── MainActivity.kt
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ ├── launch_background.xml
│ │ │ │ ├── scrollbar_vertical_thumb.xml
│ │ │ │ └── scrollbar_vertical_track.xml
│ │ │ ├── drawable-v21/
│ │ │ │ └── launch_background.xml
│ │ │ ├── values/
│ │ │ │ └── styles.xml
│ │ │ └── values-night/
│ │ │ └── styles.xml
│ │ └── profile/
│ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ └── settings.gradle
├── ios/
│ ├── .gitignore
│ ├── Flutter/
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Runner/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── LaunchImage.imageset/
│ │ │ ├── Contents.json
│ │ │ └── README.md
│ │ ├── Base.lproj/
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ ├── Runner.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── Runner.xcscheme
│ └── Runner.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── lib/
│ ├── animated_flutter_browser_logo.dart
│ ├── app_bar/
│ │ ├── browser_app_bar.dart
│ │ ├── certificates_info_popup.dart
│ │ ├── custom_app_bar_wrapper.dart
│ │ ├── desktop_app_bar.dart
│ │ ├── find_on_page_app_bar.dart
│ │ ├── tab_viewer_app_bar.dart
│ │ ├── url_info_popup.dart
│ │ └── webview_tab_app_bar.dart
│ ├── browser.dart
│ ├── custom_image.dart
│ ├── custom_popup_dialog.dart
│ ├── custom_popup_menu_item.dart
│ ├── empty_tab.dart
│ ├── javascript_console_result.dart
│ ├── long_press_alert_dialog.dart
│ ├── main.dart
│ ├── material_transparent_page_route.dart
│ ├── models/
│ │ ├── browser_model.dart
│ │ ├── favorite_model.dart
│ │ ├── search_engine_model.dart
│ │ ├── web_archive_model.dart
│ │ ├── webview_model.dart
│ │ └── window_model.dart
│ ├── multiselect_dialog.dart
│ ├── pages/
│ │ ├── developers/
│ │ │ ├── javascript_console.dart
│ │ │ ├── main.dart
│ │ │ ├── network_info.dart
│ │ │ └── storage_manager.dart
│ │ └── settings/
│ │ ├── android_settings.dart
│ │ ├── cross_platform_settings.dart
│ │ ├── ios_settings.dart
│ │ └── main.dart
│ ├── popup_menu_actions.dart
│ ├── project_info_popup.dart
│ ├── tab_popup_menu_actions.dart
│ ├── tab_viewer.dart
│ ├── tab_viewer_popup_menu_actions.dart
│ ├── util.dart
│ └── webview_tab.dart
├── macos/
│ ├── .gitignore
│ ├── Flutter/
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Podfile
│ ├── Runner/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ └── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ └── MainMenu.xib
│ │ ├── Configs/
│ │ │ ├── AppInfo.xcconfig
│ │ │ ├── Debug.xcconfig
│ │ │ ├── Release.xcconfig
│ │ │ └── Warnings.xcconfig
│ │ ├── DebugProfile.entitlements
│ │ ├── Info.plist
│ │ ├── MainFlutterWindow.swift
│ │ └── Release.entitlements
│ ├── Runner.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ ├── RunnerTests/
│ │ └── RunnerTests.swift
│ └── packaging/
│ └── dmg/
│ └── make_config.yaml
├── pubspec.yaml
├── test/
│ └── widget_test.dart
└── windows/
├── .gitignore
├── CMakeLists.txt
├── flutter/
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
├── packaging/
│ └── msix/
│ └── make_config.yaml
└── runner/
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.paypal.me/LorenzoPichilli'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/BUG_REPORT.md
================================================
---
name: Bug report
about: Something is crashing or not working as intended
labels: bug
---
<!--
❗️❗️❗️ IMPORTANT ❗️❗️❗️
DON'T DELETE THIS TEMPLATE! USE IT TO WRITE YOUR ISSUE.
If you delete this and you don't give me enough info,
how would you expect me to solve your issue? Thanks.
Also, before posting a new issue, make sure to check the following points!
You may already find an answer to your problem!
❗️❗️❗️ IMPORTANT ❗️❗️❗️
-->
## Environment
| Technology | Version |
| -------------------- | ------------- |
| Flutter version | |
| App version | |
| Android version | |
| iOS version | |
| Xcode version | |
Device information: <!-- Manufacturer and model -->
## Description
**Expected behavior:**
**Current behavior:**
## Steps to reproduce
<!-- Optionally provide the least amount of code that shows this behaviour. -->
1. This
2. Than that
3. Then
## Images <!-- if available, else delete -->
## Stacktrace/Logcat <!-- if available, else delete -->
================================================
FILE: .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
================================================
---
name: Feature request
about: Suggest an idea for this project
---
## Environment
**Flutter version:**
**App version:** <!-- Add branch if necessary -->
**Android version:** <!-- If customize ROM, write which -->
**iOS version:**
**Xcode version:**
**Device information:** <!-- Manufacturer and model -->
## Description
**What you'd like to happen:**
**Alternatives you've considered:** <!-- if available, else delete -->
**Images:** <!-- if available, else delete -->
================================================
FILE: .github/workflows/main.yml
================================================
name: "Build & Release"
on:
push:
tags:
- "v*"
jobs:
build-mac-ios-android:
runs-on: macos-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Download Android keystore
id: android_keystore
uses: timheuer/base64-to-file@v1.2
with:
fileName: keystore.jks
encodedString: ${{ secrets.KEYSTORE_BASE64 }}
- name: Create key.properties
run: |
echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" >> android/key.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: "17"
cache: 'gradle'
- name: Flutter action
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.x'
cache: true
- name: Restore packages
run: |
flutter pub get
- name: Install appdmg
run: npm install -g appdmg
- name: Install flutter_distributor
run: dart pub global activate flutter_distributor
- name: Build APK
run: |
flutter build apk --release --split-per-abi
- name: Upload APK to Artifacts
uses: actions/upload-artifact@v3
with:
name: android
path: |
build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
build/app/outputs/flutter-apk/app-x86_64-release.apk
- name: Build IPA
run: |
flutter build ios --release --no-codesign
- name: Create IPA
run: |
mkdir build/ios/iphoneos/Payload
cp -R build/ios/iphoneos/Runner.app build/ios/iphoneos/Payload/Runner.app
cd build/ios/iphoneos/
zip -q -r ios_no_sign.ipa Payload
cd ../../..
- name: Upload IPA to Artifacts
uses: actions/upload-artifact@v3
with:
name: ios
path: |
build/ios/iphoneos/ios_no_sign.ipa
- name: Build MacOS
run: |
flutter_distributor package --platform macos --targets dmg,zip --skip-clean
- name: Upload MacOS to Artifacts
uses: actions/upload-artifact@v4
with:
name: mac
path: |
dist/*/*.dmg
dist/*/*.zip
- name: Extract version from pubspec.yaml
id: yq
run: |
yq -r '.version' 'pubspec.yaml'
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
name: "${{ steps.yq.outputs.result }}"
token: ${{ secrets.TOKEN }}
files: |
build/app/outputs/flutter-apk/app-x86_64-release.apk
build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
build/ios/iphoneos/ios_no_sign.ipa
dist/*/*.dmg
dist/*/*.zip
- run: echo "🍏 This job's status is ${{ job.status }}."
build-windows:
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Install yq command line utility
run: choco install yq
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: "3.24.x"
cache: true
- name: Restore Packages
run: |
flutter pub get
- name: Install flutter_distributor
run: dart pub global activate flutter_distributor
- name: Build Windows
run: |
flutter_distributor package --platform windows --targets msix,zip --skip-clean
- name: Upload Windows APP to Artifacts
uses: actions/upload-artifact@v4
with:
name: windows
path: |
dist/*/*.msix
dist/*/*.zip
- name: Extract version from pubspec.yaml
id: yq
run: |
yq -r '.version' 'pubspec.yaml'
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
name: "${{ steps.yq.outputs.result }}"
token: ${{ secrets.TOKEN }}
files: |
dist/*/*.msix
dist/*/*.zip
- run: echo "🍏 Windows job's status is ${{ job.status }}."
================================================
FILE: .gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
.fvm/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
================================================
FILE: .metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "2663184aa79047d0a33a14a3b607954f8fdd8730"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: macos
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: windows
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2020 Lorenzo Pichilli
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Flutter Browser App

A Full-Featured Mobile and Desktop Browser App (such as the Google Chrome mobile browser) created using Flutter and the features offered by the [flutter_inappwebview](https://github.com/pichillilorenzo/flutter_inappwebview) plugin.
It is available on the **Google Play Store** at [https://play.google.com/store/apps/details?id=com.pichillilorenzo.flutter_browser](https://play.google.com/store/apps/details?id=com.pichillilorenzo.flutter_browser)
For Desktop builds, check the [Releases](https://github.com/pichillilorenzo/flutter_browser_app/releases) page.
## Introduction
**Old article**: [Creating a Full-Featured Browser using WebViews in Flutter](https://medium.com/flutter-community/creating-a-full-featured-browser-using-webviews-in-flutter-9c8f2923c574?source=friends_link&sk=55fc8267f351082aa9e73ced546f6bcb).
**OLD**: Check out also the article that introduces the [flutter_inappwebview](https://github.com/pichillilorenzo/flutter_inappwebview) plugin here: [InAppWebView: The Real Power of WebViews in Flutter](https://medium.com/flutter-community/inappwebview-the-real-power-of-webviews-in-flutter-c6d52374209d?source=friends_link&sk=cb74487219bcd85e610a670ee0b447d0).
## Features
- **Multi-Window Support on Desktop**;
- **WebView Tab**, with custom on long-press link/image preview, and how to move from one tab to another without losing the WebView state;
- **Browser App Bar** with the current URL and all popup menu actions such as opening a new tab, a new incognito tab, saving the current URL to the favorite list, saving a page to offline usage, viewing the SSL Certificate used by the website, enable Desktop Mode, etc. (features similar to the Google Chrome App);
- **Developer console**, where you can execute JavaScript code, see some network info, manage the browser storage such as cookies, window.localStorage, etc;
- **Settings page**, where you can update the browser general settings and enable/disable all the features offered by the flutter_inappwebview for each WebView Tab, such as enabling/disabling JavaScript, caching, scrollbars, setting custom user-agent, etc., and all the Android and iOS-specific features;
- **Save** and **restore** the current Browser state.
### Final Result
**Old video**: [Flutter Browser App Final Result](https://drive.google.com/file/d/1wE2yUGwjNBiUy72GOjPIYyDXYQn3ewYn/view?usp=sharing).
If you found this useful and you like the [flutter_inappwebview](https://github.com/pichillilorenzo/flutter_inappwebview) plugin and this App project, give a star to these projects, thanks!
================================================
FILE: analysis_options.yaml
================================================
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
================================================
FILE: android/.gitignore
================================================
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
================================================
FILE: android/app/build.gradle
================================================
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdk 34
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
applicationId "com.pichillilorenzo.flutter_browser"
minSdkVersion flutter.minSdkVersion
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
ndk {
debugSymbolLevel 'FULL'
}
}
}
namespace 'com.pichillilorenzo.flutter_browser'
lint {
disable 'InvalidPackage'
}
}
flutter {
source '../..'
}
dependencies {
implementation 'com.google.android.material:material:1.12.0'
def multidex_version = "2.0.1"
implementation "androidx.multidex:multidex:$multidex_version"
}
================================================
FILE: android/app/src/debug/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
================================================
FILE: android/app/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:label="Flutter Browser"
android:name="${applicationName}"
android:usesCleartextTraffic="true"
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- For these schemes were not particular MIME type has been
supplied, we are a good candidate. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="about" />
<data android:scheme="javascript" />
</intent-filter>
<!-- For these schemes where any of these particular MIME types
have been supplied, we are a good candidate. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="inline" />
<data android:mimeType="text/html"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="application/xhtml+xml"/>
<data android:mimeType="application/vnd.wap.xhtml+xml"/>
</intent-filter>
<!-- We are also the main entry point of the browser. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<provider
android:name="com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider"
android:authorities="${applicationId}.flutter_inappwebview_android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
android:authorities="${applicationId}.flutter_downloader.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
================================================
FILE: android/app/src/main/kotlin/com/pichillilorenzo/flutter_browser/MainActivity.kt
================================================
package com.pichillilorenzo.flutter_browser
import android.app.SearchManager
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.speech.RecognizerResultsIntent
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
private var url: String? = null;
//private var headers: Map<String, String>? = null;
private val CHANNEL = "com.pichillilorenzo.flutter_browser.intent_data"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Log.d("intent URI", intent.toUri(0));
var url: String? = null
//var headers: Map<String, String>? = null
val action = intent.action
if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS == action) {
return
}
if (Intent.ACTION_VIEW == action) {
val data: Uri? = intent.data
if (data != null) url = data.toString()
} else if (Intent.ACTION_SEARCH == action || MediaStore.INTENT_ACTION_MEDIA_SEARCH == action
|| Intent.ACTION_WEB_SEARCH == action) {
url = intent.getStringExtra(SearchManager.QUERY)
}
// if (url != null && url.startsWith("http")) {
// val pairs = intent
// .getBundleExtra(Browser.EXTRA_HEADERS)
// if (pairs != null && !pairs.isEmpty) {
// val iter: Iterator<String> = pairs.keySet().iterator()
// headers = HashMap()
// while (iter.hasNext()) {
// val key = iter.next()
// headers.put(key, pairs.getString(key)!!)
// }
// }
// }
this.url = url
//this.headers = headers
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
val methodName = call.method
if (methodName == "getIntentData") {
// val data = ArrayList<Any?>();
// data.add(url)
// data.add(headers)
result.success(url)
this.url = null;
//this.headers = null;
}
}
}
}
================================================
FILE: android/app/src/main/res/drawable/launch_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
================================================
FILE: android/app/src/main/res/drawable/scrollbar_vertical_thumb.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="0"
android:endColor="#005A87"
android:startColor="#007AB8" />
<corners android:radius="6dp" />
</shape>
================================================
FILE: android/app/src/main/res/drawable/scrollbar_vertical_track.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="0"
android:endColor="#9BA3C5"
android:startColor="#8388A4" />
<corners android:radius="6dp" />
</shape>
================================================
FILE: android/app/src/main/res/drawable-v21/launch_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
================================================
FILE: android/app/src/main/res/values/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
================================================
FILE: android/app/src/main/res/values-night/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
================================================
FILE: android/app/src/profile/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
================================================
FILE: android/build.gradle
================================================
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
================================================
FILE: android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
================================================
FILE: android/settings.gradle
================================================
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '7.4.2' apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"
================================================
FILE: ios/.gitignore
================================================
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
================================================
FILE: ios/Flutter/AppFrameworkInfo.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
================================================
FILE: ios/Flutter/Debug.xcconfig
================================================
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: ios/Flutter/Release.xcconfig
================================================
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: ios/Podfile
================================================
# Uncomment this line to define a global platform for your project
platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
================================================
FILE: ios/Runner/AppDelegate.swift
================================================
import UIKit
import Flutter
import flutter_downloader
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
private func registerPlugins(registry: FlutterPluginRegistry) {
if (!registry.hasPlugin("FlutterDownloaderPlugin")) {
FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!)
}
}
================================================
FILE: ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
================================================
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
================================================
FILE: ios/Runner/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
================================================
FILE: ios/Runner/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
================================================
FILE: ios/Runner/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>FlutterBrowser</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSBonjourServices</key>
<array>
<string>_dartobservatory._tcp</string>
</array>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) camera use</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) microphone use</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
================================================
FILE: ios/Runner/Runner-Bridging-Header.h
================================================
#import "GeneratedPluginRegistrant.h"
================================================
FILE: ios/Runner.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
420340CC0D9D0D1B41D8C7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65580FF38E583F75628F4B2B /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3D42713954B5CD677C63D861 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
58603F292996258C3A0A1BB4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
65580FF38E583F75628F4B2B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9BF35EFC29634C4D7E14C52D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
420340CC0D9D0D1B41D8C7D1 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
04C19C2BB5F0C41EB406429B /* Frameworks */ = {
isa = PBXGroup;
children = (
65580FF38E583F75628F4B2B /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
B49E7B33350EC7421B2098A9 /* Pods */,
04C19C2BB5F0C41EB406429B /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
);
name = "Supporting Files";
sourceTree = "<group>";
};
B49E7B33350EC7421B2098A9 /* Pods */ = {
isa = PBXGroup;
children = (
58603F292996258C3A0A1BB4 /* Pods-Runner.debug.xcconfig */,
3D42713954B5CD677C63D861 /* Pods-Runner.release.xcconfig */,
9BF35EFC29634C4D7E14C52D /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
13F2619FF9F3F4C52B0076CA /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
37FF70F36850709A3BFADFDB /* [CP] Embed Pods Frameworks */,
ADCA64C961EA4FED14CC71A2 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
13F2619FF9F3F4C52B0076CA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
37FF70F36850709A3BFADFDB /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
ADCA64C961EA4FED14CC71A2 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = PFP8UV45Y6;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-browser-app2";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = PFP8UV45Y6;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-browser-app2";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = PFP8UV45Y6;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-browser-app2";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
================================================
FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: ios/Runner.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
================================================
FILE: lib/animated_flutter_browser_logo.dart
================================================
import 'package:flutter/material.dart';
class AnimatedFlutterBrowserLogo extends StatefulWidget {
final Duration animationDuration;
final double size;
const AnimatedFlutterBrowserLogo({
super.key,
this.animationDuration = const Duration(milliseconds: 1000),
this.size = 100.0,
});
@override
State<StatefulWidget> createState() => _AnimatedFlutterBrowserLogoState();
}
class _AnimatedFlutterBrowserLogoState extends State<AnimatedFlutterBrowserLogo>
with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller =
AnimationController(duration: widget.animationDuration, vsync: this);
_controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: Tween(begin: 0.75, end: 2.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.elasticOut)),
child: SizedBox(
height: widget.size,
width: widget.size,
child: const CircleAvatar(
backgroundImage: AssetImage("assets/icon/icon.png")),
),
);
}
}
================================================
FILE: lib/app_bar/browser_app_bar.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_browser/app_bar/desktop_app_bar.dart';
import 'package:flutter_browser/app_bar/find_on_page_app_bar.dart';
import 'package:flutter_browser/app_bar/webview_tab_app_bar.dart';
import 'package:flutter_browser/util.dart';
class BrowserAppBar extends StatefulWidget implements PreferredSizeWidget {
BrowserAppBar({super.key})
: preferredSize =
Size.fromHeight(Util.isMobile() ? kToolbarHeight : 90.0);
@override
State<BrowserAppBar> createState() => _BrowserAppBarState();
@override
final Size preferredSize;
}
class _BrowserAppBarState extends State<BrowserAppBar> {
bool _isFindingOnPage = false;
@override
Widget build(BuildContext context) {
final List<Widget> children = [];
if (Util.isDesktop()) {
children.add(const DesktopAppBar());
}
children.add(_isFindingOnPage
? FindOnPageAppBar(
hideFindOnPage: () {
setState(() {
_isFindingOnPage = false;
});
},
)
: WebViewTabAppBar(
showFindOnPage: () {
setState(() {
_isFindingOnPage = true;
});
},
));
return Column(
children: children,
);
}
}
================================================
FILE: lib/app_bar/certificates_info_popup.dart
================================================
import 'dart:developer';
import 'dart:io';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_browser/models/webview_model.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:collection/collection.dart';
import 'package:url_launcher/url_launcher.dart';
class CertificateInfoPopup extends StatefulWidget {
const CertificateInfoPopup({super.key});
@override
State<CertificateInfoPopup> createState() => _CertificateInfoPopupState();
}
class _CertificateInfoPopupState extends State<CertificateInfoPopup> {
final List<X509Certificate> _otherCertificates = [];
X509Certificate? _topMainCertificate;
X509Certificate? _selectedCertificate;
@override
Widget build(BuildContext context) {
return _build();
}
Widget _build() {
if (_topMainCertificate == null) {
var webViewModel = Provider.of<WebViewModel>(context, listen: true);
return FutureBuilder(
future: webViewModel.webViewController?.getCertificate(),
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.connectionState != ConnectionState.done) {
return Container();
}
SslCertificate sslCertificate = snapshot.data as SslCertificate;
_topMainCertificate = sslCertificate.x509Certificate;
_selectedCertificate = _topMainCertificate!;
return FutureBuilder(
future: _getOtherCertificatesFromTopMain(
_otherCertificates, _topMainCertificate!),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return _buildCertificatesInfoAlertDialog();
}
return Center(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(2.5))),
padding: const EdgeInsets.all(25.0),
width: 100.0,
height: 100.0,
child: const CircularProgressIndicator(
strokeWidth: 4.0,
),
),
);
},
);
},
);
} else {
return _buildCertificatesInfoAlertDialog();
}
}
Future<void> _getOtherCertificatesFromTopMain(
List<X509Certificate> otherCertificates,
X509Certificate x509certificate) async {
var authorityInfoAccess = x509certificate.authorityInfoAccess;
if (authorityInfoAccess != null && authorityInfoAccess.infoAccess != null) {
for (var i = 0; i < authorityInfoAccess.infoAccess!.length; i++) {
try {
var caIssuerUrl = authorityInfoAccess
.infoAccess![i].location; // [OID.caIssuers.toValue()];
HttpClientRequest request =
await HttpClient().getUrl(Uri.parse(caIssuerUrl));
HttpClientResponse response = await request.close();
var certData = Uint8List.fromList(await response.first);
var cert = X509Certificate.fromData(data: certData);
otherCertificates.add(cert);
await _getOtherCertificatesFromTopMain(otherCertificates, cert);
} catch (e) {
log(e.toString());
}
}
}
var cRLDistributionPoints = x509certificate.cRLDistributionPoints;
if (cRLDistributionPoints != null && cRLDistributionPoints.crls != null) {
for (var i = 0; i < cRLDistributionPoints.crls!.length; i++) {
var crlUrl = cRLDistributionPoints.crls![i];
try {
HttpClientRequest request =
await HttpClient().getUrl(Uri.parse(crlUrl));
HttpClientResponse response = await request.close();
var certData = Uint8List.fromList(await response.first);
var cert = X509Certificate.fromData(data: certData);
otherCertificates.add(cert);
await _getOtherCertificatesFromTopMain(otherCertificates, cert);
} catch (e) {
log(e.toString());
}
}
}
}
AlertDialog _buildCertificatesInfoAlertDialog() {
var webViewModel = Provider.of<WebViewModel>(context, listen: true);
var url = webViewModel.url;
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
decoration: const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(5.0))),
padding: const EdgeInsets.all(5.0),
child: const Icon(
Icons.lock,
color: Colors.white,
size: 20.0,
),
),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
url?.host ?? "",
style: const TextStyle(
fontSize: 16.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 15.0,
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
"Flutter Browser has verified that ${_topMainCertificate?.issuer(dn: ASN1DistinguishedNames.COMMON_NAME)} has emitted the web site certificate.",
softWrap: true,
style: const TextStyle(fontSize: 12.0),
),
),
],
),
const SizedBox(
height: 15.0,
),
RichText(
text: TextSpan(
text: "Certificate info",
style: const TextStyle(
color: Colors.blue, fontSize: 12.0),
recognizer: TapGestureRecognizer()
..onTap = () {
showDialog(
context: context,
builder: (context) {
List<X509Certificate> certificates = [
_topMainCertificate!
];
certificates.addAll(_otherCertificates);
return AlertDialog(
content: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context)
.size
.width /
2.5),
child: StatefulBuilder(
builder: (context, setState) {
List<
DropdownMenuItem<
X509Certificate>>
dropdownMenuItems = [];
for (var certificate
in certificates) {
var name = _findCommonName(
x509certificate:
certificate,
isSubject: true) ??
_findOrganizationName(
x509certificate:
certificate,
isSubject: true) ??
"";
if (name != "") {
dropdownMenuItems.add(
DropdownMenuItem<
X509Certificate>(
value: certificate,
child: Text(name),
));
}
}
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text(
"Certificate Viewer",
style: TextStyle(
fontSize: 24.0,
color: Colors.black,
fontWeight:
FontWeight.bold),
),
const SizedBox(
height: 15.0,
),
DropdownButton<
X509Certificate>(
isExpanded: true,
onChanged: (value) {
setState(() {
_selectedCertificate =
value;
});
},
value: _selectedCertificate,
items: dropdownMenuItems,
),
const SizedBox(
height: 15.0,
),
Flexible(
child:
SingleChildScrollView(
child: _buildCertificateInfo(
_selectedCertificate!),
),
),
],
);
},
)),
);
},
);
}),
)
],
),
),
),
],
)
],
),
);
}
Widget _buildCertificateInfo(X509Certificate x509certificate) {
var issuedToSection = _buildIssuedToSection(x509certificate);
var issuedBySection = _buildIssuedBySection(x509certificate);
var validityPeriodSection = _buildValidityPeriodSection(x509certificate);
var publicKeySection = _buildPublicKeySection(x509certificate);
var fingerprintSection = _buildFingerprintSection(x509certificate);
var extensionSection = _buildExtensionSection(x509certificate);
var children = <Widget>[];
children.addAll(issuedToSection);
children.addAll(issuedBySection);
children.addAll(validityPeriodSection);
children.addAll(publicKeySection);
children.addAll(fingerprintSection);
children.addAll(extensionSection);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
String? _findCountryName(
{required X509Certificate x509certificate, required bool isSubject}) {
try {
return (isSubject
? x509certificate.subject(dn: ASN1DistinguishedNames.COUNTRY_NAME)
: x509certificate.issuer(
dn: ASN1DistinguishedNames.COUNTRY_NAME)) ??
x509certificate.block1
?.findOid(oid: OID.countryName)
?.parent
?.sub
?.last
.value;
} catch (e) {
log(e.toString());
}
return null;
}
String? _findStateOrProvinceName(
{required X509Certificate x509certificate, required bool isSubject}) {
try {
return (isSubject
? x509certificate.subject(
dn: ASN1DistinguishedNames.STATE_OR_PROVINCE_NAME)
: x509certificate.issuer(
dn: ASN1DistinguishedNames.STATE_OR_PROVINCE_NAME)) ??
x509certificate.block1
?.findOid(oid: OID.stateOrProvinceName)
?.parent
?.sub
?.last
.value;
} catch (e) {
log(e.toString());
}
return null;
}
String? _findCommonName(
{required X509Certificate x509certificate, required bool isSubject}) {
try {
return (isSubject
? x509certificate.subject(dn: ASN1DistinguishedNames.COMMON_NAME)
: x509certificate.issuer(
dn: ASN1DistinguishedNames.COMMON_NAME)) ??
x509certificate.block1
?.findOid(oid: OID.commonName)
?.parent
?.sub
?.last
.value;
} catch (e) {
log(e.toString());
}
return null;
}
String? _findOrganizationName(
{required X509Certificate x509certificate, required bool isSubject}) {
try {
return (isSubject
? x509certificate.subject(
dn: ASN1DistinguishedNames.ORGANIZATION_NAME)
: x509certificate.issuer(
dn: ASN1DistinguishedNames.ORGANIZATION_NAME)) ??
x509certificate.block1
?.findOid(oid: OID.organizationName)
?.parent
?.sub
?.last
.value;
} catch (e) {
log(e.toString());
}
return null;
}
String? _findOrganizationUnitName(
{required X509Certificate x509certificate, required bool isSubject}) {
try {
return (isSubject
? x509certificate.subject(
dn: ASN1DistinguishedNames.ORGANIZATIONAL_UNIT_NAME)
: x509certificate.issuer(
dn: ASN1DistinguishedNames.ORGANIZATIONAL_UNIT_NAME)) ??
x509certificate.block1
?.findOid(oid: OID.organizationalUnitName)
?.parent
?.sub
?.last
.value;
} catch (e) {
log(e.toString());
}
return null;
}
List<Widget> _buildIssuedToSection(X509Certificate x509certificate) {
var subjectCountryName =
_findCountryName(x509certificate: x509certificate, isSubject: true) ??
"<Not Part Of Certificate>";
var subjectStateOrProvinceName = _findStateOrProvinceName(
x509certificate: x509certificate, isSubject: true) ??
"<Not Part Of Certificate>";
var subjectCN =
_findCommonName(x509certificate: x509certificate, isSubject: true) ??
"<Not Part Of Certificate>";
var subjectO = _findOrganizationName(
x509certificate: x509certificate, isSubject: true) ??
"<Not Part Of Certificate>";
var subjectU = _findOrganizationUnitName(
x509certificate: x509certificate, isSubject: true) ??
"<Not Part Of Certificate>";
return <Widget>[
const Text(
"ISSUED TO",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
const Text(
"Common Name (CN)",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
subjectCN,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Organization (O)",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
subjectO,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Organizational Unit (U)",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
subjectU,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Country",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
subjectCountryName,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"State/Province",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
subjectStateOrProvinceName,
style: const TextStyle(fontSize: 14.0),
),
];
}
List<Widget> _buildIssuedBySection(X509Certificate x509certificate) {
var issuerCountryName =
_findCountryName(x509certificate: x509certificate, isSubject: false) ??
"<Not Part Of Certificate>";
var issuerStateOrProvinceName = _findStateOrProvinceName(
x509certificate: x509certificate, isSubject: false) ??
"<Not Part Of Certificate>";
var issuerCN =
_findCommonName(x509certificate: x509certificate, isSubject: false) ??
"<Not Part Of Certificate>";
var issuerO = _findOrganizationName(
x509certificate: x509certificate, isSubject: false) ??
"<Not Part Of Certificate>";
var issuerU = _findOrganizationUnitName(
x509certificate: x509certificate, isSubject: false) ??
"<Not Part Of Certificate>";
var serialNumber = x509certificate.serialNumber
?.map((byte) {
var hexValue = byte.toRadixString(16);
if (byte == 0 || hexValue.length == 1) {
hexValue = "0$hexValue";
}
return hexValue.toUpperCase();
})
.toList()
.join(":") ??
"<Not Part Of Certificate>";
var version =
x509certificate.version?.toString() ?? "<Not Part Of Certificate>";
var sigAlgName = x509certificate.sigAlgName ?? "<Not Part Of Certificate>";
return <Widget>[
const SizedBox(
height: 15.0,
),
const Text(
"ISSUED BY",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
const Text(
"Common Name (CN)",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
issuerCN,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Organization (O)",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
issuerO,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Organizational Unit (U)",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
issuerU,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Country",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
issuerCountryName,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"State/Province",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
issuerStateOrProvinceName,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Serial Number",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
serialNumber,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Version",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
version,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Signature Algorithm",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
sigAlgName,
style: const TextStyle(fontSize: 14.0),
),
];
}
List<Widget> _buildValidityPeriodSection(X509Certificate x509certificate) {
var issuedOnDate = x509certificate.notBefore != null
? DateFormat("dd MMM yyyy HH:mm:ss").format(x509certificate.notBefore!)
: "<Not Part Of Certificate>";
var expiresOnDate = x509certificate.notAfter != null
? DateFormat("dd MMM yyyy HH:mm:ss").format(x509certificate.notAfter!)
: "<Not Part Of Certificate>";
return <Widget>[
const SizedBox(
height: 15.0,
),
const Text(
"VALIDITY PERIOD",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
const Text(
"Issued on date",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
issuedOnDate,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Expires on date",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
expiresOnDate,
style: const TextStyle(fontSize: 14.0),
),
];
}
List<Widget> _buildPublicKeySection(X509Certificate x509certificate) {
var publicKey = x509certificate.publicKey;
var publicKeyAlg = "<Not Part Of Certificate>";
var publicKeyAlgParams = "<Not Part Of Certificate>";
if (publicKey != null) {
if (publicKey.algOid != null) {
publicKeyAlg =
"${OID.fromValue(publicKey.algOid)!.name()} ( ${publicKey.algOid} )";
}
if (publicKey.algParams != null) {
publicKeyAlgParams =
"${OID.fromValue(publicKey.algParams)!.name()} ( ${publicKey.algParams} )";
}
}
return <Widget>[
const SizedBox(
height: 15.0,
),
const Text(
"PUBLIC KEY",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
const Text(
"Algorithm",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
publicKeyAlg,
style: const TextStyle(fontSize: 14.0),
),
const SizedBox(
height: 5.0,
),
const Text(
"Parameters",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
Text(
publicKeyAlgParams,
style: const TextStyle(fontSize: 14.0),
),
];
}
List<Widget> _buildFingerprintSection(X509Certificate x509certificate) {
return <Widget>[
const SizedBox(
height: 15.0,
),
const Text(
"FINGERPRINT",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
const Text(
"Fingerprint SHA-256",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
FutureBuilder(
future: x509certificate.encoded != null
? sha256.bind(Stream.value(x509certificate.encoded!)).first
: null,
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.connectionState != ConnectionState.done) {
return const Text("");
}
Digest digest = snapshot.data as Digest;
return Text(
digest.bytes
.map((byte) {
var hexValue = byte.toRadixString(16);
if (byte == 0 || hexValue.length == 1) {
hexValue = "0$hexValue";
}
return hexValue.toUpperCase();
})
.toList()
.join(" "),
style: const TextStyle(fontSize: 14.0),
);
},
),
const SizedBox(
height: 5.0,
),
const Text(
"Fingerprint SHA-1",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
FutureBuilder(
future: sha1.bind(Stream.value(x509certificate.encoded!)).first,
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.connectionState != ConnectionState.done) {
return const Text("");
}
Digest digest = snapshot.data as Digest;
return Text(
digest.bytes
.map((byte) {
var hexValue = byte.toRadixString(16);
if (byte == 0 || hexValue.length == 1) {
hexValue = "0$hexValue";
}
return hexValue.toUpperCase();
})
.toList()
.join(" "),
style: const TextStyle(fontSize: 14.0),
);
},
),
];
}
List<Widget> _buildExtensionSection(X509Certificate x509certificate) {
var extensionSection = <Widget>[
const SizedBox(
height: 15.0,
),
const Text(
"EXTENSIONS",
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
];
extensionSection.addAll(_buildKeyUsageSection(x509certificate));
extensionSection.addAll(_buildBasicConstraints(x509certificate));
extensionSection.addAll(_buildExtendedKeyUsage(x509certificate));
extensionSection.addAll(_buildSubjectKeyIdentifier(x509certificate));
extensionSection.addAll(_buildAuthorityKeyIdentifier(x509certificate));
extensionSection.addAll(_buildCertificatePolicies(x509certificate));
extensionSection.addAll(_buildCRLDistributionPoints(x509certificate));
extensionSection.addAll(_buildAuthorityInfoAccess(x509certificate));
extensionSection.addAll(_buildSubjectAlternativeNames(x509certificate));
return extensionSection;
}
List<Widget> _buildKeyUsageSection(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var keyUsage = x509certificate.keyUsage;
var keyUsageSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Key Usage ( ${OID.keyUsage.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
];
var keyUsageIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull((oid) => oid == OID.keyUsage.toValue()) !=
null
? "YES"
: "NO";
if (keyUsage.isNotEmpty) {
for (var i = 0; i < keyUsage.length; i++) {
if (keyUsage[i]) {
keyUsageSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: keyUsageIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Usage ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: KeyUsage.fromIndex(i)!.name(),
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
]);
}
}
} else {
keyUsageSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return keyUsageSection;
}
List<Widget> _buildBasicConstraints(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var basicConstraints = x509certificate.basicConstraints;
var basicConstraintsSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Basic Constraints ( ${OID.basicConstraints.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
];
var basicConstraintsIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull(
(oid) => oid == OID.basicConstraints.toValue()) !=
null
? "YES"
: "NO";
if (basicConstraints != null && basicConstraints.pathLenConstraint == -1) {
basicConstraintsSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: basicConstraintsIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
RichText(
text: const TextSpan(children: [
TextSpan(
text: "Certificate Authority ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: "NO",
style: TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
]);
} else {
basicConstraintsSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: basicConstraintsIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
RichText(
text: const TextSpan(children: [
TextSpan(
text: "Certificate Authority ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: "YES",
style: TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Path Length Constraints ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: basicConstraints.toString(),
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
]);
}
return basicConstraintsSection;
}
List<Widget> _buildExtendedKeyUsage(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var extendedKeyUsage = x509certificate.extendedKeyUsage;
var extendedKeyUsageSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Extended Key Usage ( ${OID.extKeyUsage.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
];
var extendedKeyUsageIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull((oid) => oid == OID.extKeyUsage.toValue()) !=
null
? "YES"
: "NO";
if (extendedKeyUsage.isNotEmpty) {
for (var i = 0; i < extendedKeyUsage.length; i++) {
OID oid = OID.fromValue(extendedKeyUsage[i])!;
extendedKeyUsageSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: extendedKeyUsageIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
RichText(
text: TextSpan(children: [
TextSpan(
text: "Purpose #${i + 1} ",
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: "${oid.name()} ( ${oid.toValue()} )",
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
]);
}
} else {
extendedKeyUsageSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return extendedKeyUsageSection;
}
List<Widget> _buildSubjectKeyIdentifier(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var subjectKeyIdentifier = x509certificate.subjectKeyIdentifier;
var subjectKeyIdentifierSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Subject Key Identifier ( ${OID.subjectKeyIdentifier.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
];
var subjectKeyIdentifierIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull(
(oid) => oid == OID.subjectKeyIdentifier.toValue()) !=
null
? "YES"
: "NO";
if (subjectKeyIdentifier?.value != null &&
subjectKeyIdentifier!.value!.isNotEmpty) {
var subjectKeyIdentifierToHexValue = subjectKeyIdentifier.value!
.map((byte) {
var hexValue = byte.toRadixString(16);
if (byte == 0 || hexValue.length == 1) {
hexValue = "0$hexValue";
}
return hexValue.toUpperCase();
})
.toList()
.join(" ");
subjectKeyIdentifierSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: subjectKeyIdentifierIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Key ID ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: subjectKeyIdentifierToHexValue,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
)
]);
} else {
subjectKeyIdentifierSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return subjectKeyIdentifierSection;
}
List<Widget> _buildAuthorityKeyIdentifier(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var authorityKeyIdentifier = x509certificate.authorityKeyIdentifier;
var authorityKeyIdentifierSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Authority Key Identifier ( ${OID.authorityKeyIdentifier.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
];
var authorityKeyIdentifierIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull(
(oid) => oid == OID.authorityKeyIdentifier.toValue()) !=
null
? "YES"
: "NO";
if (authorityKeyIdentifier?.keyIdentifier != null &&
authorityKeyIdentifier!.keyIdentifier!.isNotEmpty) {
var authorityKeyIdentifierToHexValue =
authorityKeyIdentifier.keyIdentifier!
.map((byte) {
var hexValue = byte.toRadixString(16);
if (byte == 0 || hexValue.length == 1) {
hexValue = "0$hexValue";
}
return hexValue.toUpperCase();
})
.toList()
.join(" ");
authorityKeyIdentifierSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: authorityKeyIdentifierIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Key ID ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: authorityKeyIdentifierToHexValue,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
)
]);
} else {
authorityKeyIdentifierSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return authorityKeyIdentifierSection;
}
List<Widget> _buildCertificatePolicies(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var certificatePolicies = x509certificate.certificatePolicies;
var certificatePoliciesIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull((oid) => oid == OID.extKeyUsage.toValue()) !=
null
? "YES"
: "NO";
var certificatePoliciesSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Certificate Policies ( ${OID.certificatePolicies.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: certificatePoliciesIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
];
if (certificatePolicies?.policies != null &&
certificatePolicies!.policies!.isNotEmpty) {
for (var i = 0; i < certificatePolicies.policies!.length; i++) {
OID? oid = OID.fromValue(certificatePolicies.policies![i].oid);
certificatePoliciesSection.addAll(<Widget>[
RichText(
text: TextSpan(children: [
TextSpan(
text: "ID policy num. ${i + 1} ",
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: (oid != null)
? "${oid.name()} ( ${oid.toValue()} )"
: "( ${certificatePolicies.policies![i].oid} )",
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
]);
}
} else {
certificatePoliciesSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return certificatePoliciesSection;
}
List<Widget> _buildCRLDistributionPoints(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var cRLDistributionPoints = x509certificate.cRLDistributionPoints;
var cRLDistributionPointsIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull(
(oid) => oid == OID.cRLDistributionPoints.toValue()) !=
null
? "YES"
: "NO";
var cRLDistributionPointsSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"CRL Distribution Points ( ${OID.cRLDistributionPoints.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: cRLDistributionPointsIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
];
if (cRLDistributionPoints?.crls != null &&
cRLDistributionPoints!.crls!.isNotEmpty) {
for (var i = 0; i < cRLDistributionPoints.crls!.length; i++) {
cRLDistributionPointsSection.addAll(<Widget>[
RichText(
text: TextSpan(children: [
const TextSpan(
text: "URI ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: cRLDistributionPoints.crls![i],
style: const TextStyle(fontSize: 12.0, color: Colors.blue),
recognizer: TapGestureRecognizer()
..onTap = () async {
final crlUrl = cRLDistributionPoints.crls![i];
try {
Directory? directory =
await getExternalStorageDirectory();
await FlutterDownloader.enqueue(
url: crlUrl,
savedDir: directory!.path,
showNotification: true,
// show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
);
} catch (e) {
final crlUri = Uri.tryParse(crlUrl);
if (crlUri != null && await canLaunchUrl(crlUri)) {
launchUrl(crlUri);
}
}
})
]),
),
]);
}
} else {
cRLDistributionPointsSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return cRLDistributionPointsSection;
}
List<Widget> _buildAuthorityInfoAccess(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var authorityInfoAccess = x509certificate.authorityInfoAccess;
var authorityInfoAccessIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull(
(oid) => oid == OID.authorityInfoAccess.toValue()) !=
null
? "YES"
: "NO";
var authorityInfoAccessSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Authority Info Access ( ${OID.authorityInfoAccess.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: authorityInfoAccessIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
];
if (authorityInfoAccess?.infoAccess != null &&
authorityInfoAccess!.infoAccess!.isNotEmpty) {
for (var i = 0; i < authorityInfoAccess.infoAccess!.length; i++) {
var infoAccess = authorityInfoAccess.infoAccess![i];
var value = infoAccess.location;
var oid = OID.fromValue(infoAccess.method);
authorityInfoAccessSection.addAll(<Widget>[
RichText(
text: TextSpan(children: [
TextSpan(
text: "Method #${i + 1} ",
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: oid != null
? "${oid.name()} ( ${oid.toValue()} )"
: infoAccess.method,
style: const TextStyle(fontSize: 12.0, color: Colors.black),
)
]),
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "URI ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: value,
style: const TextStyle(fontSize: 12.0, color: Colors.blue),
recognizer: TapGestureRecognizer()
..onTap = () async {
Directory? directory =
await getExternalStorageDirectory();
await FlutterDownloader.enqueue(
url: value,
savedDir: directory!.path,
showNotification: true,
// show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
);
})
]),
),
]);
}
} else {
authorityInfoAccessSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return authorityInfoAccessSection;
}
List<Widget> _buildSubjectAlternativeNames(X509Certificate x509certificate) {
var criticalExtensionOIDs = x509certificate.criticalExtensionOIDs;
var subjectAlternativeNames = x509certificate.subjectAlternativeNames;
var subjectAlternativeNamesSection = <Widget>[
const SizedBox(
height: 15.0,
),
Text(
"Subject Alternative Names ( ${OID.subjectAltName.toValue()} )",
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
];
var subjectAlternativeNamesIsCritical = criticalExtensionOIDs
.map((e) => e)
.firstWhereOrNull(
(oid) => oid == OID.subjectAltName.toValue()) !=
null
? "YES"
: "NO";
if (subjectAlternativeNames.isNotEmpty) {
subjectAlternativeNamesSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "Critical ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: subjectAlternativeNamesIsCritical,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
]);
for (var subjectAlternativeName in subjectAlternativeNames) {
subjectAlternativeNamesSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(children: [
const TextSpan(
text: "DNS Name ",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Colors.black)),
TextSpan(
text: subjectAlternativeName,
style: const TextStyle(fontSize: 12.0, color: Colors.black))
]),
),
]);
}
} else {
subjectAlternativeNamesSection.addAll(<Widget>[
const SizedBox(
height: 5.0,
),
const Text(
"<Not Part Of Certificate>",
style: TextStyle(fontSize: 14.0),
),
]);
}
return subjectAlternativeNamesSection;
}
}
================================================
FILE: lib/app_bar/custom_app_bar_wrapper.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_browser/app_bar/desktop_app_bar.dart';
import 'package:flutter_browser/util.dart';
class CustomAppBarWrapper extends StatefulWidget implements PreferredSizeWidget {
final AppBar appBar;
CustomAppBarWrapper({super.key, required this.appBar})
: preferredSize = Util.isMobile()
? _PreferredAppBarSize(
kToolbarHeight, appBar.bottom?.preferredSize.height)
: Size.fromHeight(_PreferredAppBarSize(
kToolbarHeight, appBar.bottom?.preferredSize.height)
.height +
40);
@override
State<CustomAppBarWrapper> createState() => _CustomAppBarWrapperState();
@override
final Size preferredSize;
}
class _CustomAppBarWrapperState extends State<CustomAppBarWrapper> {
@override
Widget build(BuildContext context) {
final List<Widget> children = [];
if (Util.isDesktop()) {
children.add(const DesktopAppBar(showTabs: false,));
} else {
return widget.appBar;
}
children.add(Flexible(child: widget.appBar));
return Column(
children: children,
);
}
}
class _PreferredAppBarSize extends Size {
_PreferredAppBarSize(this.toolbarHeight, this.bottomHeight)
: super.fromHeight(
(toolbarHeight ?? kToolbarHeight) + (bottomHeight ?? 0));
final double? toolbarHeight;
final double? bottomHeight;
}
================================================
FILE: lib/app_bar/desktop_app_bar.dart
================================================
import 'package:collection/collection.dart';
import 'package:context_menus/context_menus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:provider/provider.dart';
import 'package:window_manager_plus/window_manager_plus.dart';
import '../custom_image.dart';
import '../models/browser_model.dart';
import '../models/webview_model.dart';
import '../models/window_model.dart';
import '../util.dart';
import '../webview_tab.dart';
class DesktopAppBar extends StatefulWidget {
final bool showTabs;
const DesktopAppBar({super.key, this.showTabs = true});
@override
State<DesktopAppBar> createState() => _DesktopAppBarState();
}
class _DesktopAppBarState extends State<DesktopAppBar> {
@override
Widget build(BuildContext context) {
final windowModel = Provider.of<WindowModel>(context, listen: true);
final tabSelectors = !widget.showTabs
? <Widget>[]
: windowModel.webViewTabs.map((webViewTab) {
final index = windowModel.webViewTabs.indexOf(webViewTab);
final currentIndex = windowModel.getCurrentTabIndex();
return Flexible(
flex: 1,
fit: FlexFit.loose,
child: IntrinsicHeight(
child: Row(
children: [
Expanded(
child: WebViewTabSelector(
tab: webViewTab, index: index)),
SizedBox(
height: 15,
child: VerticalDivider(
thickness: 1,
width: 1,
color: index == currentIndex - 1 ||
index == currentIndex
? Colors.transparent
: Colors.black45),
)
],
),
));
}).toList();
final windowActions = [];
if (!Util.isWindows()) {
windowActions.addAll([
const SizedBox(
width: 8,
),
IconButton(
onPressed: () {
WindowManagerPlus.current.close();
},
constraints: const BoxConstraints(
maxWidth: 13,
minWidth: 13,
maxHeight: 13,
minHeight: 13,
),
padding: EdgeInsets.zero,
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: const WidgetStatePropertyAll(Colors.red),
iconColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.hovered)
? Colors.black45
: Colors.red,
)),
color: Colors.red,
icon: const Icon(
Icons.close,
size: 10,
)),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () async {
if (!(await WindowManagerPlus.current.isFullScreen())) {
WindowManagerPlus.current.minimize();
}
},
constraints: const BoxConstraints(
maxWidth: 13,
minWidth: 13,
maxHeight: 13,
minHeight: 13,
),
padding: EdgeInsets.zero,
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: const WidgetStatePropertyAll(Colors.amber),
iconColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.hovered)
? Colors.black45
: Colors.amber,
)),
color: Colors.amber,
icon: const Icon(
Icons.remove,
size: 10,
)),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () async {
WindowManagerPlus.current
.setFullScreen(!(await WindowManagerPlus.current.isFullScreen()));
},
constraints: const BoxConstraints(
maxWidth: 13,
minWidth: 13,
maxHeight: 13,
minHeight: 13,
),
padding: EdgeInsets.zero,
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: const WidgetStatePropertyAll(Colors.green),
iconColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.hovered)
? Colors.black45
: Colors.green,
)),
color: Colors.green,
icon: const Icon(
Icons.open_in_full,
size: 10,
)),
const SizedBox(
width: 8,
),
]);
}
final children = [
Container(
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width - 100),
child: IntrinsicWidth(
child: Row(
children: [
...windowActions,
Flexible(
child: Container(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: tabSelectors.isNotEmpty
? tabSelectors
: [
const SizedBox(
height: 30,
)
],
),
)),
const SizedBox(
width: 5,
),
!widget.showTabs
? null
: IconButton(
onPressed: () {
_addNewTab();
},
constraints: const BoxConstraints(
maxWidth: 25,
minWidth: 25,
maxHeight: 25,
minHeight: 25,
),
padding: EdgeInsets.zero,
icon: const Icon(
Icons.add,
size: 15,
color: Colors.white,
)),
].whereNotNull().toList().cast<Widget>(),
),
),
),
Flexible(
child: MouseRegion(
hitTestBehavior: HitTestBehavior.opaque,
onEnter: (details) {
if (!Util.isWindows()) {
WindowManagerPlus.current.setMovable(true);
}
setState(() {});
},
onExit: (details) {
if (!Util.isWindows()) {
WindowManagerPlus.current.setMovable(false);
}
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onDoubleTap: () async {
await WindowManagerPlus.current.maximize();
},
child: !widget.showTabs
? const SizedBox(
height: 30,
width: double.infinity,
)
: ContextMenuRegion(
behavior: const [ContextMenuShowBehavior.secondaryTap],
contextMenu: GenericContextMenu(
buttonConfigs: [
ContextMenuButtonConfig(
"New Tab",
onPressed: () {
_addNewTab();
},
),
ContextMenuButtonConfig(
"Close All",
onPressed: () {
windowModel.closeAllTabs();
},
),
],
),
child: const SizedBox(
height: 30,
width: double.infinity,
)),
))),
!widget.showTabs
? null
: OpenTabsViewer(
webViewTabs: windowModel.webViewTabs,
),
].whereNotNull().toList();
return Container(
color: Theme.of(context).colorScheme.primary,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: children,
),
);
}
void _addNewTab() {
final browserModel = Provider.of<BrowserModel>(context, listen: false);
final windowModel = Provider.of<WindowModel>(context, listen: false);
final settings = browserModel.getSettings();
windowModel.addTab(WebViewTab(
key: GlobalKey(),
webViewModel: WebViewModel(url: WebUri(settings.searchEngine.url)),
));
}
}
class WebViewTabSelector extends StatefulWidget {
final WebViewTab tab;
final int index;
const WebViewTabSelector({super.key, required this.tab, required this.index});
@override
State<WebViewTabSelector> createState() => _WebViewTabSelectorState();
}
class _WebViewTabSelectorState extends State<WebViewTabSelector> {
bool isHover = false;
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final windowModel = Provider.of<WindowModel>(context, listen: true);
final isCurrentTab = windowModel.getCurrentTabIndex() == widget.index;
final tab = widget.tab;
final url = tab.webViewModel.url;
var tabName = tab.webViewModel.title ?? url?.toString() ?? '';
if (tabName.isEmpty) {
tabName = 'New Tab';
}
final tooltipText =
'$tabName\n${(url?.host ?? '').isEmpty ? url?.toString() : url?.host}'
.trim();
final faviconUrl = tab.webViewModel.favicon != null
? tab.webViewModel.favicon!.url
: (url != null && ["http", "https"].contains(url.scheme)
? Uri.parse("${url.origin}/favicon.ico")
: null);
return MouseRegion(
onEnter: (event) {
setState(() {
isHover = true;
});
},
onExit: (event) {
setState(() {
isHover = false;
});
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
windowModel.showTab(widget.index);
},
child: ContextMenuRegion(
contextMenu: GenericContextMenu(
buttonConfigs: [
ContextMenuButtonConfig(
"Reload",
onPressed: () {
tab.webViewModel.webViewController?.reload();
},
),
ContextMenuButtonConfig(
"Duplicate",
onPressed: () {
if (tab.webViewModel.url != null) {
windowModel.addTab(WebViewTab(
key: GlobalKey(),
webViewModel: WebViewModel(url: tab.webViewModel.url),
));
}
},
),
ContextMenuButtonConfig(
"Close",
onPressed: () {
windowModel.closeTab(widget.index);
},
),
],
),
child: Container(
height: 30,
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 250),
padding: const EdgeInsets.only(right: 5.0),
decoration: !isCurrentTab
? null
: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(5))),
child: Tooltip(
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.all(Radius.circular(5))),
richMessage: WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
child: Container(
constraints: const BoxConstraints(maxWidth: 400),
child: Text(
tooltipText,
overflow: TextOverflow.ellipsis,
maxLines: 3,
style: const TextStyle(color: Colors.white),
),
)),
waitDuration: const Duration(milliseconds: 500),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
child: CustomImage(
url: faviconUrl, maxWidth: 20.0, height: 20.0),
),
Flexible(
child: Text(tabName,
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
style: TextStyle(
fontSize: 12,
color:
!isCurrentTab ? Colors.white : null))),
],
)),
IconButton(
onPressed: () {
windowModel.closeTab(widget.index);
},
constraints: const BoxConstraints(
maxWidth: 20,
minWidth: 20,
maxHeight: 20,
minHeight: 20,
),
padding: EdgeInsets.zero,
icon: Icon(
Icons.cancel,
color: !isCurrentTab ? Colors.white : null,
size: 15,
)),
],
),
),
)),
),
);
}
}
class OpenTabsViewer extends StatefulWidget {
final List<WebViewTab> webViewTabs;
const OpenTabsViewer({super.key, required this.webViewTabs});
@override
State<OpenTabsViewer> createState() => _OpenTabsViewerState();
}
class _OpenTabsViewerState extends State<OpenTabsViewer> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(4.0),
child: MenuAnchor(
builder: (context, controller, child) {
return IconButton(
onPressed: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
constraints: const BoxConstraints(
maxWidth: 25,
minWidth: 25,
maxHeight: 25,
minHeight: 25,
),
padding: EdgeInsets.zero,
icon: const Icon(
Icons.keyboard_arrow_down,
size: 15,
color: Colors.white,
));
},
menuChildren: [
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 200,
),
child: TextFormField(
controller: _controller,
maxLines: 1,
style: Theme.of(context).textTheme.labelLarge,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.search),
hintText: 'Search open tabs',
contentPadding: EdgeInsets.only(top: 15),
isDense: true,
),
onChanged: (value) {
setState(() {});
},
),
),
MenuItemButton(
onPressed: null,
child: Text(
widget.webViewTabs.isEmpty ? 'No tabs open' : 'Tabs open',
style: Theme.of(context).textTheme.labelLarge,
),
),
...(widget.webViewTabs.where(
(element) {
final search = _controller.text.toLowerCase().trim();
final containsInTitle = element.webViewModel.title
?.toLowerCase()
.contains(search) ??
false;
final containsInUrl = element.webViewModel.url
?.toString()
.toLowerCase()
.contains(search) ??
false;
return search.isEmpty || containsInTitle || containsInUrl;
},
).map((w) {
final url = w.webViewModel.url;
final title = (w.webViewModel.title ?? '').isNotEmpty
? w.webViewModel.title!
: 'New Tab';
var subtitle =
(url?.host ?? '').isEmpty ? url?.toString() : url?.host;
final diffTime =
DateTime.now().difference(w.webViewModel.lastOpenedTime);
var diffTimeSubtitle = 'now';
if (diffTime.inDays > 0) {
diffTimeSubtitle =
'${diffTime.inDays} ${diffTime.inDays == 1 ? 'day' : 'days'} ago';
} else if (diffTime.inMinutes > 0) {
diffTimeSubtitle = '${diffTime.inMinutes} min ago';
} else if (diffTime.inSeconds > 0) {
diffTimeSubtitle = '${diffTime.inSeconds} sec ago';
}
final faviconUrl = w.webViewModel.favicon != null
? w.webViewModel.favicon!.url
: (url != null && ["http", "https"].contains(url.scheme)
? Uri.parse("${url.origin}/favicon.ico")
: null);
return MenuItemButton(
onPressed: () {
final windowModel =
Provider.of<WindowModel>(context, listen: false);
windowModel.showTab(windowModel.webViewTabs.indexOf(w));
},
leadingIcon: Container(
padding: const EdgeInsets.all(8),
child: CustomImage(url: faviconUrl, maxWidth: 15, height: 15),
),
trailingIcon: IconButton(
onPressed: () {
final windowModel =
Provider.of<WindowModel>(context, listen: false);
windowModel.closeTab(widget.webViewTabs.indexOf(w));
},
constraints: const BoxConstraints(
maxWidth: 25,
minWidth: 25,
maxHeight: 25,
minHeight: 25,
),
padding: EdgeInsets.zero,
icon: const Icon(
Icons.cancel,
size: 15,
)),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 250,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.labelMedium,
),
Row(
children: [
Flexible(
child: Text(
subtitle ?? '',
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.labelSmall,
)),
Text(
" - $diffTimeSubtitle",
style: Theme.of(context).textTheme.labelSmall,
)
],
)
].whereNotNull().toList(),
)),
);
}).toList())
].whereNotNull().toList(),
));
}
}
================================================
FILE: lib/app_bar/find_on_page_app_bar.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_browser/models/browser_model.dart';
import 'package:provider/provider.dart';
import '../models/window_model.dart';
class FindOnPageAppBar extends StatefulWidget {
final void Function()? hideFindOnPage;
const FindOnPageAppBar({super.key, this.hideFindOnPage});
@override
State<FindOnPageAppBar> createState() => _FindOnPageAppBarState();
}
class _FindOnPageAppBarState extends State<FindOnPageAppBar> {
final TextEditingController _finOnPageController = TextEditingController();
OutlineInputBorder outlineBorder = const OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent, width: 0.0),
borderRadius: BorderRadius.all(
Radius.circular(50.0),
),
);
@override
void dispose() {
_finOnPageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final windowModel = Provider.of<WindowModel>(context, listen: false);
final webViewModel = windowModel.getCurrentTab()?.webViewModel;
final findInteractionController = webViewModel?.findInteractionController;
return AppBar(
titleSpacing: 10.0,
title: SizedBox(
height: 40.0,
child: TextField(
onSubmitted: (value) {
findInteractionController?.findAll(find: value);
},
controller: _finOnPageController,
textInputAction: TextInputAction.go,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(10.0),
filled: true,
fillColor: Colors.white,
border: outlineBorder,
focusedBorder: outlineBorder,
enabledBorder: outlineBorder,
hintText: "Find on page ...",
hintStyle: const TextStyle(color: Colors.black54, fontSize: 16.0),
),
style: const TextStyle(color: Colors.black, fontSize: 16.0),
)),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.keyboard_arrow_up),
onPressed: () {
findInteractionController?.findNext(forward: false);
},
),
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () {
findInteractionController?.findNext(forward: true);
},
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
findInteractionController?.clearMatches();
_finOnPageController.text = "";
if (widget.hideFindOnPage != null) {
widget.hideFindOnPage!();
}
},
),
],
);
}
}
================================================
FILE: lib/app_bar/tab_viewer_app_bar.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_browser/models/browser_model.dart';
import 'package:flutter_browser/models/webview_model.dart';
import 'package:flutter_browser/pages/settings/main.dart';
import 'package:flutter_browser/webview_tab.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:provider/provider.dart';
import '../custom_popup_menu_item.dart';
import '../models/window_model.dart';
import '../tab_viewer_popup_menu_actions.dart';
class TabViewerAppBar extends StatefulWidget implements PreferredSizeWidget {
const TabViewerAppBar({super.key})
: preferredSize = const Size.fromHeight(kToolbarHeight);
@override
State<TabViewerAppBar> createState() => _TabViewerAppBarState();
@override
final Size preferredSize;
}
class _TabViewerAppBarState extends State<TabViewerAppBar> {
GlobalKey tabInkWellKey = GlobalKey();
@override
Widget build(BuildContext context) {
return AppBar(
titleSpacing: 10.0,
leading: _buildAddTabButton(),
actions: _buildActionsMenu(),
);
}
Widget _buildAddTabButton() {
return IconButton(
icon: const Icon(Icons.add),
onPressed: () {
addNewTab();
},
);
}
List<Widget> _buildActionsMenu() {
final browserModel = Provider.of<BrowserModel>(context, listen: true);
final windowModel = Provider.of<WindowModel>(context, listen: true);
final settings = browserModel.getSettings();
return <Widget>[
InkWell(
key: tabInkWellKey,
onTap: () {
if (windowModel.webViewTabs.isNotEmpty) {
browserModel.showTabScroller = !browserModel.showTabScroller;
} else {
browserModel.showTabScroller = false;
}
},
child: Padding(
padding: settings.homePageEnabled
? const EdgeInsets.only(
left: 20.0, top: 15.0, right: 10.0, bottom: 15.0)
: const EdgeInsets.only(
left: 10.0, top: 15.0, right: 10.0, bottom: 15.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 2.0),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0)),
constraints: const BoxConstraints(minWidth: 25.0),
child: Center(
child: Text(
windowModel.webViewTabs.length.toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.0),
)),
),
),
),
PopupMenuButton<String>(
onSelected: _popupMenuChoiceAction,
itemBuilder: (popupMenuContext) {
var items = <PopupMenuEntry<String>>[];
items.addAll(TabViewerPopupMenuActions.choices.map((choice) {
switch (choice) {
case TabViewerPopupMenuActions.NEW_TAB:
return CustomPopupMenuItem<String>(
enabled: true,
value: choice,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(choice),
const Icon(
Icons.add,
color: Colors.black,
)
]),
);
case TabViewerPopupMenuActions.NEW_INCOGNITO_TAB:
return CustomPopupMenuItem<String>(
enabled: true,
value: choice,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(choice),
const Icon(
MaterialCommunityIcons.incognito,
color: Colors.black,
)
]),
);
case TabViewerPopupMenuActions.CLOSE_ALL_TABS:
return CustomPopupMenuItem<String>(
enabled: windowModel.webViewTabs.isNotEmpty,
value: choice,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(choice),
const Icon(
Icons.close,
color: Colors.black,
)
]),
);
case TabViewerPopupMenuActions.SETTINGS:
return CustomPopupMenuItem<String>(
enabled: true,
value: choice,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(choice),
const Icon(
Icons.settings,
color: Colors.grey,
)
]),
);
default:
return CustomPopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}
}).toList());
return items;
},
)
];
}
void _popupMenuChoiceAction(String choice) async {
switch (choice) {
case TabViewerPopupMenuActions.NEW_TAB:
Future.delayed(const Duration(milliseconds: 300), () {
addNewTab();
});
break;
case TabViewerPopupMenuActions.NEW_INCOGNITO_TAB:
Future.delayed(const Duration(milliseconds: 300), () {
addNewIncognitoTab();
});
break;
case TabViewerPopupMenuActions.CLOSE_ALL_TABS:
Future.delayed(const Duration(milliseconds: 300), () {
closeAllTabs();
});
break;
case TabViewerPopupMenuActions.SETTINGS:
Future.delayed(const Duration(milliseconds: 300), () {
goToSettingsPage();
});
break;
}
}
void addNewTab({WebUri? url}) {
final browserModel = Provider.of<BrowserModel>(context, listen: false);
final windowModel = Provider.of<WindowModel>(context, listen: false);
final settings = browserModel.getSettings();
url ??= settings.homePageEnabled && settings.customUrlHomePage.isNotEmpty
? WebUri(settings.customUrlHomePage)
: WebUri(settings.searchEngine.url);
browserModel.showTabScroller = false;
windowModel.addTab(WebViewTab(
key: GlobalKey(),
webViewModel: WebViewModel(url: url),
));
}
void addNewIncognitoTab({WebUri? url}) {
final browserModel = Provider.of<BrowserModel>(context, listen: false);
final windowModel = Provider.of<WindowModel>(context, listen: false);
final settings = browserModel.getSettings();
url ??= settings.homePageEnabled && settings.customUrlHomePage.isNotEmpty
? WebUri(settings.customUrlHomePage)
: WebUri(settings.searchEngine.url);
browserModel.showTabScroller = false;
windowModel.addTab(WebViewTab(
key: GlobalKey(),
webViewModel: WebViewModel(url: url, isIncognitoMode: true),
));
}
void closeAllTabs() {
final browserModel = Provider.of<BrowserModel>(context, listen: false);
final windowModel = Provider.of<WindowModel>(context, listen: false);
browserModel.showTabScroller = false;
windowModel.closeAllTabs();
}
void goToSettingsPage() {
Navigator.push(
context, MaterialPageRoute(builder: (context) => const SettingsPage()));
}
}
================================================
FILE: lib/app_bar/url_info_popup.dart
================================================
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_browser/app_bar/certificates_info_popup.dart';
import 'package:flutter_browser/models/webview_model.dart';
import 'package:provider/provider.dart';
import '../custom_popup_dialog.dart';
class UrlInfoPopup extends StatefulWidget {
final CustomPopupDialogPageRoute route;
final Duration transitionDuration;
final Function()? onWebViewTabSettingsClicked;
const UrlInfoPopup(
{super.key,
required this.route,
required this.transitionDuration,
this.onWebViewTabSettingsClicked});
@override
State<UrlInfoPopup> createState() => _UrlInfoPopupState();
}
class _UrlInfoPopupState extends State<UrlInfoPopup> {
var text1 = "Your connection to this website is not protected";
var text2 =
"You should not enter sensitive data on this site (e.g. passwords or credit cards) because they could be intercepted by malicious users.";
var showFullInfoUrl = false;
var defaultTextSpanStyle = const TextStyle(
color: Colors.black54,
fontSize: 12.5,
);
@override
Widget build(BuildContext context) {
var webViewModel = Provider.of<WebViewModel>(context, listen: true);
if (webViewModel.isSecure) {
text1 = "Your connection is protected";
text2 =
"Your sensitive data (e.g. passwords or credit card numbers) remains private when it is sent to this site.";
}
var url = webViewModel.url;
return SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
StatefulBuilder(
builder: (context, setState) {
return GestureDetector(
onTap: () {
setState(() {
showFullInfoUrl = !showFullInfoUrl;
});
},
child: Container(
padding: const EdgeInsets.only(bottom: 15.0),
constraints: const BoxConstraints(maxHeight: 100.0),
child: RichText(
maxLines: showFullInfoUrl ? null : 2,
overflow: showFullInfoUrl
? TextOverflow.clip
: TextOverflow.ellipsis,
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: url?.scheme,
style: defaultTextSpanStyle.copyWith(
color: webViewModel.isSecure
? Colors.green
: Colors.black54,
fontWeight: FontWeight.bold)),
TextSpan(
text: webViewModel.url?.toString() == "about:blank"
? ':'
: '://',
style: defaultTextSpanStyle),
TextSpan(
text: url?.host,
style: defaultTextSpanStyle.copyWith(
color: Colors.black)),
TextSpan(text: url?.path, style: defaultTextSpanStyle),
TextSpan(text: url?.query, style: defaultTextSpanStyle),
],
),
)),
);
},
),
Container(
padding: const EdgeInsets.only(bottom: 10.0),
child: Text(text1,
style: const TextStyle(
fontSize: 16.0,
)),
),
RichText(
text: TextSpan(
style: const TextStyle(fontSize: 12.0, color: Colors.black87),
children: [
TextSpan(
text: "$text2 ",
),
TextSpan(
text: "Details",
style: const TextStyle(color: Colors.blue),
recognizer: TapGestureRecognizer()
..onTap = () async {
Navigator.maybePop(context);
await widget.route.popped;
await Future.delayed(Duration(
milliseconds:
widget.transitionDuration.inMilliseconds - 200));
showDialog(
context: context,
builder: (context) {
return const CertificateInfoPopup();
},
);
},
),
])),
const SizedBox(
height: 30.0,
),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
child: const Text(
"WebView Tab Settings",
),
onPressed: () async {
Navigator.maybePop(context);
await widget.route.popped;
Future.delayed(widget.transitionDuration, () {
if (widget.onWebViewTabSettingsClicked != null) {
widget.onWebViewTabSettingsClicked!();
}
});
},
),
),
],
));
}
}
================================================
FILE: lib/app_bar/webview_tab_app_bar.dart
================================================
// import 'package:cached_network_image/cached_network_image.dart';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_browser/app_bar/url_info_popup.dart';
import 'package:flutter_browser/custom_image.dart';
import 'package:flutter_browser/main.dart';
import 'package:flutter_browser/models/browser_model.dart';
import 'package:flutter_browser/models/favorite_model.dart';
import 'package:flutter_browser/models/web_archive_model.dart';
import 'package:flutter_browser/models/webview_model.dart';
import 'package:flutter_browser/pages/developers/main.dart';
import 'package:flutter_browser/pages/settings/main.dart';
import 'package:flutter_browser/tab_popup_menu_actions.dart';
import 'package:flutter_browser/util.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import '../animated_flutter_browser_logo.dart';
import '../custom_popup_dialog.dart';
import '../custom_popup_menu_item.dart';
import '../models/window_model.dart';
import '../popup_menu_actions.dart';
import '../project_info_popup.dart';
import '../webview_tab.dart';
class WebViewTabAppBar extends StatefulWidget {
final void Function()? showFindOnPage;
const WebViewTabAppBar({super.key, this.showFindOnPage});
@override
State<WebViewTabAppBar> createState() => _WebViewTabAppBarState();
}
class _WebViewTabAppBarState extends State<WebViewTabAppBar>
with SingleTickerProviderStateMixin {
TextEditingController? _searchController = TextEditingController();
FocusNode? _focusNode;
GlobalKey tabInkWellKey = GlobalKey();
Duration customPopupDialogTransitionDuration =
const Duration(milliseconds: 300);
CustomPopupDialogPageRoute? route;
OutlineInputBorder outlineBorder = const OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent, width: 0.0),
borderRadius: BorderRadius.all(
Radius.circular(50.0),
),
);
bool shouldSelectText = true;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode?.addListener(() async {
if (_focusNode != null &&
!_focusNode!.hasFocus &&
_searchController != null &&
_searchController!.text.isEmpty) {
final windowModel = Provider.of<WindowModel>(context, listen: false);
final webViewModel = windowModel.getCurrentTab()?.webViewModel;
var webViewController = webViewModel?.webViewController;
_searchController!.text =
(await webViewController?.getUrl())?.toString() ?? "";
}
});
}
@override
void dispose() {
_focusNode?.dispose();
_focusNode = null;
_searchController?.dispose();
_searchController = null;
super.dispose();
}
int _prevTabIndex = -1;
@override
Widget build(BuildContext context) {
return Selector<WebViewModel, ({WebUri? item1, int? item2})>(
selector: (context, webViewModel) => (item1: webViewModel.url, item2: webViewModel.tabIndex),
builder: (context, record, child) {
if (_prevTabIndex != record.item2) {
_searchController?.text = record.item1?.toString() ?? '';
_prevTabIndex = record.item2 ?? _prevTabIndex;
_focusNode?.unfocus();
} else {
if (record.item1 == null) {
_searchController?.text = "";
}
if (record.item1 != null && _focusNode != null &&
!_focusNode!.hasFocus) {
_searchController?.text = record.item1.toString();
}
}
Widget? leading = _buildAppBarHomePageWidget();
return Selector<WebViewModel, bool>(
selector: (context, webViewModel) => webViewModel.isIncognitoMode,
builder: (context, isIncognitoMode, child) {
return leading != null
? AppBar(
backgroundColor: isIncognitoMode
? Colors.black38
: Theme.of(context).colorScheme.primaryContainer,
leading: leading,
leadingWidth: 130,
titleSpacing: 0.0,
title: _buildSearchTextField(),
actions: _buildActionsMenu(),
)
: AppBar(
backgroundColor: isIncognitoMode
? Colors.black38
: Theme.of(context).colorScheme.primaryContainer,
titleSpacing: 10.0,
title: _buildSearchTextField(),
actions: _buildActionsMenu(),
);
});
});
}
Widget? _buildAppBarHomePageWidget() {
var browserModel = Provider.of<BrowserModel>(context, listen: true);
var settings = browserModel.getSettings();
var webViewModel = Provider.of<WebViewModel>(context, listen: true);
if (Util.isMobile() && !settings.homePageEnabled) {
return null;
}
final children = <Widget>[];
if (Util.isDesktop()) {
children.addAll([
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 20,
),
constraints: const BoxConstraints(
maxWidth: 30,
minWidth: 30,
maxHeight: 30,
minHeight: 30,
),
padding: EdgeInsets.zero,
onPressed: () async {
webViewModel.webViewController?.goBack();
},
),
IconButton(
icon: const Icon(
Icons.arrow_forward,
size: 20,
),
constraints: const BoxConstraints(
maxWidth: 30,
minWidth: 30,
maxHeight: 30,
minHeight: 30,
),
padding: EdgeInsets.zero,
onPressed: () async {
webViewModel.webViewController?.goForward();
},
),
IconButton(
icon: const Icon(
Icons.refresh,
size: 20,
),
constraints: const BoxConstraints(
maxWidth: 30,
minWidth: 30,
maxHeight: 30,
minHeight: 30,
),
padding: EdgeInsets.zero,
onPressed: () async {
webViewModel.webViewController?.reload();
},
)
]);
}
if (settings.homePageEnabled || Util.isDesktop()) {
children.add(IconButton(
icon: const Icon(
Icons.home,
size: 20,
),
constraints: const BoxConstraints(
maxWidth: 30,
minWidth: 30,
maxHeight: 30,
minHeight: 30,
),
padding: EdgeInsets.zero,
onPressed: () {
if (webViewModel.webViewController != null) {
var url = settings.homePageEnabled &&
settings.customUrlHomePage.isNotEmpty
? WebUri(settings.customUrlHomePage)
: WebUri(settings.searchEngine.url);
webViewModel.webViewController
?.loadUrl(urlRequest: URLRequest(url: url));
} else {
addNewTab();
}
},
));
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 5),
child: Row(
children: children,
),
);
}
Widget _buildSearchTextField() {
final browserModel = Provider.of<BrowserModel>(context, listen: true);
final settings = browserModel.getSettings();
final webViewModel = Provider.of<WebViewModel>(context, listen: true);
return SizedBox(
height: 40.0,
child: Stack(
children: <Widget>[
TextField(
onSubmitted: (value) {
var url = WebUri(value.trim());
if (Util.isLocalizedContent(url) ||
(url.isValidUri && url.toString().split(".").length > 1)) {
url = url.scheme.isEmpty ? WebUri("https://$url") : url;
} else {
url = WebUri(settings.searchEngine.searchUrl + value);
}
if (webViewModel.webViewController != null) {
webViewModel.webViewController
?.loadUrl(urlRequest: URLRequest(url: url));
} else {
addNewTab(url: url);
webViewModel.url = url;
}
},
onTap: () {
if (!shouldSelectText ||
_searchController == null ||
_searchController!.text.isEmpty) return;
shouldSelectText = false;
_searchController!.selection = TextSelection(
baseOffset: 0, extentOffset: _searchController!.text.length);
},
onTapOutside: (event) {
shouldSelectText = true;
},
keyboardType: TextInputType.url,
focusNode: _focusNode,
autofocus: false,
controller: _searchController,
textInputAction: TextInputAction.go,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
left: 45.0, top: 10.0, right: 10.0, bottom: 10.0),
filled: true,
fillColor: Colors.white,
border: outlineBorder,
focusedBorder: outlineBorder,
enabledBorder: outlineBorder,
hintText: "Search for or type a web address",
hintStyle: const TextStyle(color: Colors.black54, fontSize: 16.0),
),
style: const TextStyle(color: Colors.black, fontSize: 16.0),
),
IconButton(
icon: Selector<WebViewModel, bool>(
selector: (context, webViewModel) => webViewModel.isSecure,
builder: (context, isSecure, child) {
var icon = Icons.info_outline;
if (webViewModel.isIncognitoMode) {
icon = MaterialCommunityIcons.incognito;
} else if (isSecure) {
if (webViewModel.url != null &&
webViewModel.url!.scheme == "file") {
icon = Icons.offline_pin;
} else {
icon = Icons.lock;
}
}
return Icon(
icon,
color: isSecure ? Colors.green : Colors.grey,
);
},
),
onPressed: () {
showUrlInfo();
},
),
],
),
);
}
List<Widget> _buildActionsMenu() {
final browserModel = Provider.of<BrowserModel>(context, listen: true);
final windowModel = Provider.of<WindowModel>(context, listen: true);
final settings = browserModel.getSettings();
return [
settings.homePageEnabled
? const SizedBox(
width: 10.0,
)
: Container(),
Util.isDesktop()
? null
: InkWell(
key: tabInkWellKey,
onLongPress: () {
final RenderBox? box = tabInkWellKey.currentContext!
.findRenderObject() as RenderBox?;
if (box == null) {
return;
}
Offset position = box.localToGlobal(Offset.zero);
showMenu(
context: context,
position: RelativeRect.fromLTRB(position.dx,
position.dy + box.size.height, box.size.width, 0),
items: TabPopupMenuActions.choices
.map((tabPopupMenuAction) {
IconData? iconData;
switch (tabPopupMenuAction) {
case TabPopupMenuActions.CLOSE_TABS:
iconData = Icons.cancel;
break;
case TabPopupMenuActions.NEW_TAB:
iconData = Icons.add;
break;
case TabPopupMenuActions.NEW_INCOGNITO_TAB:
iconData = MaterialCommunityIcons.incognito;
break;
}
return PopupMenuItem<String>(
value: tabPopupMenuAction,
child: Row(children: [
Icon(
iconData,
color: Colors.black,
),
Container(
padding: const EdgeInsets.only(left: 10.0),
child: Text(tabPopupMenuAction),
)
]),
);
}).toList())
.then((value) {
switch (value) {
case TabPopupMenuActions.CLOSE_TABS:
windowModel.closeAllTabs();
break;
case TabPopupMenuActions.NEW_TAB:
addNewTab();
break;
case TabPopupMenuActions.NEW_INCOGNITO_TAB:
addNewIncognitoTab();
break;
}
});
},
onTap: () async {
if (windowModel.webViewTabs.isNotEmpty) {
var webViewModel = windowModel.getCurrentTab()?.webViewModel;
var webViewController = webViewModel?.webViewController;
if (View.of(context).viewInsets.bottom > 0.0) {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (FocusManager.instance.primaryFocus != null) {
FocusManager.instance.primaryFocus!.unfocus();
}
if (webViewController != null) {
await webViewController.evaluateJavascript(
source: "document.activeElemen
gitextract_n9elqeox/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── BUG_REPORT.md
│ │ └── FEATURE_REQUEST.md
│ └── workflows/
│ └── main.yml
├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android/
│ ├── .gitignore
│ ├── app/
│ │ ├── build.gradle
│ │ └── src/
│ │ ├── debug/
│ │ │ └── AndroidManifest.xml
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── pichillilorenzo/
│ │ │ │ └── flutter_browser/
│ │ │ │ └── MainActivity.kt
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ ├── launch_background.xml
│ │ │ │ ├── scrollbar_vertical_thumb.xml
│ │ │ │ └── scrollbar_vertical_track.xml
│ │ │ ├── drawable-v21/
│ │ │ │ └── launch_background.xml
│ │ │ ├── values/
│ │ │ │ └── styles.xml
│ │ │ └── values-night/
│ │ │ └── styles.xml
│ │ └── profile/
│ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ └── settings.gradle
├── ios/
│ ├── .gitignore
│ ├── Flutter/
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Runner/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── LaunchImage.imageset/
│ │ │ ├── Contents.json
│ │ │ └── README.md
│ │ ├── Base.lproj/
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ ├── Runner.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── Runner.xcscheme
│ └── Runner.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── lib/
│ ├── animated_flutter_browser_logo.dart
│ ├── app_bar/
│ │ ├── browser_app_bar.dart
│ │ ├── certificates_info_popup.dart
│ │ ├── custom_app_bar_wrapper.dart
│ │ ├── desktop_app_bar.dart
│ │ ├── find_on_page_app_bar.dart
│ │ ├── tab_viewer_app_bar.dart
│ │ ├── url_info_popup.dart
│ │ └── webview_tab_app_bar.dart
│ ├── browser.dart
│ ├── custom_image.dart
│ ├── custom_popup_dialog.dart
│ ├── custom_popup_menu_item.dart
│ ├── empty_tab.dart
│ ├── javascript_console_result.dart
│ ├── long_press_alert_dialog.dart
│ ├── main.dart
│ ├── material_transparent_page_route.dart
│ ├── models/
│ │ ├── browser_model.dart
│ │ ├── favorite_model.dart
│ │ ├── search_engine_model.dart
│ │ ├── web_archive_model.dart
│ │ ├── webview_model.dart
│ │ └── window_model.dart
│ ├── multiselect_dialog.dart
│ ├── pages/
│ │ ├── developers/
│ │ │ ├── javascript_console.dart
│ │ │ ├── main.dart
│ │ │ ├── network_info.dart
│ │ │ └── storage_manager.dart
│ │ └── settings/
│ │ ├── android_settings.dart
│ │ ├── cross_platform_settings.dart
│ │ ├── ios_settings.dart
│ │ └── main.dart
│ ├── popup_menu_actions.dart
│ ├── project_info_popup.dart
│ ├── tab_popup_menu_actions.dart
│ ├── tab_viewer.dart
│ ├── tab_viewer_popup_menu_actions.dart
│ ├── util.dart
│ └── webview_tab.dart
├── macos/
│ ├── .gitignore
│ ├── Flutter/
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Podfile
│ ├── Runner/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ └── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ └── MainMenu.xib
│ │ ├── Configs/
│ │ │ ├── AppInfo.xcconfig
│ │ │ ├── Debug.xcconfig
│ │ │ ├── Release.xcconfig
│ │ │ └── Warnings.xcconfig
│ │ ├── DebugProfile.entitlements
│ │ ├── Info.plist
│ │ ├── MainFlutterWindow.swift
│ │ └── Release.entitlements
│ ├── Runner.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ ├── RunnerTests/
│ │ └── RunnerTests.swift
│ └── packaging/
│ └── dmg/
│ └── make_config.yaml
├── pubspec.yaml
├── test/
│ └── widget_test.dart
└── windows/
├── .gitignore
├── CMakeLists.txt
├── flutter/
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
├── packaging/
│ └── msix/
│ └── make_config.yaml
└── runner/
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
SYMBOL INDEX (378 symbols across 48 files)
FILE: lib/animated_flutter_browser_logo.dart
class AnimatedFlutterBrowserLogo (line 3) | class AnimatedFlutterBrowserLogo extends StatefulWidget {
method createState (line 14) | State<StatefulWidget> createState()
class _AnimatedFlutterBrowserLogoState (line 17) | class _AnimatedFlutterBrowserLogoState extends State<AnimatedFlutterBrow...
method initState (line 22) | void initState()
method dispose (line 31) | void dispose()
method build (line 37) | Widget build(BuildContext context)
FILE: lib/app_bar/browser_app_bar.dart
class BrowserAppBar (line 7) | class BrowserAppBar extends StatefulWidget implements PreferredSizeWidget {
method createState (line 13) | State<BrowserAppBar> createState()
class _BrowserAppBarState (line 19) | class _BrowserAppBarState extends State<BrowserAppBar> {
method build (line 23) | Widget build(BuildContext context)
FILE: lib/app_bar/certificates_info_popup.dart
class CertificateInfoPopup (line 17) | class CertificateInfoPopup extends StatefulWidget {
method createState (line 21) | State<CertificateInfoPopup> createState()
class _CertificateInfoPopupState (line 24) | class _CertificateInfoPopupState extends State<CertificateInfoPopup> {
method build (line 30) | Widget build(BuildContext context)
method _build (line 34) | Widget _build()
method _getOtherCertificatesFromTopMain (line 78) | Future<void> _getOtherCertificatesFromTopMain(
method _buildCertificatesInfoAlertDialog (line 119) | AlertDialog _buildCertificatesInfoAlertDialog()
method _buildCertificateInfo (line 277) | Widget _buildCertificateInfo(X509Certificate x509certificate)
method _findCountryName (line 299) | String? _findCountryName(
method _findStateOrProvinceName (line 318) | String? _findStateOrProvinceName(
method _findCommonName (line 338) | String? _findCommonName(
method _findOrganizationName (line 357) | String? _findOrganizationName(
method _findOrganizationUnitName (line 377) | String? _findOrganizationUnitName(
method _buildIssuedToSection (line 397) | List<Widget> _buildIssuedToSection(X509Certificate x509certificate)
method _buildIssuedBySection (line 477) | List<Widget> _buildIssuedBySection(X509Certificate x509certificate)
method _buildValidityPeriodSection (line 607) | List<Widget> _buildValidityPeriodSection(X509Certificate x509certificate)
method _buildPublicKeySection (line 648) | List<Widget> _buildPublicKeySection(X509Certificate x509certificate)
method _buildFingerprintSection (line 696) | List<Widget> _buildFingerprintSection(X509Certificate x509certificate)
method _buildExtensionSection (line 772) | List<Widget> _buildExtensionSection(X509Certificate x509certificate)
method _buildKeyUsageSection (line 796) | List<Widget> _buildKeyUsageSection(X509Certificate x509certificate)
method _buildBasicConstraints (line 868) | List<Widget> _buildBasicConstraints(X509Certificate x509certificate)
method _buildExtendedKeyUsage (line 970) | List<Widget> _buildExtendedKeyUsage(X509Certificate x509certificate)
method _buildSubjectKeyIdentifier (line 1040) | List<Widget> _buildSubjectKeyIdentifier(X509Certificate x509certificate)
method _buildAuthorityKeyIdentifier (line 1119) | List<Widget> _buildAuthorityKeyIdentifier(X509Certificate x509certific...
method _buildCertificatePolicies (line 1199) | List<Widget> _buildCertificatePolicies(X509Certificate x509certificate)
method _buildCRLDistributionPoints (line 1274) | List<Widget> _buildCRLDistributionPoints(X509Certificate x509certificate)
method _buildAuthorityInfoAccess (line 1367) | List<Widget> _buildAuthorityInfoAccess(X509Certificate x509certificate)
method _buildSubjectAlternativeNames (line 1472) | List<Widget> _buildSubjectAlternativeNames(X509Certificate x509certifi...
FILE: lib/app_bar/custom_app_bar_wrapper.dart
class CustomAppBarWrapper (line 5) | class CustomAppBarWrapper extends StatefulWidget implements PreferredSiz...
method createState (line 18) | State<CustomAppBarWrapper> createState()
class _CustomAppBarWrapperState (line 24) | class _CustomAppBarWrapperState extends State<CustomAppBarWrapper> {
method build (line 26) | Widget build(BuildContext context)
class _PreferredAppBarSize (line 43) | class _PreferredAppBarSize extends Size {
FILE: lib/app_bar/desktop_app_bar.dart
class DesktopAppBar (line 15) | class DesktopAppBar extends StatefulWidget {
method createState (line 21) | State<DesktopAppBar> createState()
class _DesktopAppBarState (line 24) | class _DesktopAppBarState extends State<DesktopAppBar> {
method build (line 26) | Widget build(BuildContext context)
method _addNewTab (line 263) | void _addNewTab()
class WebViewTabSelector (line 274) | class WebViewTabSelector extends StatefulWidget {
method createState (line 281) | State<WebViewTabSelector> createState()
class _WebViewTabSelectorState (line 284) | class _WebViewTabSelectorState extends State<WebViewTabSelector> {
method dispose (line 288) | void dispose()
method build (line 293) | Widget build(BuildContext context)
class OpenTabsViewer (line 432) | class OpenTabsViewer extends StatefulWidget {
method createState (line 438) | State<OpenTabsViewer> createState()
class _OpenTabsViewerState (line 441) | class _OpenTabsViewerState extends State<OpenTabsViewer> {
method dispose (line 445) | void dispose()
method build (line 451) | Widget build(BuildContext context)
FILE: lib/app_bar/find_on_page_app_bar.dart
class FindOnPageAppBar (line 7) | class FindOnPageAppBar extends StatefulWidget {
method createState (line 13) | State<FindOnPageAppBar> createState()
class _FindOnPageAppBarState (line 16) | class _FindOnPageAppBarState extends State<FindOnPageAppBar> {
method dispose (line 27) | void dispose()
method build (line 33) | Widget build(BuildContext context)
FILE: lib/app_bar/tab_viewer_app_bar.dart
class TabViewerAppBar (line 14) | class TabViewerAppBar extends StatefulWidget implements PreferredSizeWid...
method createState (line 19) | State<TabViewerAppBar> createState()
class _TabViewerAppBarState (line 25) | class _TabViewerAppBarState extends State<TabViewerAppBar> {
method build (line 29) | Widget build(BuildContext context)
method _buildAddTabButton (line 37) | Widget _buildAddTabButton()
method _buildActionsMenu (line 46) | List<Widget> _buildActionsMenu()
method _popupMenuChoiceAction (line 160) | void _popupMenuChoiceAction(String choice)
method addNewTab (line 185) | void addNewTab({WebUri? url})
method addNewIncognitoTab (line 202) | void addNewIncognitoTab({WebUri? url})
method closeAllTabs (line 219) | void closeAllTabs()
method goToSettingsPage (line 228) | void goToSettingsPage()
FILE: lib/app_bar/url_info_popup.dart
class UrlInfoPopup (line 9) | class UrlInfoPopup extends StatefulWidget {
method createState (line 21) | State<UrlInfoPopup> createState()
class _UrlInfoPopupState (line 24) | class _UrlInfoPopupState extends State<UrlInfoPopup> {
method build (line 36) | Widget build(BuildContext context)
FILE: lib/app_bar/webview_tab_app_bar.dart
class WebViewTabAppBar (line 32) | class WebViewTabAppBar extends StatefulWidget {
method createState (line 38) | State<WebViewTabAppBar> createState()
class _WebViewTabAppBarState (line 41) | class _WebViewTabAppBarState extends State<WebViewTabAppBar>
method initState (line 62) | void initState()
method dispose (line 80) | void dispose()
method build (line 91) | Widget build(BuildContext context)
method _buildAppBarHomePageWidget (line 137) | Widget? _buildAppBarHomePageWidget()
method _buildSearchTextField (line 236) | Widget _buildSearchTextField()
method _buildActionsMenu (line 324) | List<Widget> _buildActionsMenu()
method _popupMenuChoiceAction (line 864) | void _popupMenuChoiceAction(String choice)
method addNewTab (line 929) | void addNewTab({WebUri? url})
method addNewIncognitoTab (line 944) | void addNewIncognitoTab({WebUri? url})
method showSavedWindows (line 959) | void showSavedWindows()
method showFavorites (line 1020) | void showFavorites()
method showHistory (line 1089) | void showHistory()
method showWebArchives (line 1150) | void showWebArchives()
method share (line 1221) | void share()
method openNewWindow (line 1230) | void openNewWindow()
method setShouldSave (line 1235) | void setShouldSave()
method toggleDesktopMode (line 1240) | void toggleDesktopMode()
method showUrlInfo (line 1264) | void showUrlInfo()
method goToDevelopersPage (line 1286) | void goToDevelopersPage()
method goToSettingsPage (line 1291) | void goToSettingsPage()
method openProjectPopup (line 1296) | void openProjectPopup()
method takeScreenshotAndShow (line 1307) | void takeScreenshotAndShow()
FILE: lib/browser.dart
class Browser (line 20) | class Browser extends StatefulWidget {
method createState (line 24) | State<Browser> createState()
class _BrowserState (line 27) | class _BrowserState extends State<Browser> with SingleTickerProviderStat...
method initState (line 34) | void initState()
method didChangeDependencies (line 62) | void didChangeDependencies()
method build (line 72) | Widget build(BuildContext context)
method _buildBrowser (line 76) | Widget _buildBrowser()
method _buildWebViewTabs (line 103) | Widget _buildWebViewTabs()
method _buildWebViewTabsContent (line 144) | Widget _buildWebViewTabsContent()
method _createProgressIndicator (line 179) | Widget _createProgressIndicator()
method _buildWebViewTabsViewer (line 196) | Widget _buildWebViewTabsViewer()
FILE: lib/custom_image.dart
class CustomImage (line 6) | class CustomImage extends StatelessWidget {
method build (line 26) | Widget build(BuildContext context)
method getImage (line 39) | Widget? getImage()
method getBrokenImageIcon (line 59) | Widget getBrokenImageIcon()
FILE: lib/custom_popup_dialog.dart
class CustomPopupDialogPageRoute (line 4) | class CustomPopupDialogPageRoute<T> extends MaterialTransparentPageRoute...
method didPop (line 23) | bool didPop(T? result)
method buildTransitions (line 29) | Widget buildTransitions(BuildContext context, Animation<double> animat...
class CustomPopupDialog (line 57) | class CustomPopupDialog extends StatefulWidget {
method createState (line 67) | State<StatefulWidget> createState()
method show (line 69) | CustomPopupDialogPageRoute show(
class _CustomPopupDialogState (line 90) | class _CustomPopupDialogState extends State<CustomPopupDialog>
method initState (line 96) | void initState()
method dispose (line 116) | void dispose()
method build (line 122) | Widget build(BuildContext context)
method hideTransition (line 145) | Future<void> hideTransition()
method hide (line 150) | Future<void> hide()
FILE: lib/custom_popup_menu_item.dart
class CustomPopupMenuItem (line 3) | class CustomPopupMenuItem<T> extends PopupMenuEntry<T> {
method represents (line 28) | bool represents(T? value)
method createState (line 31) | CustomPopupMenuItemState<T, CustomPopupMenuItem<T>> createState()
class CustomPopupMenuItemState (line 35) | class CustomPopupMenuItemState<T, W extends CustomPopupMenuItem<T>>
method buildChild (line 38) | Widget buildChild()
method handleTap (line 41) | void handleTap()
method build (line 46) | Widget build(BuildContext context)
FILE: lib/empty_tab.dart
class EmptyTab (line 11) | class EmptyTab extends StatefulWidget {
method createState (line 15) | State<EmptyTab> createState()
class _EmptyTabState (line 18) | class _EmptyTabState extends State<EmptyTab> {
method build (line 22) | Widget build(BuildContext context)
method openNewTab (line 72) | void openNewTab(value)
FILE: lib/javascript_console_result.dart
class JavaScriptConsoleResult (line 4) | class JavaScriptConsoleResult extends StatefulWidget {
method createState (line 20) | State<JavaScriptConsoleResult> createState()
class _JavaScriptConsoleResultState (line 24) | class _JavaScriptConsoleResultState extends State<JavaScriptConsoleResul...
method build (line 26) | Widget build(BuildContext context)
FILE: lib/long_press_alert_dialog.dart
class LongPressAlertDialog (line 21) | class LongPressAlertDialog extends StatefulWidget {
method createState (line 39) | State<LongPressAlertDialog> createState()
class _LongPressAlertDialogState (line 42) | class _LongPressAlertDialogState extends State<LongPressAlertDialog> {
method build (line 46) | Widget build(BuildContext context)
method _buildDialogLongPressHitTestResult (line 61) | List<Widget> _buildDialogLongPressHitTestResult()
method _buildLinkTile (line 96) | Widget _buildLinkTile()
method _buildLinkPreview (line 138) | Widget _buildLinkPreview()
method _buildOpenNewTab (line 181) | Widget _buildOpenNewTab()
method _buildOpenNewIncognitoTab (line 197) | Widget _buildOpenNewIncognitoTab()
method _buildCopyAddressLink (line 214) | Widget _buildCopyAddressLink()
method _buildShareLink (line 227) | Widget _buildShareLink()
method _buildImageTile (line 252) | Widget _buildImageTile()
method _buildDownloadImage (line 274) | Widget _buildDownloadImage()
method _buildShareImage (line 299) | Widget _buildShareImage()
method _buildOpenImageNewTab (line 323) | Widget _buildOpenImageNewTab()
method _buildSearchImageOnGoogle (line 339) | Widget _buildSearchImageOnGoogle()
FILE: lib/main.dart
function main (line 48) | void main(List<String> args)
class FlutterBrowserApp (line 143) | class FlutterBrowserApp extends StatefulWidget {
method createState (line 147) | State<FlutterBrowserApp> createState()
class _FlutterBrowserAppState (line 150) | class _FlutterBrowserAppState extends State<FlutterBrowserApp>
method initState (line 157) | void initState()
method _handleStateChange (line 169) | void _handleStateChange(AppLifecycleState state)
method dispose (line 178) | void dispose()
method build (line 185) | Widget build(BuildContext context)
method onWindowFocus (line 206) | void onWindowFocus([int? windowId])
method onWindowBlur (line 214) | void onWindowBlur([int? windowId])
FILE: lib/material_transparent_page_route.dart
class MaterialTransparentPageRoute (line 4) | class MaterialTransparentPageRoute<T> extends PageRoute<T> {
method canTransitionTo (line 29) | bool canTransitionTo(TransitionRoute<dynamic> nextRoute)
method buildPage (line 36) | Widget buildPage(
method buildTransitions (line 50) | Widget buildTransitions(BuildContext context, Animation<double> animat...
FILE: lib/models/browser_model.dart
class BrowserSettings (line 17) | class BrowserSettings {
method copy (line 29) | BrowserSettings copy()
method fromMap (line 37) | BrowserSettings? fromMap(Map<String, dynamic>? map)
method toMap (line 47) | Map<String, dynamic> toMap()
method toJson (line 56) | Map<String, dynamic> toJson()
method toString (line 61) | String toString()
class BrowserModel (line 66) | class BrowserModel extends ChangeNotifier {
method openWindow (line 90) | Future<void> openWindow(WindowModel? windowModel)
method removeWindow (line 109) | Future<void> removeWindow(WindowModel window)
method removeAllWindows (line 113) | Future<void> removeAllWindows()
method containsFavorite (line 120) | bool containsFavorite(FavoriteModel favorite)
method addFavorite (line 128) | void addFavorite(FavoriteModel favorite)
method addFavorites (line 133) | void addFavorites(List<FavoriteModel> favorites)
method clearFavorites (line 138) | void clearFavorites()
method removeFavorite (line 143) | void removeFavorite(FavoriteModel favorite)
method addWebArchive (line 154) | void addWebArchive(String url, WebArchiveModel webArchiveModel)
method addWebArchives (line 159) | void addWebArchives(Map<String, WebArchiveModel> webArchives)
method removeWebArchive (line 164) | void removeWebArchive(WebArchiveModel webArchive)
method clearWebArchives (line 177) | void clearWebArchives()
method getSettings (line 193) | BrowserSettings getSettings()
method updateSettings (line 197) | void updateSettings(BrowserSettings settings)
method getWindows (line 202) | Future<List<WindowModel>> getWindows()
method save (line 224) | Future<void> save()
method flush (line 239) | Future<void> flush()
method restore (line 256) | Future<void> restore()
method toMap (line 294) | Map<String, dynamic> toMap()
method toJson (line 303) | Map<String, dynamic> toJson()
method toString (line 308) | String toString()
FILE: lib/models/favorite_model.dart
class FavoriteModel (line 3) | class FavoriteModel {
method fromMap (line 10) | FavoriteModel? fromMap(Map<String, dynamic>? map)
method toMap (line 26) | Map<String, dynamic> toMap()
method toJson (line 34) | Map<String, dynamic> toJson()
method toString (line 39) | String toString()
FILE: lib/models/search_engine_model.dart
class SearchEngineModel (line 1) | class SearchEngineModel {
method fromMap (line 13) | SearchEngineModel? fromMap(Map<String, dynamic>? map)
method toMap (line 23) | Map<String, dynamic> toMap()
method toJson (line 32) | Map<String, dynamic> toJson()
method toString (line 37) | String toString()
FILE: lib/models/web_archive_model.dart
class WebArchiveModel (line 3) | class WebArchiveModel {
method fromMap (line 13) | WebArchiveModel? fromMap(Map<String, dynamic>? map)
method toMap (line 31) | Map<String, dynamic> toMap()
method toJson (line 41) | Map<String, dynamic> toJson()
method toString (line 46) | String toString()
FILE: lib/models/webview_model.dart
class WebViewModel (line 7) | class WebViewModel extends ChangeNotifier {
method addJavaScriptConsoleResults (line 162) | void addJavaScriptConsoleResults(Widget value)
method addJavaScriptConsoleHistory (line 177) | void addJavaScriptConsoleHistory(String value)
method addLoadedResources (line 192) | void addLoadedResources(LoadedResource value)
method updateWithValue (line 206) | void updateWithValue(WebViewModel webViewModel)
method fromMap (line 227) | WebViewModel? fromMap(Map<String, dynamic>? map)
method toMap (line 258) | Map<String, dynamic> toMap()
method toJson (line 276) | Map<String, dynamic> toJson()
method toString (line 281) | String toString()
FILE: lib/models/window_model.dart
class WindowModel (line 16) | class WindowModel extends ChangeNotifier {
method addTab (line 79) | void addTab(WebViewTab webViewTab)
method addTabs (line 90) | void addTabs(List<WebViewTab> webViewTabs)
method closeTab (line 104) | void closeTab(int index)
method showTab (line 127) | void showTab(int index)
method closeAllTabs (line 138) | void closeAllTabs()
method getCurrentTabIndex (line 150) | int getCurrentTabIndex()
method getCurrentTab (line 154) | WebViewTab? getCurrentTab()
method notifyWebViewTabUpdated (line 158) | void notifyWebViewTabUpdated()
method setCurrentWebViewModel (line 162) | void setCurrentWebViewModel(WebViewModel webViewModel)
method saveInfo (line 169) | Future<void> saveInfo()
method removeInfo (line 188) | Future<void> removeInfo()
method flushInfo (line 195) | Future<void> flushInfo()
method restoreInfo (line 215) | Future<void> restoreInfo()
method fromMap (line 273) | WindowModel fromMap(Map<String, dynamic> map)
method toMap (line 298) | Map<String, dynamic> toMap()
method toJson (line 311) | Map<String, dynamic> toJson()
method toString (line 316) | String toString()
FILE: lib/multiselect_dialog.dart
class MultiSelectDialogItem (line 6) | class MultiSelectDialogItem<V> {
class MultiSelectDialog (line 13) | class MultiSelectDialog<V> extends StatefulWidget {
method createState (line 53) | State<StatefulWidget> createState()
class _MultiSelectDialogState (line 56) | class _MultiSelectDialogState<V> extends State<MultiSelectDialog<V>> {
method initState (line 60) | void initState()
method _onItemCheckedChange (line 67) | void _onItemCheckedChange(V itemValue, bool checked)
method _onCancelTap (line 77) | void _onCancelTap()
method _onSubmitTap (line 81) | void _onSubmitTap()
method build (line 86) | Widget build(BuildContext context)
method _buildItem (line 124) | Widget _buildItem(MultiSelectDialogItem<V> item)
FILE: lib/pages/developers/javascript_console.dart
class JavaScriptConsole (line 9) | class JavaScriptConsole extends StatefulWidget {
method createState (line 13) | State<JavaScriptConsole> createState()
class _JavaScriptConsoleState (line 16) | class _JavaScriptConsoleState extends State<JavaScriptConsole> {
method dispose (line 24) | void dispose()
method build (line 31) | Widget build(BuildContext context)
method _buildJavaScriptConsole (line 35) | Widget _buildJavaScriptConsole()
method evaluateJavaScript (line 149) | void evaluateJavaScript(String source)
FILE: lib/pages/developers/main.dart
class DevelopersPage (line 7) | class DevelopersPage extends StatefulWidget {
method createState (line 11) | State<DevelopersPage> createState()
class _DevelopersPageState (line 14) | class _DevelopersPageState extends State<DevelopersPage> {
method build (line 16) | Widget build(BuildContext context)
FILE: lib/pages/developers/network_info.dart
class NetworkInfo (line 12) | class NetworkInfo extends StatefulWidget {
method createState (line 16) | State<NetworkInfo> createState()
class _NetworkInfoState (line 19) | class _NetworkInfoState extends State<NetworkInfo> {
method build (line 21) | Widget build(BuildContext context)
method _buildNetworkInfo (line 25) | Widget _buildNetworkInfo()
FILE: lib/pages/developers/storage_manager.dart
class StorageManager (line 9) | class StorageManager extends StatefulWidget {
method createState (line 13) | State<StorageManager> createState()
class _StorageManagerState (line 16) | class _StorageManagerState extends State<StorageManager> {
method initState (line 55) | void initState()
method dispose (line 69) | void dispose()
method build (line 82) | Widget build(BuildContext context)
method _buildStorageManager (line 86) | Widget _buildStorageManager()
method _buildCookiesExpansionTile (line 116) | Widget _buildCookiesExpansionTile(BoxConstraints constraints)
method _buildWebLocalStorageExpansionTile (line 389) | Widget _buildWebLocalStorageExpansionTile(BoxConstraints constraints)
method _buildWebSessionStorageExpansionTile (line 518) | Widget _buildWebSessionStorageExpansionTile(BoxConstraints constraints)
method _buildAndroidWebStorageExpansionTile (line 651) | Widget _buildAndroidWebStorageExpansionTile(BoxConstraints constraints)
method _buildIOSWebStorageExpansionTile (line 704) | Widget _buildIOSWebStorageExpansionTile(BoxConstraints constraints)
method _buildHttpAuthCredentialDatabaseExpansionTile (line 817) | Widget _buildHttpAuthCredentialDatabaseExpansionTile(
method _buildDataCellEditable (line 952) | DataCell _buildDataCellEditable(
method _buildAddNewWebStorageItem (line 994) | Widget _buildAddNewWebStorageItem(
FILE: lib/pages/settings/android_settings.dart
class AndroidSettings (line 10) | class AndroidSettings extends StatefulWidget {
method createState (line 14) | State<AndroidSettings> createState()
class _AndroidSettingsState (line 17) | class _AndroidSettingsState extends State<AndroidSettings> {
method build (line 19) | Widget build(BuildContext context)
method _buildAndroidWebViewTabSettings (line 25) | List<Widget> _buildAndroidWebViewTabSettings()
FILE: lib/pages/settings/cross_platform_settings.dart
class CrossPlatformSettings (line 14) | class CrossPlatformSettings extends StatefulWidget {
method createState (line 18) | State<CrossPlatformSettings> createState()
class _CrossPlatformSettingsState (line 21) | class _CrossPlatformSettingsState extends State<CrossPlatformSettings> {
method dispose (line 28) | void dispose()
method build (line 35) | Widget build(BuildContext context)
method _buildBaseSettings (line 47) | List<Widget> _buildBaseSettings()
method _buildWebViewTabSettings (line 263) | List<Widget> _buildWebViewTabSettings()
FILE: lib/pages/settings/ios_settings.dart
class IOSSettings (line 11) | class IOSSettings extends StatefulWidget {
method createState (line 15) | State<IOSSettings> createState()
class _IOSSettingsState (line 18) | class _IOSSettingsState extends State<IOSSettings> {
method build (line 20) | Widget build(BuildContext context)
method _buildIOSWebViewSettings (line 26) | List<Widget> _buildIOSWebViewSettings()
FILE: lib/pages/settings/main.dart
class PopupSettingsMenuActions (line 14) | class PopupSettingsMenuActions {
class SettingsPage (line 27) | class SettingsPage extends StatefulWidget {
method createState (line 31) | State<SettingsPage> createState()
class _SettingsPageState (line 34) | class _SettingsPageState extends State<SettingsPage> {
method build (line 36) | Widget build(BuildContext context)
method _popupMenuChoiceAction (line 123) | void _popupMenuChoiceAction(String choice)
FILE: lib/popup_menu_actions.dart
class PopupMenuActions (line 3) | class PopupMenuActions {
FILE: lib/project_info_popup.dart
class ProjectInfoPopup (line 13) | class ProjectInfoPopup extends StatefulWidget {
method createState (line 17) | State<StatefulWidget> createState()
class _ProjectInfoPopupState (line 20) | class _ProjectInfoPopupState extends State<ProjectInfoPopup> {
method build (line 22) | Widget build(BuildContext context)
FILE: lib/tab_popup_menu_actions.dart
class TabPopupMenuActions (line 1) | class TabPopupMenuActions {
FILE: lib/tab_viewer.dart
class ScrollableTab (line 8) | class ScrollableTab extends StatefulWidget {
method createState (line 17) | State<ScrollableTab> createState()
class _ScrollableTabState (line 20) | class _ScrollableTabState extends State<ScrollableTab> {
method build (line 22) | Widget build(BuildContext context)
class TabViewer (line 48) | class TabViewer extends StatefulWidget {
method createState (line 57) | State<TabViewer> createState()
class _TabViewerState (line 60) | class _TabViewerState extends State<TabViewer>
method initState (line 69) | void initState()
method didChangeDependencies (line 77) | void didChangeDependencies()
method initialize (line 85) | void initialize()
method didUpdateWidget (line 121) | void didUpdateWidget(TabViewer oldWidget)
method dispose (line 136) | void dispose()
method build (line 142) | Widget build(BuildContext context)
method updatePositions (line 233) | void updatePositions(double dy)
FILE: lib/tab_viewer_popup_menu_actions.dart
class TabViewerPopupMenuActions (line 1) | class TabViewerPopupMenuActions {
FILE: lib/util.dart
class Util (line 3) | class Util {
method urlIsSecure (line 4) | bool urlIsSecure(Uri url)
method isLocalizedContent (line 8) | bool isLocalizedContent(Uri url)
method isMobile (line 16) | bool isMobile()
method isAndroid (line 20) | bool isAndroid()
method isIOS (line 24) | bool isIOS()
method isDesktop (line 28) | bool isDesktop()
method isMacOS (line 32) | bool isMacOS()
method isWindows (line 36) | bool isWindows()
FILE: lib/webview_tab.dart
class WebViewTab (line 20) | class WebViewTab extends StatefulWidget {
method createState (line 26) | State<WebViewTab> createState()
class _WebViewTabState (line 29) | class _WebViewTabState extends State<WebViewTab> with WidgetsBindingObse...
method initState (line 42) | void initState()
method dispose (line 67) | void dispose()
method didChangeAppLifecycleState (line 84) | void didChangeAppLifecycleState(AppLifecycleState state)
method pauseAll (line 94) | void pauseAll()
method resumeAll (line 101) | void resumeAll()
method pause (line 108) | void pause()
method resume (line 114) | void resume()
method pauseTimers (line 120) | void pauseTimers()
method resumeTimers (line 126) | void resumeTimers()
method build (line 133) | Widget build(BuildContext context)
method _buildWebView (line 149) | InAppWebView _buildWebView()
method isCurrentTab (line 519) | bool isCurrentTab(WebViewModel currentWebViewModel)
method createHttpAuthDialog (line 523) | Future<HttpAuthResponseAction> createHttpAuthDialog(
method onShowTab (line 571) | void onShowTab()
method onHideTab (line 580) | void onHideTab()
FILE: test/widget_test.dart
function main (line 13) | void main()
FILE: windows/flutter/generated_plugin_registrant.cc
function RegisterPlugins (line 18) | void RegisterPlugins(flutter::PluginRegistry* registry) {
FILE: windows/runner/flutter_window.cpp
function LRESULT (line 50) | LRESULT
FILE: windows/runner/flutter_window.h
function class (line 12) | class FlutterWindow : public Win32Window {
FILE: windows/runner/main.cpp
function wWinMain (line 11) | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FILE: windows/runner/utils.cpp
function CreateAndAttachConsole (line 10) | void CreateAndAttachConsole() {
function GetCommandLineArguments (line 24) | std::vector<std::string> GetCommandLineArguments() {
function Utf8FromUtf16 (line 44) | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
FILE: windows/runner/win32_window.cpp
function Scale (line 36) | int Scale(int source, double scale_factor) {
function EnableFullDpiSupportIfAvailable (line 42) | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
class WindowClassRegistrar (line 59) | class WindowClassRegistrar {
method WindowClassRegistrar (line 64) | static WindowClassRegistrar* GetInstance() {
method WindowClassRegistrar (line 80) | WindowClassRegistrar() = default;
function wchar_t (line 89) | const wchar_t* WindowClassRegistrar::GetWindowClass() {
function LRESULT (line 157) | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
function LRESULT (line 176) | LRESULT
function Win32Window (line 236) | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
function RECT (line 252) | RECT Win32Window::GetClientArea() {
function HWND (line 258) | HWND Win32Window::GetHandle() {
FILE: windows/runner/win32_window.h
type Size (line 21) | struct Size {
Condensed preview — 129 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (649K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 753,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": ".github/ISSUE_TEMPLATE/BUG_REPORT.md",
"chars": 1132,
"preview": "---\nname: Bug report\nabout: Something is crashing or not working as intended\nlabels: bug\n---\n\n<!--\n ❗️❗️❗️ IMPORTANT "
},
{
"path": ".github/ISSUE_TEMPLATE/FEATURE_REQUEST.md",
"chars": 496,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n## Environment\n\n**Flutter version:** \n**App vers"
},
{
"path": ".github/workflows/main.yml",
"chars": 4654,
"preview": "name: \"Build & Release\"\n\non:\n push:\n tags:\n - \"v*\"\n\njobs:\n build-mac-ios-android:\n runs-on: macos-latest\n "
},
{
"path": ".gitignore",
"chars": 878,
"preview": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_d"
},
{
"path": ".metadata",
"chars": 1116,
"preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
},
{
"path": "LICENSE",
"chars": 10761,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 2708,
"preview": "# Flutter Browser App\n\n\n mavenCentral()\n }\n}\n\nrootProject.buildDir = '../build'\nsubp"
},
{
"path": "android/gradle/wrapper/gradle-wrapper.properties",
"chars": 200,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dist"
},
{
"path": "android/gradle.properties",
"chars": 153,
"preview": "org.gradle.jvmargs=-Xmx1536M\nandroid.enableR8=true\nandroid.useAndroidX=true\nandroid.enableJetifier=true\nandroid.bundle.e"
},
{
"path": "android/settings.gradle",
"chars": 726,
"preview": "pluginManagement {\n def flutterSdkPath = {\n def properties = new Properties()\n file(\"local.properties\")"
},
{
"path": "ios/.gitignore",
"chars": 569,
"preview": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/De"
},
{
"path": "ios/Flutter/AppFrameworkInfo.plist",
"chars": 795,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "ios/Flutter/Debug.xcconfig",
"chars": 106,
"preview": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
},
{
"path": "ios/Flutter/Release.xcconfig",
"chars": 108,
"preview": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
},
{
"path": "ios/Podfile",
"chars": 1353,
"preview": "# Uncomment this line to define a global platform for your project\nplatform :ios, '12.0'\n\n# CocoaPods analytics sends ne"
},
{
"path": "ios/Runner/AppDelegate.swift",
"chars": 741,
"preview": "import UIKit\nimport Flutter\nimport flutter_downloader\n\n@UIApplicationMain\n\n@objc class AppDelegate: FlutterAppDelegate {"
},
{
"path": "ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 2519,
"preview": "{\n \"images\" : [\n {\n \"size\" : \"20x20\",\n \"idiom\" : \"iphone\",\n \"filename\" : \"Icon-App-20x20@2x.png\",\n "
},
{
"path": "ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
"chars": 391,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"filename\" : \"LaunchImage.png\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
"chars": 336,
"preview": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in"
},
{
"path": "ios/Runner/Base.lproj/LaunchScreen.storyboard",
"chars": 2377,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "ios/Runner/Base.lproj/Main.storyboard",
"chars": 1605,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "ios/Runner/Info.plist",
"chars": 2231,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "ios/Runner/Runner-Bridging-Header.h",
"chars": 38,
"preview": "#import \"GeneratedPluginRegistrant.h\"\n"
},
{
"path": "ios/Runner.xcodeproj/project.pbxproj",
"chars": 24705,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
"chars": 226,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
"chars": 3185,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1510\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "ios/Runner.xcworkspace/contents.xcworkspacedata",
"chars": 224,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"group:Runner.xcodepr"
},
{
"path": "ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
"chars": 226,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "lib/animated_flutter_browser_logo.dart",
"chars": 1243,
"preview": "import 'package:flutter/material.dart';\n\nclass AnimatedFlutterBrowserLogo extends StatefulWidget {\n final Duration anim"
},
{
"path": "lib/app_bar/browser_app_bar.dart",
"chars": 1307,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/app_bar/desktop_app_bar.dart';\nimport 'package:f"
},
{
"path": "lib/app_bar/certificates_info_popup.dart",
"chars": 52887,
"preview": "import 'dart:developer';\nimport 'dart:io';\nimport 'dart:typed_data';\n\nimport 'package:crypto/crypto.dart';\nimport 'packa"
},
{
"path": "lib/app_bar/custom_app_bar_wrapper.dart",
"chars": 1436,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/app_bar/desktop_app_bar.dart';\nimport 'package:f"
},
{
"path": "lib/app_bar/desktop_app_bar.dart",
"chars": 21405,
"preview": "import 'package:collection/collection.dart';\nimport 'package:context_menus/context_menus.dart';\nimport 'package:flutter/"
},
{
"path": "lib/app_bar/find_on_page_app_bar.dart",
"chars": 2716,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/models/browser_model.dart';\nimport 'package:prov"
},
{
"path": "lib/app_bar/tab_viewer_app_bar.dart",
"chars": 7756,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/models/browser_model.dart';\nimport 'package:flut"
},
{
"path": "lib/app_bar/url_info_popup.dart",
"chars": 5243,
"preview": "import 'package:flutter/gestures.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_browser/app_bar/"
},
{
"path": "lib/app_bar/webview_tab_app_bar.dart",
"chars": 51308,
"preview": "// import 'package:cached_network_image/cached_network_image.dart';\nimport 'dart:io';\n\nimport 'package:collection/collec"
},
{
"path": "lib/browser.dart",
"chars": 11400,
"preview": "import 'dart:async';\n\n// import 'package:cached_network_image/cached_network_image.dart';\nimport 'package:flutter/materi"
},
{
"path": "lib/custom_image.dart",
"chars": 1567,
"preview": "import 'dart:convert';\nimport 'dart:typed_data';\n\nimport 'package:flutter/material.dart';\n\nclass CustomImage extends Sta"
},
{
"path": "lib/custom_popup_dialog.dart",
"chars": 4191,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/material_transparent_page_route.dart';\n\nclass Cu"
},
{
"path": "lib/custom_popup_menu_item.dart",
"chars": 2086,
"preview": "import 'package:flutter/material.dart';\n\nclass CustomPopupMenuItem<T> extends PopupMenuEntry<T> {\n const CustomPopupMen"
},
{
"path": "lib/empty_tab.dart",
"chars": 2942,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/util.dart';\nimport 'package:flutter_browser/webv"
},
{
"path": "lib/javascript_console_result.dart",
"chars": 1681,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\n\nclass JavaScriptConsoleResult extends S"
},
{
"path": "lib/long_press_alert_dialog.dart",
"chars": 10841,
"preview": "// import 'package:cached_network_image/cached_network_image.dart';\nimport 'dart:io';\n\nimport 'package:flutter/foundatio"
},
{
"path": "lib/main.dart",
"chars": 6874,
"preview": "import 'dart:io';\n\nimport 'package:context_menus/context_menus.dart';\nimport 'package:flutter/foundation.dart';\nimport '"
},
{
"path": "lib/material_transparent_page_route.dart",
"chars": 1501,
"preview": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\n\nclass MaterialTransparentPageRoute<T> "
},
{
"path": "lib/models/browser_model.dart",
"chars": 8153,
"preview": "import 'dart:convert';\nimport 'dart:async';\nimport 'dart:io';\n\nimport 'package:flutter/foundation.dart';\nimport 'package"
},
{
"path": "lib/models/favorite_model.dart",
"chars": 1042,
"preview": "import 'package:flutter_inappwebview/flutter_inappwebview.dart';\n\nclass FavoriteModel {\n WebUri? url;\n String? title;\n"
},
{
"path": "lib/models/search_engine_model.dart",
"chars": 2213,
"preview": "class SearchEngineModel {\n final String name;\n final String assetIcon;\n final String url;\n final String searchUrl;\n\n"
},
{
"path": "lib/models/web_archive_model.dart",
"chars": 1295,
"preview": "import 'package:flutter_inappwebview/flutter_inappwebview.dart';\n\nclass WebArchiveModel {\n WebUri? url;\n String? title"
},
{
"path": "lib/models/webview_model.dart",
"chars": 7995,
"preview": "import 'package:collection/collection.dart';\n\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/foundatio"
},
{
"path": "lib/models/window_model.dart",
"chars": 8665,
"preview": "import 'dart:async';\nimport 'dart:convert';\nimport 'dart:math';\n\nimport 'package:flutter/foundation.dart';\nimport 'packa"
},
{
"path": "lib/multiselect_dialog.dart",
"chars": 3944,
"preview": "import 'package:flutter/material.dart';\n\nconst EdgeInsets _defaultInsetPadding =\n EdgeInsets.symmetric(horizontal: 40"
},
{
"path": "lib/pages/developers/javascript_console.dart",
"chars": 7206,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/javascript_console_result.dart';\nimport 'package"
},
{
"path": "lib/pages/developers/main.dart",
"chars": 1570,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/app_bar/custom_app_bar_wrapper.dart';\nimport 'pa"
},
{
"path": "lib/pages/developers/network_info.dart",
"chars": 9707,
"preview": "// import 'package:cached_network_image/cached_network_image.dart';\nimport 'package:flutter/material.dart';\nimport 'pack"
},
{
"path": "lib/pages/developers/storage_manager.dart",
"chars": 40647,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_browser/main.dar"
},
{
"path": "lib/pages/settings/android_settings.dart",
"chars": 46331,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/models/browser_model.dart';\nimport 'package:flut"
},
{
"path": "lib/pages/settings/cross_platform_settings.dart",
"chars": 20872,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_browser/models/b"
},
{
"path": "lib/pages/settings/ios_settings.dart",
"chars": 25003,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/models/browser_model.dart';\nimport 'package:flut"
},
{
"path": "lib/pages/settings/main.dart",
"chars": 5470,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/models/browser_model.dart';\nimport 'package:flut"
},
{
"path": "lib/popup_menu_actions.dart",
"chars": 1911,
"preview": "import 'package:flutter_browser/util.dart';\n\nclass PopupMenuActions {\n // ignore: constant_identifier_names\n static co"
},
{
"path": "lib/project_info_popup.dart",
"chars": 5760,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_browser/models/browser_model.dart';\nimport 'package:flut"
},
{
"path": "lib/tab_popup_menu_actions.dart",
"chars": 407,
"preview": "class TabPopupMenuActions {\n // ignore: constant_identifier_names\n static const String CLOSE_TABS = \"Close tabs\";\n //"
},
{
"path": "lib/tab_viewer.dart",
"chars": 9390,
"preview": "import 'dart:async';\nimport 'dart:math';\n\nimport 'package:flutter/material.dart';\n\nimport 'main.dart';\n\nclass Scrollable"
},
{
"path": "lib/tab_viewer_popup_menu_actions.dart",
"chars": 524,
"preview": "class TabViewerPopupMenuActions {\n // ignore: constant_identifier_names\n static const String NEW_TAB = \"New tab\";\n //"
},
{
"path": "lib/util.dart",
"chars": 915,
"preview": "import 'package:flutter/foundation.dart';\n\nclass Util {\n static bool urlIsSecure(Uri url) {\n return (url.scheme == \""
},
{
"path": "lib/webview_tab.dart",
"chars": 19702,
"preview": "import 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart'"
},
{
"path": "macos/.gitignore",
"chars": 89,
"preview": "# Flutter-related\n**/Flutter/ephemeral/\n**/Pods/\n\n# Xcode-related\n**/dgph\n**/xcuserdata/\n"
},
{
"path": "macos/Flutter/Flutter-Debug.xcconfig",
"chars": 125,
"preview": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"ephemeral/Flutter-Generated.xccon"
},
{
"path": "macos/Flutter/Flutter-Release.xcconfig",
"chars": 127,
"preview": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"ephemeral/Flutter-Generated.xcc"
},
{
"path": "macos/Flutter/GeneratedPluginRegistrant.swift",
"chars": 1479,
"preview": "//\n// Generated file. Do not edit.\n//\n\nimport FlutterMacOS\nimport Foundation\n\nimport dynamic_color\nimport flutter_inapp"
},
{
"path": "macos/Podfile",
"chars": 1389,
"preview": "platform :osx, '10.14'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['CO"
},
{
"path": "macos/Runner/AppDelegate.swift",
"chars": 322,
"preview": "import Cocoa\nimport FlutterMacOS\nimport window_manager_plus\n\n@main\nclass AppDelegate: FlutterAppDelegate {\n override fu"
},
{
"path": "macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1291,
"preview": "{\n \"images\" : [\n {\n \"size\" : \"16x16\",\n \"idiom\" : \"mac\",\n \"filename\" : \"app_icon_16.png\",\n \"scale"
},
{
"path": "macos/Runner/Base.lproj/MainMenu.xib",
"chars": 23723,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
},
{
"path": "macos/Runner/Configs/AppInfo.xcconfig",
"chars": 638,
"preview": "// Application-level settings for the Runner target.\n//\n// This may be replaced with something auto-generated from metad"
},
{
"path": "macos/Runner/Configs/Debug.xcconfig",
"chars": 77,
"preview": "#include \"../../Flutter/Flutter-Debug.xcconfig\"\n#include \"Warnings.xcconfig\"\n"
},
{
"path": "macos/Runner/Configs/Release.xcconfig",
"chars": 79,
"preview": "#include \"../../Flutter/Flutter-Release.xcconfig\"\n#include \"Warnings.xcconfig\"\n"
},
{
"path": "macos/Runner/Configs/Warnings.xcconfig",
"chars": 580,
"preview": "WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverl"
},
{
"path": "macos/Runner/DebugProfile.entitlements",
"chars": 633,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "macos/Runner/Info.plist",
"chars": 1060,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "macos/Runner/MainFlutterWindow.swift",
"chars": 734,
"preview": "import Cocoa\nimport FlutterMacOS\nimport window_manager_plus\n\nclass MainFlutterWindow: NSWindow {\n override func awake"
},
{
"path": "macos/Runner/Release.entitlements",
"chars": 580,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "macos/Runner.xcodeproj/project.pbxproj",
"chars": 33122,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXAggregateTarget sec"
},
{
"path": "macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
"chars": 3699,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1510\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "macos/Runner.xcworkspace/contents.xcworkspacedata",
"chars": 224,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"group:Runner.xcodepr"
},
{
"path": "macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "macos/RunnerTests/RunnerTests.swift",
"chars": 290,
"preview": "import Cocoa\nimport FlutterMacOS\nimport XCTest\n\nclass RunnerTests: XCTestCase {\n\n func testExample() {\n // If you ad"
},
{
"path": "macos/packaging/dmg/make_config.yaml",
"chars": 166,
"preview": "title: Flutter Browser\ncontents:\n - x: 448\n y: 344\n type: link\n path: \"/Applications\"\n - x: 192\n y: 344\n "
},
{
"path": "pubspec.yaml",
"chars": 4931,
"preview": "name: flutter_browser\ndescription: A Full-Featured Mobile Browser App (such as the Google Chrome mobile browser) created"
},
{
"path": "test/widget_test.dart",
"chars": 1072,
"preview": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester"
},
{
"path": "windows/.gitignore",
"chars": 291,
"preview": "flutter/ephemeral/\n\n# Visual Studio user-specific files.\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# Visual Studio bu"
},
{
"path": "windows/CMakeLists.txt",
"chars": 4174,
"preview": "# Project-level configuration.\ncmake_minimum_required(VERSION 3.14)\nproject(flutter_browser_app LANGUAGES CXX)\n\n# The na"
},
{
"path": "windows/flutter/CMakeLists.txt",
"chars": 3742,
"preview": "# This file controls Flutter-level build steps. It should not be edited.\ncmake_minimum_required(VERSION 3.14)\n\nset(EPHEM"
},
{
"path": "windows/flutter/generated_plugin_registrant.cc",
"chars": 1659,
"preview": "//\n// Generated file. Do not edit.\n//\n\n// clang-format off\n\n#include \"generated_plugin_registrant.h\"\n\n#include <dynamic"
},
{
"path": "windows/flutter/generated_plugin_registrant.h",
"chars": 302,
"preview": "//\n// Generated file. Do not edit.\n//\n\n// clang-format off\n\n#ifndef GENERATED_PLUGIN_REGISTRANT_\n#define GENERATED_PLUG"
},
{
"path": "windows/flutter/generated_plugins.cmake",
"chars": 927,
"preview": "#\n# Generated file, do not edit.\n#\n\nlist(APPEND FLUTTER_PLUGIN_LIST\n dynamic_color\n flutter_inappwebview_windows\n per"
},
{
"path": "windows/packaging/msix/make_config.yaml",
"chars": 257,
"preview": "display_name: Flutter Browser\npublisher_display_name: pichillilorenzo\nidentity_name: com.pichillilorenzo.flutter-browser"
},
{
"path": "windows/runner/CMakeLists.txt",
"chars": 1796,
"preview": "cmake_minimum_required(VERSION 3.14)\nproject(runner LANGUAGES CXX)\n\n# Define the application target. To change its name,"
},
{
"path": "windows/runner/Runner.rc",
"chars": 3089,
"preview": "// Microsoft Visual C++ generated resource script.\n//\n#pragma code_page(65001)\n#include \"resource.h\"\n\n#define APSTUDIO_R"
},
{
"path": "windows/runner/flutter_window.cpp",
"chars": 2125,
"preview": "#include \"flutter_window.h\"\n\n#include <optional>\n\n#include \"flutter/generated_plugin_registrant.h\"\n\nFlutterWindow::Flutt"
},
{
"path": "windows/runner/flutter_window.h",
"chars": 928,
"preview": "#ifndef RUNNER_FLUTTER_WINDOW_H_\n#define RUNNER_FLUTTER_WINDOW_H_\n\n#include <flutter/dart_project.h>\n#include <flutter/f"
},
{
"path": "windows/runner/main.cpp",
"chars": 1965,
"preview": "#include <flutter/dart_project.h>\n#include <flutter/flutter_view_controller.h>\n#include <windows.h>\n\n#include \"flutter_w"
},
{
"path": "windows/runner/resource.h",
"chars": 432,
"preview": "//{{NO_DEPENDENCIES}}\n// Microsoft Visual C++ generated include file.\n// Used by Runner.rc\n//\n#define IDI_APP_ICON "
},
{
"path": "windows/runner/runner.exe.manifest",
"chars": 602,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersi"
},
{
"path": "windows/runner/utils.cpp",
"chars": 1797,
"preview": "#include \"utils.h\"\n\n#include <flutter_windows.h>\n#include <io.h>\n#include <stdio.h>\n#include <windows.h>\n\n#include <iost"
},
{
"path": "windows/runner/utils.h",
"chars": 672,
"preview": "#ifndef RUNNER_UTILS_H_\n#define RUNNER_UTILS_H_\n\n#include <string>\n#include <vector>\n\n// Creates a console for the proce"
},
{
"path": "windows/runner/win32_window.cpp",
"chars": 8534,
"preview": "#include \"win32_window.h\"\n\n#include <dwmapi.h>\n#include <flutter_windows.h>\n\n#include \"resource.h\"\n\nnamespace {\n\n/// Win"
},
{
"path": "windows/runner/win32_window.h",
"chars": 3522,
"preview": "#ifndef RUNNER_WIN32_WINDOW_H_\n#define RUNNER_WIN32_WINDOW_H_\n\n#include <windows.h>\n\n#include <functional>\n#include <mem"
}
]
About this extraction
This page contains the full source code of the pichillilorenzo/flutter_browser_app GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 129 files (600.0 KB), approximately 135.3k tokens, and a symbol index with 378 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.