Fix feed tag search and prepare 2.0.1+22

This commit is contained in:
2026-06-03 13:10:57 +03:00
parent af62ddf0da
commit f0bf2ea515
46 changed files with 4262 additions and 4154 deletions
+5 -11
View File
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "ff37bef603469fb030f2b72995ab929ccfc227f0" revision: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42"
channel: "stable" channel: "stable"
project_type: app project_type: app
@@ -13,17 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
- platform: android
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
- platform: linux - platform: linux
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
- platform: windows
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
# User provided section # User provided section
+1 -1
View File
@@ -101,4 +101,4 @@ ISCC.exe installer\Lunaris.iss
- Приложение уважает API и rate limits сайтов. - Приложение уважает API и rate limits сайтов.
- NSFW/blur/blacklist настраиваются локально. - NSFW/blur/blacklist настраиваются локально.
- API keys/custom headers хранятся локально и экспортируются только через явный export settings. - API keys/custom headers хранятся локально и экспортируются только через явный export settings.
- Локальные пути скачанных файлов не попадают в JSON export/import. - Локальные пути скачанных файлов не попадают в JSON export/import.
+8 -8
View File
@@ -1,11 +1,11 @@
/.gradle /.gradle
/captures/ /captures/
/local.properties /local.properties
GeneratedPluginRegistrant.java GeneratedPluginRegistrant.java
.cxx/ .cxx/
# Remember to never publicly share your keystore. # Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore # See https://flutter.dev/to/reference-keystore
key.properties key.properties
**/*.keystore **/*.keystore
**/*.jks **/*.jks
+41 -41
View File
@@ -1,44 +1,44 @@
plugins { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin") id("dev.flutter.flutter-gradle-plugin")
} }
android { android {
namespace = "com.example.gel_rule_app" namespace = "com.example.gel_rule_app"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString() jvmTarget = JavaVersion.VERSION_17.toString()
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.gel_rule_app" applicationId = "com.example.gel_rule_app"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
} }
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. // TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works. // Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
} }
} }
} }
flutter { flutter {
source = "../.." source = "../.."
} }
+7 -7
View File
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>
+38 -38
View File
@@ -4,32 +4,32 @@
<application <application
android:label="Lunaris" android:label="Lunaris"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity="" android:taskAffinity=""
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. --> to determine the Window background behind the Flutter UI. -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
/> />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
@@ -43,15 +43,15 @@
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT. https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.PROCESS_TEXT"/> <action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain"/>
</intent> </intent>
</queries> </queries>
</manifest> </manifest>
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/launch_background" /> <item android:drawable="@color/launch_background" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<!-- <item> <!-- <item>
<bitmap <bitmap
android:gravity="center" android:gravity="center"
android:src="@mipmap/launch_image" /> android:src="@mipmap/launch_image" />
</item> --> </item> -->
</layer-list> </layer-list>
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/launch_background" /> <item android:drawable="@color/launch_background" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<!-- <item> <!-- <item>
<bitmap <bitmap
android:gravity="center" android:gravity="center"
android:src="@mipmap/launch_image" /> android:src="@mipmap/launch_image" />
</item> --> </item> -->
</layer-list> </layer-list>
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> <!-- 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"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its Flutter UI initializes, as well as behind your Flutter UI while its
running. running.
This Theme is only used starting with V2 of Flutter's Android embedding. --> This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
</resources> </resources>
+13 -13
View File
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <!-- 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.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its Flutter UI initializes, as well as behind your Flutter UI while its
running. running.
This Theme is only used starting with V2 of Flutter's Android embedding. --> This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.NoTitleBar">
<item name="android:windowBackground">#101114</item> <item name="android:windowBackground">#101114</item>
</style> </style>
+7 -7
View File
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>
+13 -13
View File
@@ -4,19 +4,19 @@ allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
} }
val newBuildDir: Directory = val newBuildDir: Directory =
rootProject.layout.buildDirectory rootProject.layout.buildDirectory
.dir("../../build") .dir("../../build")
.get() .get()
rootProject.layout.buildDirectory.value(newBuildDir) rootProject.layout.buildDirectory.value(newBuildDir)
subprojects { subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir) project.layout.buildDirectory.value(newSubprojectBuildDir)
} }
subprojects { subprojects {
project.evaluationDependsOn(":app") project.evaluationDependsOn(":app")
} }
+4
View File
@@ -1,2 +1,6 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
+5 -5
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
+90 -90
View File
@@ -1,90 +1,90 @@
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto init
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto init
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windowz variants @rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
set CMD_LINE_ARGS= set CMD_LINE_ARGS=
set _SKIP=2 set _SKIP=2
:win9xME_args_slurp :win9xME_args_slurp
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
goto execute goto execute
:4NT_args :4NT_args
@rem Get arguments from the 4NT Shell from JP Software @rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$ set CMD_LINE_ARGS=%$
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 exit /b 1
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega
+26 -26
View File
@@ -1,26 +1,26 @@
pluginManagement { pluginManagement {
val flutterSdkPath = val flutterSdkPath =
run { run {
val properties = java.util.Properties() val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) } file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk") val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath flutterSdkPath
} }
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
} }
} }
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false
} }
include(":app") include(":app")
+1 -1
View File
@@ -1,3 +1,3 @@
const appDisplayVersion = '2.0.1'; const appDisplayVersion = '2.0.1';
const appBuildNumber = 21; const appBuildNumber = 22;
const appReleaseChannel = 'Stable'; const appReleaseChannel = 'Stable';
+44 -12
View File
@@ -32,7 +32,7 @@ class _FeedScreenState extends ConsumerState<FeedScreen> {
final _scrollController = ScrollController(); final _scrollController = ScrollController();
final Set<String> _selectedKeys = {}; final Set<String> _selectedKeys = {};
bool _selectionMode = false; bool _selectionMode = false;
bool _usedInitialQuery = false; String? _appliedInitialQuery;
Timer? _scrollSaveDebounce; Timer? _scrollSaveDebounce;
@override @override
@@ -70,10 +70,10 @@ class _FeedScreenState extends ConsumerState<FeedScreen> {
if (nextQuery != null && if (nextQuery != null &&
nextQuery.isNotEmpty && nextQuery.isNotEmpty &&
nextQuery != oldWidget.initialQuery) { nextQuery != oldWidget.initialQuery) {
_usedInitialQuery = true; _appliedInitialQuery = nextQuery;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
ref.read(feedControllerProvider.notifier).search(nextQuery); _applySearchQuery(nextQuery);
}); });
} }
} }
@@ -93,10 +93,16 @@ class _FeedScreenState extends ConsumerState<FeedScreen> {
final favoriteKeys = ref.watch(favoriteKeysProvider).value ?? <String>{}; final favoriteKeys = ref.watch(favoriteKeysProvider).value ?? <String>{};
final viewedKeys = ref.watch(viewedKeysProvider).value ?? <String>{}; final viewedKeys = ref.watch(viewedKeysProvider).value ?? <String>{};
if (!_usedInitialQuery && (widget.initialQuery?.isNotEmpty ?? false)) { final initialQuery = widget.initialQuery?.trim();
_usedInitialQuery = true; final currentTags = feed.value?.selectedTags.join(' ');
if (initialQuery != null &&
initialQuery.isNotEmpty &&
_appliedInitialQuery != initialQuery &&
currentTags != initialQuery) {
_appliedInitialQuery = initialQuery;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(feedControllerProvider.notifier).search(widget.initialQuery!); if (!mounted) return;
_applySearchQuery(initialQuery);
}); });
} }
@@ -159,17 +165,14 @@ class _FeedScreenState extends ConsumerState<FeedScreen> {
onSearchChanged: (query) => ref onSearchChanged: (query) => ref
.read(feedControllerProvider.notifier) .read(feedControllerProvider.notifier)
.updateTagSuggestions(query), .updateTagSuggestions(query),
onSuggestionTap: (query) => onSuggestionTap: _submitSearch,
ref.read(feedControllerProvider.notifier).search(query),
onTopPeriodChanged: (period) => ref onTopPeriodChanged: (period) => ref
.read(feedControllerProvider.notifier) .read(feedControllerProvider.notifier)
.setTopPeriod(period), .setTopPeriod(period),
onSearch: (query) => onSearch: _submitSearch,
ref.read(feedControllerProvider.notifier).search(query),
onRefresh: () => onRefresh: () =>
ref.read(feedControllerProvider.notifier).refresh(), ref.read(feedControllerProvider.notifier).refresh(),
onClearFilters: () => onClearFilters: _clearFilters,
ref.read(feedControllerProvider.notifier).clearFilters(),
selectionMode: _selectionMode, selectionMode: _selectionMode,
onToggleSelectionMode: () { onToggleSelectionMode: () {
setState(() { setState(() {
@@ -279,6 +282,35 @@ class _FeedScreenState extends ConsumerState<FeedScreen> {
); );
} }
void _submitSearch(String query) {
final trimmed = query.trim();
if (trimmed.isEmpty) {
_clearFilters();
return;
}
final location = '/?q=${Uri.encodeQueryComponent(trimmed)}';
if (widget.initialQuery?.trim() == trimmed) {
_applySearchQuery(trimmed);
return;
}
context.go(location);
}
Future<void> _applySearchQuery(String query) async {
await ref.read(feedControllerProvider.notifier).search(query);
if (_scrollController.hasClients) {
_scrollController.jumpTo(0);
}
}
Future<void> _clearFilters() async {
_appliedInitialQuery = null;
await ref.read(feedControllerProvider.notifier).clearFilters();
if (mounted && widget.initialQuery != null) {
context.go('/');
}
}
void _toggleSelected(Post post) { void _toggleSelected(Post post) {
setState(() { setState(() {
_selectionMode = true; _selectionMode = true;
@@ -3,33 +3,57 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../backend/backend.dart'; import '../../../backend/backend.dart';
import '../../../core/utils/result.dart'; import '../../../core/utils/result.dart';
// Меняем FutureProvider на StreamProvider
final postDetailsControllerProvider = final postDetailsControllerProvider =
FutureProvider.family<Post?, PostDetailsArgs>((ref, args) async { StreamProvider.family<Post?, PostDetailsArgs>((ref, args) async* {
// Правильно собираем зависимости на самом верхнем уровне (фиксим антипаттерн)
final providerManager = ref.watch(providerManagerProvider);
final cacheService = ref.watch(cacheServiceProvider);
final postRepository = ref.watch(postRepositoryProvider);
// Вспомогательная функция для загрузки тегов
Future<Post?> enrich(Post? post) async { Future<Post?> enrich(Post? post) async {
if (post == null) return null; if (post == null) return null;
final shouldEnrich = post.tagGroups.isEmpty || final shouldEnrich = post.tagGroups.isEmpty ||
(post.tagGroups.length == 1 && post.tagGroups.containsKey('general')); (post.tagGroups.length == 1 && post.tagGroups.containsKey('general'));
if (!shouldEnrich) return post; if (!shouldEnrich) return post;
final enriched =
await ref.watch(providerManagerProvider).enrichPostTags(post); final enriched = await providerManager.enrichPostTags(post);
if (enriched is Success<Post>) { if (enriched is Success<Post>) {
await ref.watch(cacheServiceProvider).cachePosts([enriched.data]); await cacheService.cachePosts([enriched.data]);
return enriched.data; return enriched.data;
} }
return post; return post;
} }
if (args.initialPost != null) return enrich(args.initialPost); // СЦЕНАРИЙ 1: Пост уже передан из ленты (Самый частый случай)
final cached = await ref if (args.initialPost != null) {
.watch(postRepositoryProvider) yield args
.getCachedPost(args.postId, args.providerId); .initialPost; // МГНОВЕННО отдаем пост в UI, экран сразу рендерится!
final enriched = await enrich(args.initialPost);
yield enriched; // Чуть позже отдаем версию с загруженными тегами
return;
}
// СЦЕНАРИЙ 2: Прямой переход (например, по ссылке), проверяем локальный кэш
final cached =
await postRepository.getCachedPost(args.postId, args.providerId);
if (cached is Success<Post?> && cached.data != null) { if (cached is Success<Post?> && cached.data != null) {
return enrich(cached.data); yield cached.data; // Нашли в кэше — сразу показываем контент
final enriched = await enrich(cached.data);
yield enriched; // Догружаем теги на фоне
return;
}
// СЦЕНАРИЙ 3: Полный фоллбэк, если вообще ничего нет — идем в сеть за самим постом
final remote = await providerManager.getPost(args.providerId, args.postId);
if (remote is Success<Post?> && remote.data != null) {
yield remote.data;
final enriched = await enrich(remote.data);
yield enriched;
} else {
yield null;
} }
final remote = await ref
.watch(providerManagerProvider)
.getPost(args.providerId, args.postId);
return remote is Success<Post?> ? enrich(remote.data) : null;
}); });
class PostDetailsArgs { class PostDetailsArgs {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
flutter/ephemeral flutter/ephemeral
+128 -128
View File
@@ -1,128 +1,128 @@
# Project-level configuration. # Project-level configuration.
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX) project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
# the on-disk name of your application. # the on-disk name of your application.
set(BINARY_NAME "gel_rule_app") set(BINARY_NAME "gel_rule_app")
# The unique GTK application identifier for this application. See: # The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID # https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.gel_rule_app") set(APPLICATION_ID "com.example.gel_rule_app")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.
cmake_policy(SET CMP0063 NEW) cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary. # Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building. # Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT) if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif() endif()
# Define build configuration options. # Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE) STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release") "Debug" "Profile" "Release")
endif() endif()
# Compilation settings that should be applied to most targets. # Compilation settings that should be applied to most targets.
# #
# Be cautious about adding new options here, as plugins use this function by # Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead # default. In most cases, you should add new options to specific targets instead
# of modifying this function. # of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET) function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>") target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction() endfunction()
# Flutter library and tool build rules. # Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR}) add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies. # System-level dependencies.
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt. # Application build; see runner/CMakeLists.txt.
add_subdirectory("runner") add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed. # Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble) add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch # Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid # correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of # people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location. # the default top-level location.
set_target_properties(${BINARY_NAME} set_target_properties(${BINARY_NAME}
PROPERTIES PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
) )
# Generated plugin build rules, which manage building the plugins and adding # Generated plugin build rules, which manage building the plugins and adding
# them to the application. # them to the application.
include(flutter/generated_plugins.cmake) include(flutter/generated_plugins.cmake)
# === Installation === # === Installation ===
# By default, "installing" just makes a relocatable bundle in the build # By default, "installing" just makes a relocatable bundle in the build
# directory. # directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif() endif()
# Start with a clean build bundle directory every time. # Start with a clean build bundle directory every time.
install(CODE " install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime) " COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime) COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}" install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
endforeach(bundled_library) endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages. # Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}" install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets") set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE " install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime) " COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only. # Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
endif() endif()
+88 -88
View File
@@ -1,88 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited. # This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool. # Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake) include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See # TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146. # https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...), # Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10. # which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX) function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "") set(NEW_LIST "")
foreach(element ${${LIST_NAME}}) foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}") list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element) endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction() endfunction()
# === Flutter Library === # === Flutter Library ===
# System-level dependencies. # System-level dependencies.
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step. # Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h" "fl_basic_message_channel.h"
"fl_binary_codec.h" "fl_binary_codec.h"
"fl_binary_messenger.h" "fl_binary_messenger.h"
"fl_dart_project.h" "fl_dart_project.h"
"fl_engine.h" "fl_engine.h"
"fl_json_message_codec.h" "fl_json_message_codec.h"
"fl_json_method_codec.h" "fl_json_method_codec.h"
"fl_message_codec.h" "fl_message_codec.h"
"fl_method_call.h" "fl_method_call.h"
"fl_method_channel.h" "fl_method_channel.h"
"fl_method_codec.h" "fl_method_codec.h"
"fl_method_response.h" "fl_method_response.h"
"fl_plugin_registrar.h" "fl_plugin_registrar.h"
"fl_plugin_registry.h" "fl_plugin_registry.h"
"fl_standard_message_codec.h" "fl_standard_message_codec.h"
"fl_standard_method_codec.h" "fl_standard_method_codec.h"
"fl_string_codec.h" "fl_string_codec.h"
"fl_value.h" "fl_value.h"
"fl_view.h" "fl_view.h"
"flutter_linux.h" "flutter_linux.h"
) )
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE) add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}" "${EPHEMERAL_DIR}"
) )
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE target_link_libraries(flutter INTERFACE
PkgConfig::GTK PkgConfig::GTK
PkgConfig::GLIB PkgConfig::GLIB
PkgConfig::GIO PkgConfig::GIO
) )
add_dependencies(flutter flutter_assemble) add_dependencies(flutter flutter_assemble)
# === Flutter tool backend === # === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time, # _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 # since currently there's no way to get a full input/output list from the
# flutter tool. # flutter tool.
add_custom_command( add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_ ${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT} ${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM VERBATIM
) )
add_custom_target(flutter_assemble DEPENDS add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}" "${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS} ${FLUTTER_LIBRARY_HEADERS}
) )
+26 -26
View File
@@ -1,26 +1,26 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX) project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the # Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work. # work.
# #
# Any new source files that you add to the application should be added here. # Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} add_executable(${BINARY_NAME}
"main.cc" "main.cc"
"my_application.cc" "my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
) )
# Apply the standard set of build settings. This can be removed for applications # Apply the standard set of build settings. This can be removed for applications
# that need different build settings. # that need different build settings.
apply_standard_settings(${BINARY_NAME}) apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the application ID. # Add preprocessor definitions for the application ID.
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Add dependency libraries. Add any application-specific dependencies here. # Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+6 -6
View File
@@ -1,6 +1,6 @@
#include "my_application.h" #include "my_application.h"
int main(int argc, char** argv) { int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new(); g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv); return g_application_run(G_APPLICATION(app), argc, argv);
} }
+146 -146
View File
@@ -1,148 +1,148 @@
#include "my_application.h" #include "my_application.h"
#include <flutter_linux/flutter_linux.h> #include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
#endif #endif
#include "flutter/generated_plugin_registrant.h" #include "flutter/generated_plugin_registrant.h"
struct _MyApplication { struct _MyApplication {
GtkApplication parent_instance; GtkApplication parent_instance;
char** dart_entrypoint_arguments; char** dart_entrypoint_arguments;
}; };
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Called when first Flutter frame received. // Called when first Flutter frame received.
static void first_frame_cb(MyApplication* self, FlView* view) { static void first_frame_cb(MyApplication* self, FlView* view) {
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
} }
// Implements GApplication::activate. // Implements GApplication::activate.
static void my_application_activate(GApplication* application) { static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application); MyApplication* self = MY_APPLICATION(application);
GtkWindow* window = GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used // Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu // by applications and is the setup most users will be using (e.g. Ubuntu
// desktop). // desktop).
// If running on X and not using GNOME then just use a traditional title bar // If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling. // in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing // If running on Wayland assume the header bar will work (may need changing
// if future cases occur). // if future cases occur).
gboolean use_header_bar = TRUE; gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window); GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) { if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) { if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE; use_header_bar = FALSE;
} }
} }
#endif #endif
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "Lunaris"); gtk_header_bar_set_title(header_bar, "Lunaris");
gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "Lunaris"); gtk_window_set_title(window, "Lunaris");
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments( fl_dart_project_set_dart_entrypoint_arguments(
project, self->dart_entrypoint_arguments); project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project); FlView* view = fl_view_new(project);
GdkRGBA background_color; GdkRGBA background_color;
// Background defaults to black, override it here if necessary, e.g. #00000000 // Background defaults to black, override it here if necessary, e.g. #00000000
// for transparent. // for transparent.
gdk_rgba_parse(&background_color, "#000000"); gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color); fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view)); gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders. // Show the window when Flutter renders.
// Requires the view to be realized so we can start rendering. // Requires the view to be realized so we can start rendering.
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
self); self);
gtk_widget_realize(GTK_WIDGET(view)); gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view)); gtk_widget_grab_focus(GTK_WIDGET(view));
} }
// Implements GApplication::local_command_line. // Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, static gboolean my_application_local_command_line(GApplication* application,
gchar*** arguments, gchar*** arguments,
int* exit_status) { int* exit_status) {
MyApplication* self = MY_APPLICATION(application); MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name. // Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr; g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) { if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message); g_warning("Failed to register: %s", error->message);
*exit_status = 1; *exit_status = 1;
return TRUE; return TRUE;
} }
g_application_activate(application); g_application_activate(application);
*exit_status = 0; *exit_status = 0;
return TRUE; return TRUE;
} }
// Implements GApplication::startup. // Implements GApplication::startup.
static void my_application_startup(GApplication* application) { static void my_application_startup(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object); // MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup. // Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application); G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
} }
// Implements GApplication::shutdown. // Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) { static void my_application_shutdown(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object); // MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown. // Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
} }
// Implements GObject::dispose. // Implements GObject::dispose.
static void my_application_dispose(GObject* object) { static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object); MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object); G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
} }
static void my_application_class_init(MyApplicationClass* klass) { static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = G_APPLICATION_CLASS(klass)->local_command_line =
my_application_local_command_line; my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup; G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose; G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
} }
static void my_application_init(MyApplication* self) {} static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() { MyApplication* my_application_new() {
// Set the program name to the application ID, which helps various systems // Set the program name to the application ID, which helps various systems
// like GTK and desktop environments map this running application to its // like GTK and desktop environments map this running application to its
// corresponding .desktop file. This ensures better integration by allowing // corresponding .desktop file. This ensures better integration by allowing
// the application to be recognized beyond its binary name. // the application to be recognized beyond its binary name.
g_set_prgname(APPLICATION_ID); g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(), return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID, "flags", "application-id", APPLICATION_ID, "flags",
G_APPLICATION_NON_UNIQUE, nullptr)); G_APPLICATION_NON_UNIQUE, nullptr));
} }
+21 -21
View File
@@ -1,21 +1,21 @@
#ifndef FLUTTER_MY_APPLICATION_H_ #ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h> #include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, G_DECLARE_FINAL_TYPE(MyApplication,
my_application, my_application,
MY, MY,
APPLICATION, APPLICATION,
GtkApplication) GtkApplication)
/** /**
* my_application_new: * my_application_new:
* *
* Creates a new Flutter-based application. * Creates a new Flutter-based application.
* *
* Returns: a new #MyApplication. * Returns: a new #MyApplication.
*/ */
MyApplication* my_application_new(); MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_ #endif // FLUTTER_MY_APPLICATION_H_
+22 -14
View File
@@ -165,10 +165,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_assets name: code_assets
sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f sha256: "67cf6d84013f9c601e42a6f8a6b74c4c0d9dc1a1619d775f2b28b732d3551b85"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
code_builder: code_builder:
dependency: transitive dependency: transitive
description: description:
@@ -289,6 +289,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
ffi_leak_tracker:
dependency: transitive
description:
name: ffi_leak_tracker
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@@ -675,10 +683,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -731,18 +739,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus name: package_info_plus
sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.1" version: "10.1.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "4.1.0"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1056,10 +1064,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.10" version: "0.7.11"
time: time:
dependency: transitive dependency: transitive
description: description:
@@ -1200,10 +1208,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: wakelock_plus name: wakelock_plus
sha256: ddf3db70eaa10c37558ff817519b85d527dbd21034fd5d8e1c2e85f31588f1c1 sha256: "824c5bba0f800e86d32e57d3d1843c531f090005cc89d9a837933e6601093d53"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.2" version: "1.6.1"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -1256,10 +1264,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.15.0" version: "6.3.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
+1 -1
View File
@@ -1,7 +1,7 @@
name: gel_rule_app name: gel_rule_app
description: Local backend/core layer for a Flutter booru browser app. description: Local backend/core layer for a Flutter booru browser app.
publish_to: "none" publish_to: "none"
version: 2.0.1+21 version: 2.0.1+22
environment: environment:
sdk: ">=3.3.0 <4.0.0" sdk: ">=3.3.0 <4.0.0"
+10
View File
@@ -1,3 +1,4 @@
import 'dart:ffi' show Abi;
import 'dart:io'; import 'dart:io';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@@ -15,6 +16,7 @@ import 'package:gel_rule_app/core/cache/cache_service.dart';
import 'package:gel_rule_app/core/database/app_database.dart'; import 'package:gel_rule_app/core/database/app_database.dart';
import 'package:gel_rule_app/core/database/database_service.dart'; import 'package:gel_rule_app/core/database/database_service.dart';
import 'package:gel_rule_app/core/utils/result.dart'; import 'package:gel_rule_app/core/utils/result.dart';
import 'package:isar/isar.dart';
void main() { void main() {
late Directory directory; late Directory directory;
@@ -24,6 +26,14 @@ void main() {
late File copiedIsarDll; late File copiedIsarDll;
setUpAll(() async { setUpAll(() async {
if (Platform.isLinux) {
await Isar.initializeIsarCore(
libraries: {
Abi.linuxX64:
'${Directory.current.path}/third_party/isar_flutter_libs/linux/libisar.so',
},
);
}
copiedIsarDll = copiedIsarDll =
File('${Directory.current.path}${Platform.pathSeparator}isar.dll'); File('${Directory.current.path}${Platform.pathSeparator}isar.dll');
if (Platform.isWindows && !copiedIsarDll.existsSync()) { if (Platform.isWindows && !copiedIsarDll.existsSync()) {
@@ -57,6 +57,48 @@ void main() {
expect(changed, 'video'); expect(changed, 'video');
}); });
testWidgets('clear button removes all committed tags', (tester) async {
String? changed;
await tester.pumpWidget(_Harness(
child: TagInputSearchBar(
initialValue: 'touhou video',
onSubmitted: (_) {},
onChanged: (value) {
changed = value;
},
),
));
await tester.tap(find.byTooltip('Clear'));
await tester.pump();
expect(find.text('touhou'), findsNothing);
expect(find.text('video'), findsNothing);
expect(changed, '');
});
testWidgets('external initial value replaces committed tags', (tester) async {
await tester.pumpWidget(_Harness(
child: TagInputSearchBar(
initialValue: 'old_tag',
onSubmitted: (_) {},
),
));
expect(find.text('old_tag'), findsOneWidget);
await tester.pumpWidget(_Harness(
child: TagInputSearchBar(
initialValue: 'new_tag',
onSubmitted: (_) {},
),
));
await tester.pump();
expect(find.text('old_tag'), findsNothing);
expect(find.text('new_tag'), findsOneWidget);
});
testWidgets('suggestion appends active tag as a chip', (tester) async { testWidgets('suggestion appends active tag as a chip', (tester) async {
String? applied; String? applied;
await tester.pumpWidget(_Harness( await tester.pumpWidget(_Harness(
+17 -17
View File
@@ -1,17 +1,17 @@
flutter/ephemeral/ flutter/ephemeral/
# Visual Studio user-specific files. # Visual Studio user-specific files.
*.suo *.suo
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
# Visual Studio build-related files. # Visual Studio build-related files.
x64/ x64/
x86/ x86/
# Visual Studio cache files # Visual Studio cache files
# files ending in .cache can be ignored # files ending in .cache can be ignored
*.[Cc]ache *.[Cc]ache
# but keep track of directories ending in .cache # but keep track of directories ending in .cache
!*.[Cc]ache/ !*.[Cc]ache/
+103 -103
View File
@@ -1,109 +1,109 @@
# Project-level configuration. # Project-level configuration.
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
project(gel_rule_app LANGUAGES CXX) project(gel_rule_app LANGUAGES CXX)
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
# the on-disk name of your application. # the on-disk name of your application.
set(BINARY_NAME "gel_rule_app") set(BINARY_NAME "gel_rule_app")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.
cmake_policy(VERSION 3.14...3.25) cmake_policy(VERSION 3.14...3.25)
# Define build configuration option. # Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG) if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE) CACHE STRING "" FORCE)
else() else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE) STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release") "Debug" "Profile" "Release")
endif() endif()
endif() endif()
# Define settings for the Profile build mode. # Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_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_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects. # Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE) add_definitions(-DUNICODE -D_UNICODE)
add_definitions(-D_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS) add_definitions(-D_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS)
# Compilation settings that should be applied to most targets. # Compilation settings that should be applied to most targets.
# #
# Be cautious about adding new options here, as plugins use this function by # Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead # default. In most cases, you should add new options to specific targets instead
# of modifying this function. # of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET) function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>") target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction() endfunction()
# Flutter library and tool build rules. # Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR}) add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt. # Application build; see runner/CMakeLists.txt.
add_subdirectory("runner") add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding # Generated plugin build rules, which manage building the plugins and adding
# them to the application. # them to the application.
include(flutter/generated_plugins.cmake) include(flutter/generated_plugins.cmake)
# === Installation === # === Installation ===
# Support files are copied into place next to the executable, so that it can # 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) # 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. # so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>") set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run. # Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT OR CMAKE_INSTALL_PREFIX MATCHES "Program Files") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT OR CMAKE_INSTALL_PREFIX MATCHES "Program Files")
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif() endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime) COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES) if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
endif() endif()
# Copy the native assets provided by the build.dart from all packages. # Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}" install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets") set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE " install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime) " COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only. # Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release CONFIGURATIONS Profile;Release
COMPONENT Runtime) COMPONENT Runtime)
+109 -109
View File
@@ -1,109 +1,109 @@
# This file controls Flutter-level build steps. It should not be edited. # This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool. # Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake) include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See # TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146. # https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# Set fallback configurations for older versions of the flutter tool. # Set fallback configurations for older versions of the flutter tool.
if (NOT DEFINED FLUTTER_TARGET_PLATFORM) if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
set(FLUTTER_TARGET_PLATFORM "windows-x64") set(FLUTTER_TARGET_PLATFORM "windows-x64")
endif() endif()
# === Flutter Library === # === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step. # Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h" "flutter_export.h"
"flutter_windows.h" "flutter_windows.h"
"flutter_messenger.h" "flutter_messenger.h"
"flutter_plugin_registrar.h" "flutter_plugin_registrar.h"
"flutter_texture_registrar.h" "flutter_texture_registrar.h"
) )
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE) add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}" "${EPHEMERAL_DIR}"
) )
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble) add_dependencies(flutter flutter_assemble)
# === Wrapper === # === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc" "core_implementations.cc"
"standard_codec.cc" "standard_codec.cc"
) )
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc" "plugin_registrar.cc"
) )
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc" "flutter_engine.cc"
"flutter_view_controller.cc" "flutter_view_controller.cc"
) )
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin. # Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_PLUGIN}
) )
apply_standard_settings(flutter_wrapper_plugin) apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON) POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden) CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include" "${WRAPPER_ROOT}/include"
) )
add_dependencies(flutter_wrapper_plugin flutter_assemble) add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner. # Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP} ${CPP_WRAPPER_SOURCES_APP}
) )
apply_standard_settings(flutter_wrapper_app) apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include" "${WRAPPER_ROOT}/include"
) )
add_dependencies(flutter_wrapper_app flutter_assemble) add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend === # === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time, # _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 # since currently there's no way to get a full input/output list from the
# flutter tool. # flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command( add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP} ${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT} ${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT} ${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
${FLUTTER_TARGET_PLATFORM} $<CONFIG> ${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM VERBATIM
) )
add_custom_target(flutter_assemble DEPENDS add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}" "${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP} ${CPP_WRAPPER_SOURCES_APP}
) )
+40 -40
View File
@@ -1,40 +1,40 @@
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX) project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the # Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work. # work.
# #
# Any new source files that you add to the application should be added here. # Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32 add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp" "flutter_window.cpp"
"main.cpp" "main.cpp"
"utils.cpp" "utils.cpp"
"win32_window.cpp" "win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc" "Runner.rc"
"runner.exe.manifest" "runner.exe.manifest"
) )
# Apply the standard set of build settings. This can be removed for applications # Apply the standard set of build settings. This can be removed for applications
# that need different build settings. # that need different build settings.
apply_standard_settings(${BINARY_NAME}) apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the build version. # Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
# Disable Windows macros that collide with C++ standard library functions. # Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific # Add dependency libraries and include directories. Add any application-specific
# dependencies here. # dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed. # Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble) add_dependencies(${BINARY_NAME} flutter_assemble)
+118 -118
View File
@@ -1,121 +1,121 @@
// Microsoft Visual C++ generated resource script. // Microsoft Visual C++ generated resource script.
// //
#pragma code_page(65001) #pragma code_page(65001)
#include "resource.h" #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS #define APSTUDIO_READONLY_SYMBOLS
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
// Generated from the TEXTINCLUDE 2 resource. // Generated from the TEXTINCLUDE 2 resource.
// //
#include "winres.h" #include "winres.h"
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS #undef APSTUDIO_READONLY_SYMBOLS
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// English (United States) resources // English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
// TEXTINCLUDE // TEXTINCLUDE
// //
1 TEXTINCLUDE 1 TEXTINCLUDE
BEGIN BEGIN
"resource.h\0" "resource.h\0"
END END
2 TEXTINCLUDE 2 TEXTINCLUDE
BEGIN BEGIN
"#include ""winres.h""\r\n" "#include ""winres.h""\r\n"
"\0" "\0"
END END
3 TEXTINCLUDE 3 TEXTINCLUDE
BEGIN BEGIN
"\r\n" "\r\n"
"\0" "\0"
END END
#endif // APSTUDIO_INVOKED #endif // APSTUDIO_INVOKED
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
// Icon // Icon
// //
// Icon with lowest ID value placed first to ensure application icon // Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems. // remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico" IDI_APP_ICON ICON "resources\\app_icon.ico"
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
// Version // Version
// //
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else #else
#define VERSION_AS_NUMBER 1,0,0,0 #define VERSION_AS_NUMBER 1,0,0,0
#endif #endif
#if defined(FLUTTER_VERSION) #if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION #define VERSION_AS_STRING FLUTTER_VERSION
#else #else
#define VERSION_AS_STRING "1.0.0" #define VERSION_AS_STRING "1.0.0"
#endif #endif
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG FILEFLAGS VS_FF_DEBUG
#else #else
FILEFLAGS 0x0L FILEFLAGS 0x0L
#endif #endif
FILEOS VOS__WINDOWS32 FILEOS VOS__WINDOWS32
FILETYPE VFT_APP FILETYPE VFT_APP
FILESUBTYPE 0x0L FILESUBTYPE 0x0L
BEGIN BEGIN
BLOCK "StringFileInfo" BLOCK "StringFileInfo"
BEGIN BEGIN
BLOCK "040904e4" BLOCK "040904e4"
BEGIN BEGIN
VALUE "CompanyName", "com.example" "\0" VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "Lunaris" "\0" VALUE "FileDescription", "Lunaris" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "Lunaris" "\0" VALUE "InternalName", "Lunaris" "\0"
VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "gel_rule_app.exe" "\0" VALUE "OriginalFilename", "gel_rule_app.exe" "\0"
VALUE "ProductName", "Lunaris" "\0" VALUE "ProductName", "Lunaris" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"
BEGIN BEGIN
VALUE "Translation", 0x409, 1252 VALUE "Translation", 0x409, 1252
END END
END END
#endif // English (United States) resources #endif // English (United States) resources
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED #ifndef APSTUDIO_INVOKED
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
// Generated from the TEXTINCLUDE 3 resource. // Generated from the TEXTINCLUDE 3 resource.
// //
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED #endif // not APSTUDIO_INVOKED
+71 -71
View File
@@ -1,71 +1,71 @@
#include "flutter_window.h" #include "flutter_window.h"
#include <optional> #include <optional>
#include "flutter/generated_plugin_registrant.h" #include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project) FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {} : project_(project) {}
FlutterWindow::~FlutterWindow() {} FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() { bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) { if (!Win32Window::OnCreate()) {
return false; return false;
} }
RECT frame = GetClientArea(); RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface // The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path. // creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>( flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_); frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful. // Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) { if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false; return false;
} }
RegisterPlugins(flutter_controller_->engine()); RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow()); SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() { flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show(); this->Show();
}); });
// Flutter can complete the first frame before the "show window" callback is // Flutter can complete the first frame before the "show window" callback is
// registered. The following call ensures a frame is pending to ensure the // registered. The following call ensures a frame is pending to ensure the
// window is shown. It is a no-op if the first frame hasn't completed yet. // window is shown. It is a no-op if the first frame hasn't completed yet.
flutter_controller_->ForceRedraw(); flutter_controller_->ForceRedraw();
return true; return true;
} }
void FlutterWindow::OnDestroy() { void FlutterWindow::OnDestroy() {
if (flutter_controller_) { if (flutter_controller_) {
flutter_controller_ = nullptr; flutter_controller_ = nullptr;
} }
Win32Window::OnDestroy(); Win32Window::OnDestroy();
} }
LRESULT LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message, FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam, WPARAM const wparam,
LPARAM const lparam) noexcept { LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages. // Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) { if (flutter_controller_) {
std::optional<LRESULT> result = std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam); lparam);
if (result) { if (result) {
return *result; return *result;
} }
} }
switch (message) { switch (message) {
case WM_FONTCHANGE: case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts(); flutter_controller_->engine()->ReloadSystemFonts();
break; break;
} }
return Win32Window::MessageHandler(hwnd, message, wparam, lparam); return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
} }
+33 -33
View File
@@ -1,33 +1,33 @@
#ifndef RUNNER_FLUTTER_WINDOW_H_ #ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h> #include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h> #include <flutter/flutter_view_controller.h>
#include <memory> #include <memory>
#include "win32_window.h" #include "win32_window.h"
// A window that does nothing but host a Flutter view. // A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window { class FlutterWindow : public Win32Window {
public: public:
// Creates a new FlutterWindow hosting a Flutter view running |project|. // Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project); explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow(); virtual ~FlutterWindow();
protected: protected:
// Win32Window: // Win32Window:
bool OnCreate() override; bool OnCreate() override;
void OnDestroy() override; void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override; LPARAM const lparam) noexcept override;
private: private:
// The project to run. // The project to run.
flutter::DartProject project_; flutter::DartProject project_;
// The Flutter instance hosted by this window. // The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_; std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
}; };
#endif // RUNNER_FLUTTER_WINDOW_H_ #endif // RUNNER_FLUTTER_WINDOW_H_
+42 -42
View File
@@ -1,43 +1,43 @@
#include <flutter/dart_project.h> #include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h> #include <flutter/flutter_view_controller.h>
#include <windows.h> #include <windows.h>
#include "flutter_window.h" #include "flutter_window.h"
#include "utils.h" #include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) { _In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a // Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger. // new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole(); CreateAndAttachConsole();
} }
// Initialize COM, so that it is available for use in the library and/or // Initialize COM, so that it is available for use in the library and/or
// plugins. // plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data"); flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments = std::vector<std::string> command_line_arguments =
GetCommandLineArguments(); GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.Create(L"Lunaris", origin, size)) { if (!window.Create(L"Lunaris", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);
::MSG msg; ::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) { while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg); ::TranslateMessage(&msg);
::DispatchMessage(&msg); ::DispatchMessage(&msg);
} }
::CoUninitialize(); ::CoUninitialize();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
+16 -16
View File
@@ -1,16 +1,16 @@
//{{NO_DEPENDENCIES}} //{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file. // Microsoft Visual C++ generated include file.
// Used by Runner.rc // Used by Runner.rc
// //
#define IDI_APP_ICON 101 #define IDI_APP_ICON 101
// Next default values for new objects // Next default values for new objects
// //
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101 #define _APS_NEXT_SYMED_VALUE 101
#endif #endif
#endif #endif
+14 -14
View File
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings> <windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings> </windowsSettings>
</application> </application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application> <application>
<!-- Windows 10 and Windows 11 --> <!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application> </application>
</compatibility> </compatibility>
</assembly> </assembly>
+65 -65
View File
@@ -1,65 +1,65 @@
#include "utils.h" #include "utils.h"
#include <flutter_windows.h> #include <flutter_windows.h>
#include <io.h> #include <io.h>
#include <stdio.h> #include <stdio.h>
#include <windows.h> #include <windows.h>
#include <iostream> #include <iostream>
void CreateAndAttachConsole() { void CreateAndAttachConsole() {
if (::AllocConsole()) { if (::AllocConsole()) {
FILE *unused; FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) { if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1); _dup2(_fileno(stdout), 1);
} }
if (freopen_s(&unused, "CONOUT$", "w", stderr)) { if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2); _dup2(_fileno(stdout), 2);
} }
std::ios::sync_with_stdio(); std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams(); FlutterDesktopResyncOutputStreams();
} }
} }
std::vector<std::string> GetCommandLineArguments() { std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc; int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) { if (argv == nullptr) {
return std::vector<std::string>(); return std::vector<std::string>();
} }
std::vector<std::string> command_line_arguments; std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name. // Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i])); command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
} }
::LocalFree(argv); ::LocalFree(argv);
return command_line_arguments; return command_line_arguments;
} }
std::string Utf8FromUtf16(const wchar_t* utf16_string) { std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) { if (utf16_string == nullptr) {
return std::string(); return std::string();
} }
unsigned int target_length = ::WideCharToMultiByte( unsigned int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr) -1, nullptr, 0, nullptr, nullptr)
-1; // remove the trailing null character -1; // remove the trailing null character
int input_length = (int)wcslen(utf16_string); int input_length = (int)wcslen(utf16_string);
std::string utf8_string; std::string utf8_string;
if (target_length == 0 || target_length > utf8_string.max_size()) { if (target_length == 0 || target_length > utf8_string.max_size()) {
return utf8_string; return utf8_string;
} }
utf8_string.resize(target_length); utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte( int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
input_length, utf8_string.data(), target_length, nullptr, nullptr); input_length, utf8_string.data(), target_length, nullptr, nullptr);
if (converted_length == 0) { if (converted_length == 0) {
return std::string(); return std::string();
} }
return utf8_string; return utf8_string;
} }
+19 -19
View File
@@ -1,19 +1,19 @@
#ifndef RUNNER_UTILS_H_ #ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_ #define RUNNER_UTILS_H_
#include <string> #include <string>
#include <vector> #include <vector>
// Creates a console for the process, and redirects stdout and stderr to // Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library. // it for both the runner and the Flutter library.
void CreateAndAttachConsole(); void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // 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. // encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string); std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>, // Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure. // encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments(); std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_ #endif // RUNNER_UTILS_H_
+288 -288
View File
@@ -1,288 +1,288 @@
#include "win32_window.h" #include "win32_window.h"
#include <dwmapi.h> #include <dwmapi.h>
#include <flutter_windows.h> #include <flutter_windows.h>
#include "resource.h" #include "resource.h"
namespace { namespace {
/// Window attribute that enables dark mode window decorations. /// Window attribute that enables dark mode window decorations.
/// ///
/// Redefined in case the developer's machine has a Windows SDK older than /// Redefined in case the developer's machine has a Windows SDK older than
/// version 10.0.22000.0. /// version 10.0.22000.0.
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif #endif
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
/// Registry key for app theme preference. /// Registry key for app theme preference.
/// ///
/// A value of 0 indicates apps should use dark mode. A non-zero or missing /// A value of 0 indicates apps should use dark mode. A non-zero or missing
/// value indicates apps should use light mode. /// value indicates apps should use light mode.
constexpr const wchar_t kGetPreferredBrightnessRegKey[] = constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
// The number of Win32Window objects that currently exist. // The number of Win32Window objects that currently exist.
static int g_active_window_count = 0; static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in // Scale helper to convert logical scaler values to physical using passed in
// scale factor // scale factor
int Scale(int source, double scale_factor) { int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor); return static_cast<int>(source * scale_factor);
} }
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode. // This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) { void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll"); HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) { if (!user32_module) {
return; return;
} }
auto enable_non_client_dpi_scaling = auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>( reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling")); GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) { if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd); enable_non_client_dpi_scaling(hwnd);
} }
FreeLibrary(user32_module); FreeLibrary(user32_module);
} }
} // namespace } // namespace
// Manages the Win32Window's window class registration. // Manages the Win32Window's window class registration.
class WindowClassRegistrar { class WindowClassRegistrar {
public: public:
~WindowClassRegistrar() = default; ~WindowClassRegistrar() = default;
// Returns the singleton registrar instance. // Returns the singleton registrar instance.
static WindowClassRegistrar* GetInstance() { static WindowClassRegistrar* GetInstance() {
if (!instance_) { if (!instance_) {
instance_ = new WindowClassRegistrar(); instance_ = new WindowClassRegistrar();
} }
return instance_; return instance_;
} }
// Returns the name of the window class, registering the class if it hasn't // Returns the name of the window class, registering the class if it hasn't
// previously been registered. // previously been registered.
const wchar_t* GetWindowClass(); const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no // Unregisters the window class. Should only be called if there are no
// instances of the window. // instances of the window.
void UnregisterWindowClass(); void UnregisterWindowClass();
private: private:
WindowClassRegistrar() = default; WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_; static WindowClassRegistrar* instance_;
bool class_registered_ = false; bool class_registered_ = false;
}; };
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() { const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) { if (!class_registered_) {
WNDCLASS window_class{}; WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName; window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0; window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0; window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr); window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon = window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0; window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr; window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc; window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class); RegisterClass(&window_class);
class_registered_ = true; class_registered_ = true;
} }
return kWindowClassName; return kWindowClassName;
} }
void WindowClassRegistrar::UnregisterWindowClass() { void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr); UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false; class_registered_ = false;
} }
Win32Window::Win32Window() { Win32Window::Win32Window() {
++g_active_window_count; ++g_active_window_count;
} }
Win32Window::~Win32Window() { Win32Window::~Win32Window() {
--g_active_window_count; --g_active_window_count;
Destroy(); Destroy();
} }
bool Win32Window::Create(const std::wstring& title, bool Win32Window::Create(const std::wstring& title,
const Point& origin, const Point& origin,
const Size& size) { const Size& size) {
Destroy(); Destroy();
const wchar_t* window_class = const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass(); WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x), const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)}; static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0; double scale_factor = dpi / 96.0;
HWND window = CreateWindow( HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW, window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this); nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) { if (!window) {
return false; return false;
} }
UpdateTheme(window); UpdateTheme(window);
return OnCreate(); return OnCreate();
} }
bool Win32Window::Show() { bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL); return ShowWindow(window_handle_, SW_SHOWNORMAL);
} }
// static // static
LRESULT CALLBACK Win32Window::WndProc(HWND const window, LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message, UINT const message,
WPARAM const wparam, WPARAM const wparam,
LPARAM const lparam) noexcept { LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) { if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam); auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA, SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams)); reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams); auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window); EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window; that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) { } else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam); return that->MessageHandler(window, message, wparam, lparam);
} }
return DefWindowProc(window, message, wparam, lparam); return DefWindowProc(window, message, wparam, lparam);
} }
LRESULT LRESULT
Win32Window::MessageHandler(HWND hwnd, Win32Window::MessageHandler(HWND hwnd,
UINT const message, UINT const message,
WPARAM const wparam, WPARAM const wparam,
LPARAM const lparam) noexcept { LPARAM const lparam) noexcept {
switch (message) { switch (message) {
case WM_DESTROY: case WM_DESTROY:
window_handle_ = nullptr; window_handle_ = nullptr;
Destroy(); Destroy();
if (quit_on_close_) { if (quit_on_close_) {
PostQuitMessage(0); PostQuitMessage(0);
} }
return 0; return 0;
case WM_DPICHANGED: { case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam); auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left; LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top; LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE); newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0; return 0;
} }
case WM_SIZE: { case WM_SIZE: {
RECT rect = GetClientArea(); RECT rect = GetClientArea();
if (child_content_ != nullptr) { if (child_content_ != nullptr) {
// Size and position the child window. // Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE); rect.bottom - rect.top, TRUE);
} }
return 0; return 0;
} }
case WM_ACTIVATE: case WM_ACTIVATE:
if (child_content_ != nullptr) { if (child_content_ != nullptr) {
SetFocus(child_content_); SetFocus(child_content_);
} }
return 0; return 0;
case WM_DWMCOLORIZATIONCOLORCHANGED: case WM_DWMCOLORIZATIONCOLORCHANGED:
UpdateTheme(hwnd); UpdateTheme(hwnd);
return 0; return 0;
} }
return DefWindowProc(window_handle_, message, wparam, lparam); return DefWindowProc(window_handle_, message, wparam, lparam);
} }
void Win32Window::Destroy() { void Win32Window::Destroy() {
OnDestroy(); OnDestroy();
if (window_handle_) { if (window_handle_) {
DestroyWindow(window_handle_); DestroyWindow(window_handle_);
window_handle_ = nullptr; window_handle_ = nullptr;
} }
if (g_active_window_count == 0) { if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
} }
} }
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>( return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA)); GetWindowLongPtr(window, GWLP_USERDATA));
} }
void Win32Window::SetChildContent(HWND content) { void Win32Window::SetChildContent(HWND content) {
child_content_ = content; child_content_ = content;
SetParent(content, window_handle_); SetParent(content, window_handle_);
RECT frame = GetClientArea(); RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left, MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true); frame.bottom - frame.top, true);
SetFocus(child_content_); SetFocus(child_content_);
} }
RECT Win32Window::GetClientArea() { RECT Win32Window::GetClientArea() {
RECT frame; RECT frame;
GetClientRect(window_handle_, &frame); GetClientRect(window_handle_, &frame);
return frame; return frame;
} }
HWND Win32Window::GetHandle() { HWND Win32Window::GetHandle() {
return window_handle_; return window_handle_;
} }
void Win32Window::SetQuitOnClose(bool quit_on_close) { void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close; quit_on_close_ = quit_on_close;
} }
bool Win32Window::OnCreate() { bool Win32Window::OnCreate() {
// No-op; provided for subclasses. // No-op; provided for subclasses.
return true; return true;
} }
void Win32Window::OnDestroy() { void Win32Window::OnDestroy() {
// No-op; provided for subclasses. // No-op; provided for subclasses.
} }
void Win32Window::UpdateTheme(HWND const window) { void Win32Window::UpdateTheme(HWND const window) {
DWORD light_mode; DWORD light_mode;
DWORD light_mode_size = sizeof(light_mode); DWORD light_mode_size = sizeof(light_mode);
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
kGetPreferredBrightnessRegValue, kGetPreferredBrightnessRegValue,
RRF_RT_REG_DWORD, nullptr, &light_mode, RRF_RT_REG_DWORD, nullptr, &light_mode,
&light_mode_size); &light_mode_size);
if (result == ERROR_SUCCESS) { if (result == ERROR_SUCCESS) {
BOOL enable_dark_mode = light_mode == 0; BOOL enable_dark_mode = light_mode == 0;
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
&enable_dark_mode, sizeof(enable_dark_mode)); &enable_dark_mode, sizeof(enable_dark_mode));
} }
} }
+102 -102
View File
@@ -1,102 +1,102 @@
#ifndef RUNNER_WIN32_WINDOW_H_ #ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_
#include <windows.h> #include <windows.h>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be // A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom // inherited from by classes that wish to specialize with custom
// rendering and input handling // rendering and input handling
class Win32Window { class Win32Window {
public: public:
struct Point { struct Point {
unsigned int x; unsigned int x;
unsigned int y; unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {} Point(unsigned int x, unsigned int y) : x(x), y(y) {}
}; };
struct Size { struct Size {
unsigned int width; unsigned int width;
unsigned int height; unsigned int height;
Size(unsigned int width, unsigned int height) Size(unsigned int width, unsigned int height)
: width(width), height(height) {} : width(width), height(height) {}
}; };
Win32Window(); Win32Window();
virtual ~Win32Window(); virtual ~Win32Window();
// Creates a win32 window with |title| that is positioned and sized using // Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window // |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 // sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size this function will scale the inputted width and height as // consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until // as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully. // |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring& title, const Point& origin, const Size& size); bool Create(const std::wstring& title, const Point& origin, const Size& size);
// Show the current window. Returns true if the window was successfully shown. // Show the current window. Returns true if the window was successfully shown.
bool Show(); bool Show();
// Release OS resources associated with window. // Release OS resources associated with window.
void Destroy(); void Destroy();
// Inserts |content| into the window tree. // Inserts |content| into the window tree.
void SetChildContent(HWND content); void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other // Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed. // window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle(); HWND GetHandle();
// If true, closing this window will quit the application. // If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close); void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area. // Return a RECT representing the bounds of the current client area.
RECT GetClientArea(); RECT GetClientArea();
protected: protected:
// Processes and route salient window messages for mouse handling, // Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that // size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle. // inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window, virtual LRESULT MessageHandler(HWND window,
UINT const message, UINT const message,
WPARAM const wparam, WPARAM const wparam,
LPARAM const lparam) noexcept; LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related // Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails. // setup. Subclasses should return false if setup fails.
virtual bool OnCreate(); virtual bool OnCreate();
// Called when Destroy is called. // Called when Destroy is called.
virtual void OnDestroy(); virtual void OnDestroy();
private: private:
friend class WindowClassRegistrar; friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which // 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 // is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically // non-client DPI scaling so that the non-client area automatically
// responds to changes in DPI. All other messages are handled by // responds to changes in DPI. All other messages are handled by
// MessageHandler. // MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window, static LRESULT CALLBACK WndProc(HWND const window,
UINT const message, UINT const message,
WPARAM const wparam, WPARAM const wparam,
LPARAM const lparam) noexcept; LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window| // Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept; static Win32Window* GetThisFromHandle(HWND const window) noexcept;
// Update the window frame's theme to match the system theme. // Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window); static void UpdateTheme(HWND const window);
bool quit_on_close_ = false; bool quit_on_close_ = false;
// window handle for top level window. // window handle for top level window.
HWND window_handle_ = nullptr; HWND window_handle_ = nullptr;
// window handle for hosted content. // window handle for hosted content.
HWND child_content_ = nullptr; HWND child_content_ = nullptr;
}; };
#endif // RUNNER_WIN32_WINDOW_H_ #endif // RUNNER_WIN32_WINDOW_H_