diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7228680..39f33fc 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -16,7 +16,7 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
- channel: 'stable'
+ channel: 'dev'
- run: flutter config --enable-web
- run: flutter pub get
@@ -30,4 +30,4 @@ jobs:
uses: codecov/codecov-action@v1.0.2
with:
token: ${{secrets.CODECOV_TOKEN}}
- flags: unittests
\ No newline at end of file
+ flags: unittests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 764b7b9..9460c34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## [1.1.0-nullsafety.0]
+* migrate to nullsafety.
+
## [1.0.5] - 2020/7/20
* Expose duration to `showSimpleNotification` [#46](https://github.com/boyan01/overlay_support/pull/46) by [Elvis Sun](https://github.com/elvisun)
diff --git a/example/android/.gitignore b/example/android/.gitignore
new file mode 100644
index 0000000..0a741cb
--- /dev/null
+++ b/example/android/.gitignore
@@ -0,0 +1,11 @@
+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
diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..449a9f9
--- /dev/null
+++ b/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/example/ios/.gitignore b/example/ios/.gitignore
new file mode 100644
index 0000000..e96ef60
--- /dev/null
+++ b/example/ios/.gitignore
@@ -0,0 +1,32 @@
+*.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/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
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 4ce912a..8236945 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,8 +1,9 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:overlay_support/overlay_support.dart';
-import 'package:overlay_support_example/pages/page_main.dart';
-import 'package:overlay_support_example/pages/page_with_ime.dart';
+
+import 'pages/page_main.dart';
+import 'pages/page_with_ime.dart';
void main() {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
@@ -50,7 +51,7 @@ class _ExampleDrawerState extends State<_ExampleDrawer> {
color: Theme.of(context).primaryIconTheme.color,
progress: ProxyAnimation(kAlwaysDismissedAnimation)),
onPressed: () {
- _scaffoldKey.currentState.openDrawer();
+ _scaffoldKey.currentState?.openDrawer();
}),
),
body: HomePage(),
diff --git a/example/lib/notification/custom_animation.dart b/example/lib/notification/custom_animation.dart
index e20b79a..283509c 100644
--- a/example/lib/notification/custom_animation.dart
+++ b/example/lib/notification/custom_animation.dart
@@ -9,7 +9,7 @@ class CustomAnimationToast extends StatelessWidget {
static final Tween tweenOpacity = Tween(begin: 0, end: 1);
- const CustomAnimationToast({Key key, @required this.value}) : super(key: key);
+ const CustomAnimationToast({Key? key, required this.value}) : super(key: key);
@override
Widget build(BuildContext context) {
diff --git a/example/lib/notification/custom_notification.dart b/example/lib/notification/custom_notification.dart
index e1bdd30..32e5776 100644
--- a/example/lib/notification/custom_notification.dart
+++ b/example/lib/notification/custom_notification.dart
@@ -15,9 +15,9 @@ class MessageNotification extends StatelessWidget {
final String message;
const MessageNotification({
- Key key,
- @required this.onReply,
- @required this.message,
+ Key? key,
+ required this.onReply,
+ required this.message,
}) : super(key: key);
@override
diff --git a/example/lib/notification/ios_toast.dart b/example/lib/notification/ios_toast.dart
index 00929b3..2cbb76c 100644
--- a/example/lib/notification/ios_toast.dart
+++ b/example/lib/notification/ios_toast.dart
@@ -5,7 +5,7 @@ class IosStyleToast extends StatelessWidget {
Widget build(BuildContext context) {
return SafeArea(
child: DefaultTextStyle(
- style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.white),
+ style: Theme.of(context).textTheme.bodyText2!.copyWith(color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(16),
child: Center(
diff --git a/example/lib/pages/page_main.dart b/example/lib/pages/page_main.dart
index a2f8b0d..b51e70c 100644
--- a/example/lib/pages/page_main.dart
+++ b/example/lib/pages/page_main.dart
@@ -32,7 +32,7 @@ class HomePage extends StatelessWidget {
return FlatButton(
textColor: Colors.yellow,
onPressed: () {
- OverlaySupportEntry.of(context).dismiss();
+ OverlaySupportEntry.of(context)!.dismiss();
},
child: Text('Dismiss'));
}),
@@ -57,7 +57,7 @@ class HomePage extends StatelessWidget {
return MessageNotification(
message: messages[3],
onReply: () {
- OverlaySupportEntry.of(context).dismiss();
+ OverlaySupportEntry.of(context)!.dismiss();
toast('you checked this message');
},
);
@@ -74,7 +74,7 @@ class HomePage extends StatelessWidget {
return MessageNotification(
message: messages[i],
onReply: () {
- OverlaySupportEntry.of(context).dismiss();
+ OverlaySupportEntry.of(context)!.dismiss();
toast('you checked this message');
},
);
@@ -121,13 +121,13 @@ class HomePage extends StatelessWidget {
return Container(
color: Color.lerp(Colors.transparent, Colors.black54, t),
child: FractionalTranslation(
- translation: Offset.lerp(const Offset(0, -1), const Offset(0, 0), t),
+ translation: Offset.lerp(const Offset(0, -1), const Offset(0, 0), t)!,
child: Column(
children: [
MessageNotification(
message: "Hello",
onReply: () {
- OverlaySupportEntry.of(context).dismiss();
+ OverlaySupportEntry.of(context)!.dismiss();
},
key: ModalKey(const Object()),
),
@@ -150,7 +150,7 @@ class _Section extends StatelessWidget {
final List children;
- const _Section({Key key, @required this.title, @required this.children}) : super(key: key);
+ const _Section({Key? key, required this.title, required this.children}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -171,7 +171,7 @@ class _Section extends StatelessWidget {
class _Title extends StatelessWidget {
final String title;
- const _Title({Key key, @required this.title}) : super(key: key);
+ const _Title({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index e03c919..a7c422d 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -2,9 +2,10 @@ name: overlay_support_example
description: example for overlay_support library
version: 1.0.0+1
+publish_to: none
environment:
- sdk: ">=2.1.0 <3.0.0"
+ sdk: '>=2.12.0-0 <3.0.0'
dependencies:
flutter:
diff --git a/example/web/favicon.png b/example/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
Binary files /dev/null and b/example/web/favicon.png differ
diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
Binary files /dev/null and b/example/web/icons/Icon-192.png differ
diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
Binary files /dev/null and b/example/web/icons/Icon-512.png differ
diff --git a/example/web/manifest.json b/example/web/manifest.json
new file mode 100644
index 0000000..8c01291
--- /dev/null
+++ b/example/web/manifest.json
@@ -0,0 +1,23 @@
+{
+ "name": "example",
+ "short_name": "example",
+ "start_url": ".",
+ "display": "standalone",
+ "background_color": "#0175C2",
+ "theme_color": "#0175C2",
+ "description": "A new Flutter project.",
+ "orientation": "portrait-primary",
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "icons/Icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/example/windows/.gitignore b/example/windows/.gitignore
index 0f3fe5a..d492d0d 100644
--- a/example/windows/.gitignore
+++ b/example/windows/.gitignore
@@ -1,332 +1,17 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+flutter/ephemeral/
-# Local additions, not from the link above
-flutter/
-
-# User-specific files
+# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
+# Visual Studio build-related files.
x64/
x86/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-
-# Visual Studio 2015/2017 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-
-# StyleCop
-StyleCopReport.xml
-
-# Files built by Visual Studio
-*_i.c
-*_p.c
-*_i.h
-*.ilk
-*.meta
-*.obj
-*.iobj
-*.pch
-*.pdb
-*.ipdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*.log
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-*.rptproj.rsuser
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
diff --git a/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..abf9040
--- /dev/null
+++ b/example/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(example LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+ set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+ CACHE STRING "" FORCE)
+else()
+ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+ endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_17)
+ target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+ target_compile_options(${TARGET} PRIVATE /EHsc)
+ target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+ target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+ install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ CONFIGURATIONS Profile;Release
+ COMPONENT Runtime)
diff --git a/example/windows/Generated.props b/example/windows/Generated.props
deleted file mode 100644
index 2965254..0000000
--- a/example/windows/Generated.props
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- C:\Users\boyan\dev\flutter
-
-
-
-
-
-
-
- $(FLUTTER_ROOT)
- true
-
-
- $(EXTRA_BUNDLE_FLAGS)
- true
-
-
-
-
\ No newline at end of file
diff --git a/example/windows/Runner.sln b/example/windows/Runner.sln
deleted file mode 100644
index 8c59769..0000000
--- a/example/windows/Runner.sln
+++ /dev/null
@@ -1,25 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.28307.645
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{5A827760-CF8B-408A-99A3-B6C0AD2271E7}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|x64 = Debug|x64
- Release|x64 = Release|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.ActiveCfg = Debug|x64
- {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.Build.0 = Debug|x64
- {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.ActiveCfg = Release|x64
- {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {B8A69CB0-A974-4774-9EBD-1E5EECACD186}
- EndGlobalSection
-EndGlobal
diff --git a/example/windows/Runner.vcxproj b/example/windows/Runner.vcxproj
deleted file mode 100644
index e94c41c..0000000
--- a/example/windows/Runner.vcxproj
+++ /dev/null
@@ -1,161 +0,0 @@
-
-
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- 15.0
- {5A827760-CF8B-408A-99A3-B6C0AD2271E7}
- GLFWExample
- 10.0.17763.0
-
-
-
- Application
- true
- v141
- v142
- MultiByte
-
-
- Application
- false
- v141
- v142
- true
- MultiByte
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\
- $(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\
- $(ProjectDir)flutter\;$(ProjectDir)flutter\cpp_client_wrapper\include\;$(IncludePath)
- $(ProjectDir)flutter;$(LibraryPath)
- Flutter Desktop Example
-
-
- $(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\
- $(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\
- $(ProjectDir)flutter\;$(ProjectDir)flutter\cpp_client_wrapper\include\;$(IncludePath)
- $(ProjectDir)flutter;$(LibraryPath)
- Flutter Desktop Example
-
-
-
- Level3
- Disabled
- true
- true
-
-
- _MBCS;%(PreprocessorDefinitions)
-
-
- flutter_windows.dll.lib;opengl32.lib;%(AdditionalDependencies)
-
-
- "$(ProjectDir)scripts\prepare_dependencies" debug
- Sync and build dependencies
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "$(ProjectDir)scripts\bundle_assets_and_deps" "$(ProjectDir)flutter\" "$(OutputPath)" "$(TargetFileName)"
-
-
- Bundling dependencies
- Dummy_Run_Always
-
-
-
-
-
-
- Level3
- MaxSpeed
- true
- true
- true
- true
-
-
- _MBCS;%(PreprocessorDefinitions)
-
-
- true
- true
- flutter_windows.dll.lib;opengl32.lib;%(AdditionalDependencies)
-
-
- "$(ProjectDir)scripts\prepare_dependencies" release
- Sync and build dependencies
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "$(ProjectDir)scripts\bundle_assets_and_deps" "$(ProjectDir)flutter\" "$(OutputPath)" "$(TargetFileName)"
-
-
- Bundling dependencies
- Dummy_Run_Always
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(SolutionDir)
-
-
diff --git a/example/windows/Runner.vcxproj.filters b/example/windows/Runner.vcxproj.filters
deleted file mode 100644
index 9ab54f1..0000000
--- a/example/windows/Runner.vcxproj.filters
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
-
-
- {2761a4b5-57b2-4d50-a677-d20ddc17a7f1}
-
-
-
-
- Source Files
-
-
- Source Files\Client Wrapper
-
-
- Source Files\Client Wrapper
-
-
- Source Files\Client Wrapper
-
-
-
diff --git a/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..c7a8c76
--- /dev/null
+++ b/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "flutter_export.h"
+ "flutter_windows.h"
+ "flutter_messenger.h"
+ "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+ "core_implementations.cc"
+ "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+ "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+ "flutter_engine.cc"
+ "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+ POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+ CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+ "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+ "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+ ${CPP_WRAPPER_SOURCES_APP}
+ ${PHONY_OUTPUT}
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+ windows-x64 $
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_PLUGIN}
+ ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..4bfa0f3
--- /dev/null
+++ b/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,9 @@
+//
+// Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+}
diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9846246
--- /dev/null
+++ b/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+// Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+ target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/example/windows/flutter_embedder_example.cpp b/example/windows/flutter_embedder_example.cpp
deleted file mode 100644
index 20f5e5a..0000000
--- a/example/windows/flutter_embedder_example.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-#include
-#include
-#include
-
-#include "flutter/flutter_window_controller.h"
-
-// Include windows.h last, to minimize potential conflicts. The CreateWindow
-// macro needs to be undefined because it prevents calling
-// FlutterWindowController's method.
-#include
-#undef CreateWindow
-
-namespace {
-
-// Returns the path of the directory containing this executable, or an empty
-// string if the directory cannot be found.
-std::string GetExecutableDirectory() {
- char buffer[MAX_PATH];
- if (GetModuleFileName(nullptr, buffer, MAX_PATH) == 0) {
- std::cerr << "Couldn't locate executable" << std::endl;
- return "";
- }
- std::string executable_path(buffer);
- size_t last_separator_position = executable_path.find_last_of('\\');
- if (last_separator_position == std::string::npos) {
- std::cerr << "Unabled to find parent directory of " << executable_path
- << std::endl;
- return "";
- }
- return executable_path.substr(0, last_separator_position);
-}
-
-} // namespace
-
-int main(int argc, char **argv) {
- // Resources are located relative to the executable.
- std::string base_directory = GetExecutableDirectory();
- if (base_directory.empty()) {
- base_directory = ".";
- }
- std::string data_directory = base_directory + "\\data";
- std::string assets_path = data_directory + "\\flutter_assets";
- std::string icu_data_path = data_directory + "\\icudtl.dat";
-
- // Arguments for the Flutter Engine.
- std::vector arguments;
-
- flutter::FlutterWindowController flutter_controller(icu_data_path);
-
- // Start the engine.
- if (!flutter_controller.CreateWindow(800, 600, "Flutter Desktop Example",
- assets_path, arguments)) {
- return EXIT_FAILURE;
- }
-
- // Run until the window is closed.
- flutter_controller.RunEventLoop();
- return EXIT_SUCCESS;
-}
diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..977e38b
--- /dev/null
+++ b/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+ "flutter_window.cpp"
+ "main.cpp"
+ "run_loop.cpp"
+ "utils.cpp"
+ "win32_window.cpp"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+ "Runner.rc"
+ "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..4da1601
--- /dev/null
+++ b/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON ICON "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "tech.soit.example" "\0"
+ VALUE "FileDescription", "A new Flutter project." "\0"
+ VALUE "FileVersion", VERSION_AS_STRING "\0"
+ VALUE "InternalName", "example" "\0"
+ VALUE "LegalCopyright", "Copyright (C) 2020 tech.soit.example. All rights reserved." "\0"
+ VALUE "OriginalFilename", "example.exe" "\0"
+ VALUE "ProductName", "example" "\0"
+ VALUE "ProductVersion", VERSION_AS_STRING "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..c422723
--- /dev/null
+++ b/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,64 @@
+#include "flutter_window.h"
+
+#include
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(RunLoop* run_loop,
+ const flutter::DartProject& project)
+ : run_loop_(run_loop), project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+ if (!Win32Window::OnCreate()) {
+ return false;
+ }
+
+ RECT frame = GetClientArea();
+
+ // The size here must match the window dimensions to avoid unnecessary surface
+ // creation / destruction in the startup path.
+ flutter_controller_ = std::make_unique(
+ frame.right - frame.left, frame.bottom - frame.top, project_);
+ // Ensure that basic setup of the controller was successful.
+ if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+ return false;
+ }
+ RegisterPlugins(flutter_controller_->engine());
+ run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
+ SetChildContent(flutter_controller_->view()->GetNativeWindow());
+ return true;
+}
+
+void FlutterWindow::OnDestroy() {
+ if (flutter_controller_) {
+ run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
+ flutter_controller_ = nullptr;
+ }
+
+ Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ // Give Flutter, including plugins, an opporutunity to handle window messages.
+ if (flutter_controller_) {
+ std::optional result =
+ flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+ lparam);
+ if (result) {
+ return *result;
+ }
+ }
+
+ switch (message) {
+ case WM_FONTCHANGE:
+ flutter_controller_->engine()->ReloadSystemFonts();
+ break;
+ }
+
+ return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..b663ddd
--- /dev/null
+++ b/example/windows/runner/flutter_window.h
@@ -0,0 +1,39 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include
+#include
+
+#include
+
+#include "run_loop.h"
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+ // Creates a new FlutterWindow driven by the |run_loop|, hosting a
+ // Flutter view running |project|.
+ explicit FlutterWindow(RunLoop* run_loop,
+ const flutter::DartProject& project);
+ virtual ~FlutterWindow();
+
+ protected:
+ // Win32Window:
+ bool OnCreate() override;
+ void OnDestroy() override;
+ LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+ LPARAM const lparam) noexcept override;
+
+ private:
+ // The run loop driving events for this window.
+ RunLoop* run_loop_;
+
+ // The project to run.
+ flutter::DartProject project_;
+
+ // The Flutter instance hosted by this window.
+ std::unique_ptr flutter_controller_;
+};
+
+#endif // RUNNER_FLUTTER_WINDOW_H_
diff --git a/example/windows/runner/main.cpp b/example/windows/runner/main.cpp
new file mode 100644
index 0000000..b637809
--- /dev/null
+++ b/example/windows/runner/main.cpp
@@ -0,0 +1,42 @@
+#include
+#include
+#include
+
+#include "flutter_window.h"
+#include "run_loop.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+ _In_ wchar_t *command_line, _In_ int show_command) {
+ // Attach to console when present (e.g., 'flutter run') or create a
+ // new console when running with a debugger.
+ if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+ CreateAndAttachConsole();
+ }
+
+ // Initialize COM, so that it is available for use in the library and/or
+ // plugins.
+ ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+ RunLoop run_loop;
+
+ flutter::DartProject project(L"data");
+
+ std::vector command_line_arguments =
+ GetCommandLineArguments();
+
+ project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+ FlutterWindow window(&run_loop, project);
+ Win32Window::Point origin(10, 10);
+ Win32Window::Size size(1280, 720);
+ if (!window.CreateAndShow(L"example", origin, size)) {
+ return EXIT_FAILURE;
+ }
+ window.SetQuitOnClose(true);
+
+ run_loop.Run();
+
+ ::CoUninitialize();
+ return EXIT_SUCCESS;
+}
diff --git a/example/windows/runner/resource.h b/example/windows/runner/resource.h
new file mode 100644
index 0000000..66a65d1
--- /dev/null
+++ b/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/example/windows/runner/resources/app_icon.ico b/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
Binary files /dev/null and b/example/windows/runner/resources/app_icon.ico differ
diff --git a/example/windows/runner/run_loop.cpp b/example/windows/runner/run_loop.cpp
new file mode 100644
index 0000000..2d6636a
--- /dev/null
+++ b/example/windows/runner/run_loop.cpp
@@ -0,0 +1,66 @@
+#include "run_loop.h"
+
+#include
+
+#include
+
+RunLoop::RunLoop() {}
+
+RunLoop::~RunLoop() {}
+
+void RunLoop::Run() {
+ bool keep_running = true;
+ TimePoint next_flutter_event_time = TimePoint::clock::now();
+ while (keep_running) {
+ std::chrono::nanoseconds wait_duration =
+ std::max(std::chrono::nanoseconds(0),
+ next_flutter_event_time - TimePoint::clock::now());
+ ::MsgWaitForMultipleObjects(
+ 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000),
+ QS_ALLINPUT);
+ bool processed_events = false;
+ MSG message;
+ // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+ // won't return again for items left in the queue after PeekMessage.
+ while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+ processed_events = true;
+ if (message.message == WM_QUIT) {
+ keep_running = false;
+ break;
+ }
+ ::TranslateMessage(&message);
+ ::DispatchMessage(&message);
+ // Allow Flutter to process messages each time a Windows message is
+ // processed, to prevent starvation.
+ next_flutter_event_time =
+ std::min(next_flutter_event_time, ProcessFlutterMessages());
+ }
+ // If the PeekMessage loop didn't run, process Flutter messages.
+ if (!processed_events) {
+ next_flutter_event_time =
+ std::min(next_flutter_event_time, ProcessFlutterMessages());
+ }
+ }
+}
+
+void RunLoop::RegisterFlutterInstance(
+ flutter::FlutterEngine* flutter_instance) {
+ flutter_instances_.insert(flutter_instance);
+}
+
+void RunLoop::UnregisterFlutterInstance(
+ flutter::FlutterEngine* flutter_instance) {
+ flutter_instances_.erase(flutter_instance);
+}
+
+RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
+ TimePoint next_event_time = TimePoint::max();
+ for (auto instance : flutter_instances_) {
+ std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
+ if (wait_duration != std::chrono::nanoseconds::max()) {
+ next_event_time =
+ std::min(next_event_time, TimePoint::clock::now() + wait_duration);
+ }
+ }
+ return next_event_time;
+}
diff --git a/example/windows/runner/run_loop.h b/example/windows/runner/run_loop.h
new file mode 100644
index 0000000..000d362
--- /dev/null
+++ b/example/windows/runner/run_loop.h
@@ -0,0 +1,40 @@
+#ifndef RUNNER_RUN_LOOP_H_
+#define RUNNER_RUN_LOOP_H_
+
+#include
+
+#include
+#include
+
+// A runloop that will service events for Flutter instances as well
+// as native messages.
+class RunLoop {
+ public:
+ RunLoop();
+ ~RunLoop();
+
+ // Prevent copying
+ RunLoop(RunLoop const&) = delete;
+ RunLoop& operator=(RunLoop const&) = delete;
+
+ // Runs the run loop until the application quits.
+ void Run();
+
+ // Registers the given Flutter instance for event servicing.
+ void RegisterFlutterInstance(
+ flutter::FlutterEngine* flutter_instance);
+
+ // Unregisters the given Flutter instance from event servicing.
+ void UnregisterFlutterInstance(
+ flutter::FlutterEngine* flutter_instance);
+
+ private:
+ using TimePoint = std::chrono::steady_clock::time_point;
+
+ // Processes all currently pending messages for registered Flutter instances.
+ TimePoint ProcessFlutterMessages();
+
+ std::set flutter_instances_;
+};
+
+#endif // RUNNER_RUN_LOOP_H_
diff --git a/example/windows/runner/runner.exe.manifest b/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+
+
+
+
+ PerMonitorV2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/windows/runner/utils.cpp b/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..d19bdbb
--- /dev/null
+++ b/example/windows/runner/utils.cpp
@@ -0,0 +1,64 @@
+#include "utils.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+void CreateAndAttachConsole() {
+ if (::AllocConsole()) {
+ FILE *unused;
+ if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+ _dup2(_fileno(stdout), 1);
+ }
+ if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+ _dup2(_fileno(stdout), 2);
+ }
+ std::ios::sync_with_stdio();
+ FlutterDesktopResyncOutputStreams();
+ }
+}
+
+std::vector GetCommandLineArguments() {
+ // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+ int argc;
+ wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+ if (argv == nullptr) {
+ return std::vector();
+ }
+
+ std::vector command_line_arguments;
+
+ // Skip the first argument as it's the binary name.
+ for (int i = 1; i < argc; i++) {
+ command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+ }
+
+ ::LocalFree(argv);
+
+ return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+ if (utf16_string == nullptr) {
+ return std::string();
+ }
+ int target_length = ::WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+ -1, nullptr, 0, nullptr, nullptr);
+ if (target_length == 0) {
+ return std::string();
+ }
+ std::string utf8_string;
+ utf8_string.resize(target_length);
+ int converted_length = ::WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+ -1, utf8_string.data(),
+ target_length, nullptr, nullptr);
+ if (converted_length == 0) {
+ return std::string();
+ }
+ return utf8_string;
+}
diff --git a/example/windows/runner/utils.h b/example/windows/runner/utils.h
new file mode 100644
index 0000000..3879d54
--- /dev/null
+++ b/example/windows/runner/utils.h
@@ -0,0 +1,19 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include
+#include
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector,
+// encoded in UTF-8. Returns an empty std::vector on failure.
+std::vector GetCommandLineArguments();
+
+#endif // RUNNER_UTILS_H_
diff --git a/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c10f08d
--- /dev/null
+++ b/example/windows/runner/win32_window.cpp
@@ -0,0 +1,245 @@
+#include "win32_window.h"
+
+#include
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+ return static_cast(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+ HMODULE user32_module = LoadLibraryA("User32.dll");
+ if (!user32_module) {
+ return;
+ }
+ auto enable_non_client_dpi_scaling =
+ reinterpret_cast(
+ GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+ if (enable_non_client_dpi_scaling != nullptr) {
+ enable_non_client_dpi_scaling(hwnd);
+ FreeLibrary(user32_module);
+ }
+}
+
+} // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+ ~WindowClassRegistrar() = default;
+
+ // Returns the singleton registar instance.
+ static WindowClassRegistrar* GetInstance() {
+ if (!instance_) {
+ instance_ = new WindowClassRegistrar();
+ }
+ return instance_;
+ }
+
+ // Returns the name of the window class, registering the class if it hasn't
+ // previously been registered.
+ const wchar_t* GetWindowClass();
+
+ // Unregisters the window class. Should only be called if there are no
+ // instances of the window.
+ void UnregisterWindowClass();
+
+ private:
+ WindowClassRegistrar() = default;
+
+ static WindowClassRegistrar* instance_;
+
+ bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+ if (!class_registered_) {
+ WNDCLASS window_class{};
+ window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ window_class.lpszClassName = kWindowClassName;
+ window_class.style = CS_HREDRAW | CS_VREDRAW;
+ window_class.cbClsExtra = 0;
+ window_class.cbWndExtra = 0;
+ window_class.hInstance = GetModuleHandle(nullptr);
+ window_class.hIcon =
+ LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+ window_class.hbrBackground = 0;
+ window_class.lpszMenuName = nullptr;
+ window_class.lpfnWndProc = Win32Window::WndProc;
+ RegisterClass(&window_class);
+ class_registered_ = true;
+ }
+ return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+ UnregisterClass(kWindowClassName, nullptr);
+ class_registered_ = false;
+}
+
+Win32Window::Win32Window() {
+ ++g_active_window_count;
+}
+
+Win32Window::~Win32Window() {
+ --g_active_window_count;
+ Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title,
+ const Point& origin,
+ const Size& size) {
+ Destroy();
+
+ const wchar_t* window_class =
+ WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+ const POINT target_point = {static_cast(origin.x),
+ static_cast(origin.y)};
+ HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+ UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+ double scale_factor = dpi / 96.0;
+
+ HWND window = CreateWindow(
+ window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+ Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+ Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+ nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+ if (!window) {
+ return false;
+ }
+
+ return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ if (message == WM_NCCREATE) {
+ auto window_struct = reinterpret_cast(lparam);
+ SetWindowLongPtr(window, GWLP_USERDATA,
+ reinterpret_cast(window_struct->lpCreateParams));
+
+ auto that = static_cast(window_struct->lpCreateParams);
+ EnableFullDpiSupportIfAvailable(window);
+ that->window_handle_ = window;
+ } else if (Win32Window* that = GetThisFromHandle(window)) {
+ return that->MessageHandler(window, message, wparam, lparam);
+ }
+
+ return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ switch (message) {
+ case WM_DESTROY:
+ window_handle_ = nullptr;
+ Destroy();
+ if (quit_on_close_) {
+ PostQuitMessage(0);
+ }
+ return 0;
+
+ case WM_DPICHANGED: {
+ auto newRectSize = reinterpret_cast(lparam);
+ LONG newWidth = newRectSize->right - newRectSize->left;
+ LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+ SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+ newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+ return 0;
+ }
+ case WM_SIZE: {
+ RECT rect = GetClientArea();
+ if (child_content_ != nullptr) {
+ // Size and position the child window.
+ MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+ rect.bottom - rect.top, TRUE);
+ }
+ return 0;
+ }
+
+ case WM_ACTIVATE:
+ if (child_content_ != nullptr) {
+ SetFocus(child_content_);
+ }
+ return 0;
+ }
+
+ return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+ OnDestroy();
+
+ if (window_handle_) {
+ DestroyWindow(window_handle_);
+ window_handle_ = nullptr;
+ }
+ if (g_active_window_count == 0) {
+ WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+ }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+ return reinterpret_cast(
+ GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+ child_content_ = content;
+ SetParent(content, window_handle_);
+ RECT frame = GetClientArea();
+
+ MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+ frame.bottom - frame.top, true);
+
+ SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+ RECT frame;
+ GetClientRect(window_handle_, &frame);
+ return frame;
+}
+
+HWND Win32Window::GetHandle() {
+ return window_handle_;
+}
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+ quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+ // No-op; provided for subclasses.
+ return true;
+}
+
+void Win32Window::OnDestroy() {
+ // No-op; provided for subclasses.
+}
diff --git a/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..17ba431
--- /dev/null
+++ b/example/windows/runner/win32_window.h
@@ -0,0 +1,98 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include
+
+#include
+#include
+#include
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+ struct Point {
+ unsigned int x;
+ unsigned int y;
+ Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+ };
+
+ struct Size {
+ unsigned int width;
+ unsigned int height;
+ Size(unsigned int width, unsigned int height)
+ : width(width), height(height) {}
+ };
+
+ Win32Window();
+ virtual ~Win32Window();
+
+ // Creates and shows a win32 window with |title| and position and size using
+ // |origin| and |size|. New windows are created on the default monitor. Window
+ // sizes are specified to the OS in physical pixels, hence to ensure a
+ // consistent size to will treat the width height passed in to this function
+ // as logical pixels and scale to appropriate for the default monitor. Returns
+ // true if the window was created successfully.
+ bool CreateAndShow(const std::wstring& title,
+ const Point& origin,
+ const Size& size);
+
+ // Release OS resources associated with window.
+ void Destroy();
+
+ // Inserts |content| into the window tree.
+ void SetChildContent(HWND content);
+
+ // Returns the backing Window handle to enable clients to set icon and other
+ // window properties. Returns nullptr if the window has been destroyed.
+ HWND GetHandle();
+
+ // If true, closing this window will quit the application.
+ void SetQuitOnClose(bool quit_on_close);
+
+ // Return a RECT representing the bounds of the current client area.
+ RECT GetClientArea();
+
+ protected:
+ // Processes and route salient window messages for mouse handling,
+ // size change and DPI. Delegates handling of these to member overloads that
+ // inheriting classes can handle.
+ virtual LRESULT MessageHandler(HWND window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Called when CreateAndShow is called, allowing subclass window-related
+ // setup. Subclasses should return false if setup fails.
+ virtual bool OnCreate();
+
+ // Called when Destroy is called.
+ virtual void OnDestroy();
+
+ private:
+ friend class WindowClassRegistrar;
+
+ // OS callback called by message pump. Handles the WM_NCCREATE message which
+ // is passed when the non-client area is being created and enables automatic
+ // non-client DPI scaling so that the non-client area automatically
+ // responsponds to changes in DPI. All other messages are handled by
+ // MessageHandler.
+ static LRESULT CALLBACK WndProc(HWND const window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Retrieves a class instance pointer for |window|
+ static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+ bool quit_on_close_ = false;
+
+ // window handle for top level window.
+ HWND window_handle_ = nullptr;
+
+ // window handle for hosted content.
+ HWND child_content_ = nullptr;
+};
+
+#endif // RUNNER_WIN32_WINDOW_H_
diff --git a/example/windows/scripts/bundle_assets_and_deps.bat b/example/windows/scripts/bundle_assets_and_deps.bat
deleted file mode 100644
index 7b5b1d8..0000000
--- a/example/windows/scripts/bundle_assets_and_deps.bat
+++ /dev/null
@@ -1,43 +0,0 @@
-:: Copyright 2018 Google LLC
-::
-:: 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.
-@echo off
-
-set FLUTTER_CACHE_DIR=%~1
-set BUNDLE_DIR=%~2
-set EXE_NAME=%~3
-
-set DATA_DIR=%BUNDLE_DIR%data
-
-if not exist "%DATA_DIR%" call mkdir "%DATA_DIR%"
-if %errorlevel% neq 0 exit /b %errorlevel%
-
-:: Write the executable name to the location expected by the Flutter tool.
-echo %EXE_NAME%>"%FLUTTER_CACHE_DIR%exe_filename"
-
-:: Copy the Flutter assets to the data directory.
-set FLUTTER_APP_DIR=%~dp0..\..
-set ASSET_DIR_NAME=flutter_assets
-set TARGET_ASSET_DIR=%DATA_DIR%\%ASSET_DIR_NAME%
-if exist "%TARGET_ASSET_DIR%" call rmdir /s /q "%TARGET_ASSET_DIR%"
-if %errorlevel% neq 0 exit /b %errorlevel%
-call xcopy /s /e /i /q "%FLUTTER_APP_DIR%\build\%ASSET_DIR_NAME%" "%TARGET_ASSET_DIR%"
-if %errorlevel% neq 0 exit /b %errorlevel%
-
-:: Copy the icudtl.dat file from the Flutter tree to the data directory.
-call xcopy /y /d /q "%FLUTTER_CACHE_DIR%icudtl.dat" "%DATA_DIR%"
-if %errorlevel% neq 0 exit /b %errorlevel%
-
-:: Copy the Flutter DLL to the target location.
-call xcopy /y /d /q "%FLUTTER_CACHE_DIR%flutter_windows.dll" "%BUNDLE_DIR%"
-if %errorlevel% neq 0 exit /b %errorlevel%
diff --git a/example/windows/scripts/prepare_dependencies.bat b/example/windows/scripts/prepare_dependencies.bat
deleted file mode 100644
index 0a852ec..0000000
--- a/example/windows/scripts/prepare_dependencies.bat
+++ /dev/null
@@ -1,17 +0,0 @@
-:: Copyright 2018 Google LLC
-::
-:: 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.
-@echo off
-
-set BUILD_MODE=%~1
-"%FLUTTER_ROOT%\packages\flutter_tools\bin\tool_backend" windows-x64 %BUILD_MODE%
diff --git a/lib/src/notification/notification.dart b/lib/src/notification/notification.dart
index 48a82c3..42f8f38 100644
--- a/lib/src/notification/notification.dart
+++ b/lib/src/notification/notification.dart
@@ -9,12 +9,12 @@ class TopSlideNotification extends StatelessWidget {
final double progress;
- const TopSlideNotification({Key key, @required this.builder, this.progress}) : super(key: key);
+ const TopSlideNotification({Key? key, required this.builder, required this.progress}) : super(key: key);
@override
Widget build(BuildContext context) {
return FractionalTranslation(
- translation: Offset.lerp(const Offset(0, -1), const Offset(0, 0), progress),
+ translation: Offset.lerp(const Offset(0, -1), const Offset(0, 0), progress)!,
child: builder(context),
);
}
@@ -27,12 +27,12 @@ class BottomSlideNotification extends StatelessWidget {
final double progress;
- const BottomSlideNotification({Key key, @required this.builder, this.progress}) : super(key: key);
+ const BottomSlideNotification({Key? key, required this.builder,required this.progress}) : super(key: key);
@override
Widget build(BuildContext context) {
return FractionalTranslation(
- translation: Offset.lerp(const Offset(0, 1), const Offset(0, 0), progress),
+ translation: Offset.lerp(const Offset(0, 1), const Offset(0, 0), progress)!,
child: builder(context),
);
}
@@ -45,9 +45,9 @@ class SlideDismissible extends StatelessWidget {
final bool enable;
const SlideDismissible({
- @required Key key,
- @required this.child,
- @required this.enable,
+ required Key key,
+ required this.child,
+ required this.enable,
}) : super(key: key);
@override
@@ -55,9 +55,9 @@ class SlideDismissible extends StatelessWidget {
if (!enable) return child;
return Dismissible(
child: child,
- key: key,
+ key: key!,
onDismissed: (direction) {
- OverlaySupportEntry.of(context).dismiss(animate: false);
+ OverlaySupportEntry.of(context)!.dismiss(animate: false);
},
);
}
diff --git a/lib/src/notification/overlay_notification.dart b/lib/src/notification/overlay_notification.dart
index 295f8a2..3f0e48f 100644
--- a/lib/src/notification/overlay_notification.dart
+++ b/lib/src/notification/overlay_notification.dart
@@ -14,8 +14,8 @@ import 'package:overlay_support/src/overlay.dart';
///
OverlaySupportEntry showOverlayNotification(
WidgetBuilder builder, {
- Duration duration,
- Key key,
+ Duration? duration,
+ Key? key,
NotificationPosition position = NotificationPosition.top,
}) {
if (duration == null) {
@@ -55,15 +55,15 @@ OverlaySupportEntry showOverlayNotification(
/// can be [NotificationPosition.top] or [NotificationPosition.bottom].
///
OverlaySupportEntry showSimpleNotification(Widget content,
- {Widget leading,
- Widget subtitle,
- Widget trailing,
- EdgeInsetsGeometry contentPadding,
- Color background,
- Color foreground,
+ {Widget? leading,
+ Widget? subtitle,
+ Widget? trailing,
+ EdgeInsetsGeometry? contentPadding,
+ Color? background,
+ Color? foreground,
double elevation = 16,
- Duration duration,
- Key key,
+ Duration? duration,
+ Key? key,
bool autoDismiss = true,
bool slideDismiss = false,
NotificationPosition position = NotificationPosition.top}) {
@@ -73,14 +73,14 @@ OverlaySupportEntry showSimpleNotification(Widget content,
enable: slideDismiss,
key: ValueKey(key),
child: Material(
- color: background ?? Theme.of(context)?.accentColor,
+ color: background ?? Theme.of(context).accentColor,
elevation: elevation,
child: SafeArea(
bottom: position == NotificationPosition.bottom,
top: position == NotificationPosition.top,
child: ListTileTheme(
- textColor: foreground ?? Theme.of(context)?.accentTextTheme?.headline6?.color,
- iconColor: foreground ?? Theme.of(context)?.accentTextTheme?.headline6?.color,
+ textColor: foreground ?? Theme.of(context).accentTextTheme.headline6?.color,
+ iconColor: foreground ?? Theme.of(context).accentTextTheme.headline6?.color,
child: ListTile(
leading: leading,
title: content,
diff --git a/lib/src/overlay.dart b/lib/src/overlay.dart
index d4313c5..7781861 100644
--- a/lib/src/overlay.dart
+++ b/lib/src/overlay.dart
@@ -49,16 +49,14 @@ typedef Widget AnimatedOverlayWidgetBuilder(BuildContext context, double progres
///
OverlaySupportEntry showOverlay(
AnimatedOverlayWidgetBuilder builder, {
- Curve curve,
- Duration duration,
- Key key,
+ Curve? curve,
+ Duration? duration,
+ Key? key,
}) {
assert(key is! GlobalKey);
assert(_debugInitialized, 'OverlaySupport Not Initialized ! \nensure your app wrapped widget OverlaySupport');
- duration ??= kNotificationDuration;
-
- final OverlayState overlay = _overlayState;
+ final OverlayState? overlay = _overlayState;
if (overlay == null) {
assert(() {
debugPrint('overlay not available, dispose this call : $key');
@@ -69,19 +67,19 @@ OverlaySupportEntry showOverlay(
final overlayKey = _OverlayKey(key);
- final supportEntry = OverlaySupportEntry._entries[overlayKey];
- if (supportEntry != null && key is ModalKey) {
+ final oldSupportEntry = _OverlaySupportEntryImpl._entries[overlayKey];
+ if (oldSupportEntry != null && key is ModalKey) {
// Do nothing for modal key if there be a OverlayEntry hold the same model key
// and it is showing.
- return supportEntry;
+ return oldSupportEntry;
}
final dismissImmediately = key is TransientKey;
// If we got a showing overlay with [key], we should dismiss it before showing a new.
- supportEntry?.dismiss(animate: !dismissImmediately);
+ oldSupportEntry?.dismiss(animate: !dismissImmediately);
final stateKey = GlobalKey<_AnimatedOverlayState>();
- OverlaySupportEntry entry = OverlaySupportEntry(OverlayEntry(builder: (context) {
+ final OverlayEntry entry = OverlayEntry(builder: (context) {
return _KeyedOverlay(
key: overlayKey,
child: _AnimatedOverlay(
@@ -89,28 +87,27 @@ OverlaySupportEntry showOverlay(
builder: builder,
curve: curve,
animationDuration: kNotificationSlideDuration,
- duration: duration,
+ duration: duration ?? kNotificationDuration,
),
);
- }), overlayKey, stateKey);
-
- overlay.insert(entry._entry);
-
- return entry;
+ });
+ final OverlaySupportEntry supportEntry = OverlaySupportEntry.create(entry, overlayKey, stateKey);
+ overlay.insert(entry);
+ return supportEntry;
}
final GlobalKey<_OverlayFinderState> _keyFinder = GlobalKey(debugLabel: 'overlay_support');
-OverlayState get _overlayState {
+OverlayState? get _overlayState {
final context = _keyFinder.currentContext;
if (context == null) return null;
- NavigatorState navigator;
+ NavigatorState? navigator;
void visitor(Element element) {
if (navigator != null) return;
if (element.widget is Navigator) {
- navigator = (element as StatefulElement).state;
+ navigator = (element as StatefulElement).state as NavigatorState?;
} else {
element.visitChildElements(visitor);
}
@@ -130,7 +127,7 @@ OverlayState get _overlayState {
)
''');
- return navigator.overlay;
+ return navigator?.overlay;
}
bool _debugInitialized = false;
@@ -138,9 +135,9 @@ bool _debugInitialized = false;
class OverlaySupport extends StatelessWidget {
final Widget child;
- final ToastThemeData toastTheme;
+ final ToastThemeData? toastTheme;
- const OverlaySupport({Key key, @required this.child, this.toastTheme}) : super(key: key);
+ const OverlaySupport({Key? key, required this.child, this.toastTheme}) : super(key: key);
@override
StatelessElement createElement() {
@@ -167,7 +164,7 @@ class OverlaySupport extends StatelessWidget {
class _OverlayFinder extends StatefulWidget {
final Widget child;
- const _OverlayFinder({Key key, this.child}) : super(key: key);
+ const _OverlayFinder({Key? key, required this.child}) : super(key: key);
@override
_OverlayFinderState createState() => _OverlayFinderState();
diff --git a/lib/src/overlay_animation.dart b/lib/src/overlay_animation.dart
index adc777b..10cf2c7 100644
--- a/lib/src/overlay_animation.dart
+++ b/lib/src/overlay_animation.dart
@@ -13,15 +13,10 @@ class _AnimatedOverlay extends StatefulWidget {
final Curve curve;
_AnimatedOverlay(
- {@required Key key,
- @required this.animationDuration,
- Curve curve,
- @required this.builder,
- @required this.duration})
+ {required Key key, required this.animationDuration, Curve? curve, required this.builder, required this.duration})
: curve = curve ?? Curves.easeInOut,
- assert(animationDuration != null && animationDuration >= Duration.zero),
- assert(duration != null && duration >= Duration.zero),
- assert(builder != null),
+ assert(animationDuration >= Duration.zero),
+ assert(duration >= Duration.zero),
super(key: key);
@override
@@ -29,24 +24,24 @@ class _AnimatedOverlay extends StatefulWidget {
}
class _AnimatedOverlayState extends State<_AnimatedOverlay> with TickerProviderStateMixin {
- AnimationController _controller;
+ AnimationController? _controller;
- CancelableOperation _autoHideOperation;
+ CancelableOperation? _autoHideOperation;
void show() {
_autoHideOperation?.cancel();
- _controller.forward(from: _controller.value);
+ _controller!.forward(from: _controller!.value);
}
///
/// [immediately] True to dismiss notification immediately.
///
Future hide({bool immediately = false}) async {
- if (!immediately && !_controller.isDismissed && _controller.status == AnimationStatus.forward) {
- await _controller.forward(from: _controller.value);
+ if (!immediately && !_controller!.isDismissed && _controller!.status == AnimationStatus.forward) {
+ await _controller!.forward(from: _controller!.value);
}
_autoHideOperation?.cancel();
- await _controller.reverse(from: _controller.value);
+ await _controller!.reverse(from: _controller!.value);
}
@override
@@ -54,11 +49,12 @@ class _AnimatedOverlayState extends State<_AnimatedOverlay> with TickerProviderS
_controller = AnimationController(
vsync: this, duration: widget.animationDuration, debugLabel: 'AnimatedOverlayShowHideAnimation');
super.initState();
- _controller.addStatusListener((status) {
+ _controller!.addStatusListener((status) {
if (status == AnimationStatus.dismissed) {
- OverlaySupportEntry._entriesGlobal[widget.key].dismiss(animate: false);
+ _OverlaySupportEntryImpl._entriesGlobal[widget.key as GlobalKey>]!
+ .dismiss(animate: false);
} else if (status == AnimationStatus.completed) {
- if (widget.duration > Duration.zero) {
+ if (widget.duration> Duration.zero) {
_autoHideOperation = CancelableOperation.fromFuture(Future.delayed(widget.duration))
..value.whenComplete(() {
hide();
@@ -79,9 +75,9 @@ class _AnimatedOverlayState extends State<_AnimatedOverlay> with TickerProviderS
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
- animation: _controller,
+ animation: _controller!,
builder: (context, _) {
- return widget.builder(context, widget.curve.transform(_controller.value));
+ return widget.builder(context, widget.curve.transform(_controller!.value));
});
}
}
diff --git a/lib/src/overlay_entry.dart b/lib/src/overlay_entry.dart
index 17b1e7d..950c6ab 100644
--- a/lib/src/overlay_entry.dart
+++ b/lib/src/overlay_entry.dart
@@ -1,24 +1,19 @@
part of 'overlay.dart';
-///
-/// [OverlaySupportEntry] represent a overlay popup by [showOverlay].
-///
-/// Provide function [dismiss] to dismiss a Notification/Overlay.
-///
-class OverlaySupportEntry {
- static final _entries = HashMap<_OverlayKey, OverlaySupportEntry>();
- static final _entriesGlobal = HashMap();
+abstract class OverlaySupportEntry {
+ factory OverlaySupportEntry.create(OverlayEntry entry, _OverlayKey key, GlobalKey<_AnimatedOverlayState> stateKey) {
+ return _OverlaySupportEntryImpl._(entry, key, stateKey);
+ }
- final OverlayEntry _entry;
- final _OverlayKey _key;
- final GlobalKey<_AnimatedOverlayState> _stateKey;
+ factory OverlaySupportEntry.empty() {
+ return _OverlaySupportEntryEmpty();
+ }
/// Find OverlaySupportEntry by [context].
///
/// The [context] should be the BuildContext which build a element in Notification.
///
- /// deprecated parameter `requireForDebug` because it is useless.
- static OverlaySupportEntry of(BuildContext context, {@deprecated Widget requireForDebug}) {
+ static OverlaySupportEntry? of(BuildContext context) {
final animatedOverlay = context.findAncestorWidgetOfExactType<_AnimatedOverlay>();
assert(() {
if (animatedOverlay == null) {
@@ -29,16 +24,37 @@ class OverlaySupportEntry {
return true;
}());
- final key = animatedOverlay.key;
+ final key = animatedOverlay!.key!;
assert(key is GlobalKey);
- return OverlaySupportEntry._entriesGlobal[key];
+ return _OverlaySupportEntryImpl._entriesGlobal[key as GlobalKey>];
}
- OverlaySupportEntry(this._entry, this._key, this._stateKey) {
+ /// Dismiss the Overlay which associated with this entry.
+ /// If [animate] is false , remove entry immediately.
+ /// If [animate] is true, remove entry after [_AnimatedOverlayState.hide]
+ void dismiss({bool animate = true});
+
+ Future get dismissed;
+}
+
+///
+/// [OverlaySupportEntry] represent a overlay popup by [showOverlay].
+///
+/// Provide function [dismiss] to dismiss a Notification/Overlay.
+///
+class _OverlaySupportEntryImpl implements OverlaySupportEntry {
+ static final _entries = HashMap<_OverlayKey, OverlaySupportEntry>();
+ static final _entriesGlobal = HashMap();
+
+ final OverlayEntry _entry;
+ final _OverlayKey _key;
+ final GlobalKey<_AnimatedOverlayState> _stateKey;
+
+ _OverlaySupportEntryImpl._(this._entry, this._key, this._stateKey) {
assert(() {
if (_entries[_key] != null) {
- throw FlutterError('there still a OverlaySupportEntry associactd with $_key');
+ throw FlutterError('there still a OverlaySupportEntry associated with $_key');
}
return true;
}());
@@ -46,13 +62,10 @@ class OverlaySupportEntry {
_entriesGlobal[_stateKey] = this;
}
- factory OverlaySupportEntry.empty() {
- return _OverlaySupportEntryEmpty();
- }
-
// To known when notification has been dismissed.
final Completer _dismissedCompleter = Completer();
+ @override
Future get dismissed => _dismissedCompleter.future;
// OverlayEntry has been scheduled to dismiss,
@@ -62,9 +75,7 @@ class OverlaySupportEntry {
// OverlayEntry has been removed from Overlay
bool _dismissed = false;
- /// Dismiss the Overlay which associated with this entry.
- /// If [animate] is false , remove entry immediately.
- /// If [animate] is true, remove entry after [_AnimatedOverlayState.hide]
+ @override
void dismiss({bool animate = true}) {
if (_dismissed || (_dismissScheduled && animate)) {
return;
@@ -83,13 +94,13 @@ class OverlaySupportEntry {
void animateRemove() {
if (_stateKey.currentState != null) {
- _stateKey.currentState.hide().whenComplete(() {
+ _stateKey.currentState!.hide().whenComplete(() {
_dismissEntry();
});
} else {
//we need show animation before remove this entry
//so need ensure entry has been inserted into screen
- WidgetsBinding.instance.scheduleFrameCallback((_) => animateRemove());
+ WidgetsBinding.instance?.scheduleFrameCallback((_) => animateRemove());
}
}
@@ -110,30 +121,9 @@ class OverlaySupportEntry {
}
class _OverlaySupportEntryEmpty implements OverlaySupportEntry {
- @override
- void _dismissEntry() {}
-
- @override
- Completer get _dismissedCompleter => null;
-
- @override
- OverlayEntry get _entry => null;
-
- @override
- _OverlayKey get _key => null;
-
- @override
- GlobalKey<_AnimatedOverlayState> get _stateKey => null;
-
@override
void dismiss({bool animate = true}) {}
@override
Future get dismissed => Future.value(null);
-
- @override
- bool _dismissScheduled = true;
-
- @override
- bool _dismissed = true;
}
diff --git a/lib/src/overlay_key.dart b/lib/src/overlay_key.dart
index c683636..28a68e6 100644
--- a/lib/src/overlay_key.dart
+++ b/lib/src/overlay_key.dart
@@ -5,7 +5,7 @@ part of 'overlay.dart';
class _KeyedOverlay extends StatelessWidget {
final Widget child;
- const _KeyedOverlay({_OverlayKey key, this.child}) : super(key: key);
+ const _KeyedOverlay({required _OverlayKey key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -33,7 +33,7 @@ class ModalKey extends ValueKey {
@override
bool operator ==(other) {
if (other.runtimeType != runtimeType) return false;
- final ModalKey typedOther = other;
+ final ModalKey typedOther = other as ModalKey;
return value == typedOther.value;
}
@@ -42,7 +42,7 @@ class ModalKey extends ValueKey {
}
class _OverlayKey extends ValueKey {
- _OverlayKey(Key key) : super(key ?? UniqueKey());
+ _OverlayKey(Key? key) : super(key ?? UniqueKey());
}
///
@@ -69,7 +69,7 @@ class TransientKey extends ValueKey {
@override
bool operator ==(other) {
if (other.runtimeType != runtimeType) return false;
- final TransientKey typedOther = other;
+ final TransientKey typedOther = other as TransientKey;
return value == typedOther.value;
}
diff --git a/lib/src/theme.dart b/lib/src/theme.dart
index 336f5f9..b3acbac 100644
--- a/lib/src/theme.dart
+++ b/lib/src/theme.dart
@@ -9,15 +9,15 @@ class ToastThemeData {
final Alignment alignment;
const ToastThemeData.raw({
- @required this.textColor,
- @required this.background,
- @required this.alignment,
+ required this.textColor,
+ required this.background,
+ required this.alignment,
});
factory ToastThemeData({
- Color textColor,
- Color background,
- Alignment alignment,
+ Color? textColor,
+ Color? background,
+ Alignment? alignment,
}) {
return ToastThemeData.raw(
textColor: textColor ?? Colors.black87,
@@ -42,15 +42,13 @@ class OverlaySupportTheme extends InheritedWidget {
final ToastThemeData toastTheme;
const OverlaySupportTheme({
- Key key,
- @required Widget child,
- @required this.toastTheme,
- }) : assert(child != null),
- assert(toastTheme != null),
- super(key: key, child: child);
+ Key? key,
+ required Widget child,
+ required this.toastTheme,
+ }) : super(key: key, child: child);
static OverlaySupportTheme of(BuildContext context) {
- final OverlaySupportTheme theme = context.dependOnInheritedWidgetOfExactType();
+ final OverlaySupportTheme? theme = context.dependOnInheritedWidgetOfExactType();
if (theme == null) {
throw FlutterError("Can not find OverlaySupportTheme.");
}
diff --git a/lib/src/toast/toast.dart b/lib/src/toast/toast.dart
index fceaa4a..a4ddaf0 100644
--- a/lib/src/toast/toast.dart
+++ b/lib/src/toast/toast.dart
@@ -33,7 +33,7 @@ void toast(String message, {Duration duration = Toast.LENGTH_SHORT}) {
class _Toast extends StatelessWidget {
final Widget content;
- const _Toast({Key key, this.content}) : super(key: key);
+ const _Toast({Key? key, required this.content}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -51,7 +51,7 @@ class _Toast extends StatelessWidget {
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
- color: toastTheme?.background,
+ color: toastTheme.background,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: content,
),
diff --git a/pubspec.yaml b/pubspec.yaml
index 2ad8eb1..eb10387 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,16 +1,16 @@
name: overlay_support
description: proivder support for overlay, easy to build toast and internal notification
-version: 1.0.5
+version: 1.1.0-nullsafety.0
author: YangBin
homepage: https://github.com/boyan01/overlay_support
environment:
- sdk: ">=2.1.0 <3.0.0"
+ sdk: '>=2.12.0-0 <3.0.0'
dependencies:
flutter:
sdk: flutter
- async: ^2.1.0
+ async: ^2.5.0-nullsafety.3
dev_dependencies:
flutter_test:
diff --git a/test/overlay_support_test.dart b/test/overlay_support_test.dart
index 52dfb32..e062cbc 100644
--- a/test/overlay_support_test.dart
+++ b/test/overlay_support_test.dart
@@ -66,7 +66,7 @@ void main() {
});
testWidgets('notification hide manually', (tester) async {
- OverlaySupportEntry entry;
+ OverlaySupportEntry? entry;
await tester.pumpWidget(_FakeOverlay(child: Builder(builder: (context) {
return FlatButton(
onPressed: () {
@@ -81,7 +81,7 @@ void main() {
assert(entry != null, 'entry has been inited by tap event');
expect(find.text('message'), findsOneWidget);
- entry.dismiss(animate: false);
+ entry!.dismiss(animate: false);
await tester.pump();
//already hidden
@@ -227,7 +227,7 @@ void main() {
group('overlay support entry', () {
testWidgets('find OverlaySupportEntry by context', (tester) async {
- OverlaySupportEntry entry;
+ OverlaySupportEntry? entry;
await tester.pumpWidget(_FakeOverlay(child: Builder(builder: (context) {
return FlatButton(
onPressed: () {
@@ -254,7 +254,7 @@ void main() {
});
testWidgets('overlay support entry dimissed', (tester) async {
- OverlaySupportEntry entry;
+ OverlaySupportEntry? entry;
await tester.pumpWidget(_FakeOverlay(child: Builder(builder: (context) {
return FlatButton(
onPressed: () {
@@ -268,7 +268,7 @@ void main() {
assert(entry != null);
bool dismissed = false;
- entry.dismissed.whenComplete(() {
+ entry!.dismissed.whenComplete(() {
dismissed = true;
});
await tester.pump(Duration(milliseconds: 100));
@@ -280,7 +280,7 @@ void main() {
class _FakeOverlay extends StatelessWidget {
final Widget child;
- const _FakeOverlay({Key key, @required this.child}) : super(key: key);
+ const _FakeOverlay({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {