init
46
.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
10
.metadata
Normal file
@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: b101bfe32f634566e7cb2791a9efe19cf8828b15
|
||||
channel: beta
|
||||
|
||||
project_type: app
|
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# fengshui_compass
|
||||
|
||||
风水罗盘
|
||||
|
||||
## Getting Started
|
||||
|
||||
flutter channel beta
|
||||
flutter upgrade
|
||||
flutter config --enable-windows-desktop
|
29
analysis_options.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
13
android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
69
android/app/build.gradle
Normal file
@ -0,0 +1,69 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new Exception("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion "23.1.7779620"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "cn.com.motse.fengshui_compass"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
7
android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cn.com.motse.fengshui_compass">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
35
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cn.com.motse.fengshui_compass">
|
||||
<application
|
||||
android:label="fengshui_compass"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:resource="@drawable/launch_image"
|
||||
android:value="2" />
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,6 @@
|
||||
package cn.com.motse.fengshui_compass
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/transparent" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@drawable/launch_image" />
|
||||
</item>
|
||||
</layer-list>
|
BIN
android/app/src/main/res/drawable/launch_image.png
Normal file
After Width: | Height: | Size: 540 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 168 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
20
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
|
||||
</style>
|
||||
<color name="transparent">#00000000</color>
|
||||
</resources>
|
7
android/app/src/profile/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cn.com.motse.fengshui_compass">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
31
android/build.gradle
Normal file
@ -0,0 +1,31 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
3
android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
11
android/settings.gradle
Normal file
@ -0,0 +1,11 @@
|
||||
include ':app'
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
73
assets/fonts/iconfont.css
Normal file
@ -0,0 +1,73 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id */
|
||||
src: url('iconfont.ttf?t=1648102093452') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-zuoyoudengguanbi:before {
|
||||
content: "\e509";
|
||||
}
|
||||
|
||||
.icon-shangdnegguanbi:before {
|
||||
content: "\e50a";
|
||||
}
|
||||
|
||||
.icon-fanhui:before {
|
||||
content: "\e50b";
|
||||
}
|
||||
|
||||
.icon-jiesuo:before {
|
||||
content: "\e50c";
|
||||
}
|
||||
|
||||
.icon-shangdeng:before {
|
||||
content: "\e50d";
|
||||
}
|
||||
|
||||
.icon-celiang:before {
|
||||
content: "\e50e";
|
||||
}
|
||||
|
||||
.icon-mima:before {
|
||||
content: "\e50f";
|
||||
}
|
||||
|
||||
.icon-rili:before {
|
||||
content: "\e510";
|
||||
}
|
||||
|
||||
.icon-huancun:before {
|
||||
content: "\e511";
|
||||
}
|
||||
|
||||
.icon-shoujihao:before {
|
||||
content: "\e512";
|
||||
}
|
||||
|
||||
.icon-luopan:before {
|
||||
content: "\e513";
|
||||
}
|
||||
|
||||
.icon-yanzhengma:before {
|
||||
content: "\e514";
|
||||
}
|
||||
|
||||
.icon-zaixianqiju-:before {
|
||||
content: "\e515";
|
||||
}
|
||||
|
||||
.icon-zuoyoudneg:before {
|
||||
content: "\e516";
|
||||
}
|
||||
|
||||
.icon-shoujihao-1:before {
|
||||
content: "\e517";
|
||||
}
|
||||
|
114
assets/fonts/iconfont.json
Normal file
@ -0,0 +1,114 @@
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "28467053",
|
||||
"name": "closeSideLaser",
|
||||
"font_class": "zuoyoudengguanbi",
|
||||
"unicode": "e509",
|
||||
"unicode_decimal": 58633
|
||||
},
|
||||
{
|
||||
"icon_id": "28467055",
|
||||
"name": "closeUpLaser",
|
||||
"font_class": "shangdnegguanbi",
|
||||
"unicode": "e50a",
|
||||
"unicode_decimal": 58634
|
||||
},
|
||||
{
|
||||
"icon_id": "28467057",
|
||||
"name": "back",
|
||||
"font_class": "fanhui",
|
||||
"unicode": "e50b",
|
||||
"unicode_decimal": 58635
|
||||
},
|
||||
{
|
||||
"icon_id": "28467058",
|
||||
"name": "unlock",
|
||||
"font_class": "jiesuo",
|
||||
"unicode": "e50c",
|
||||
"unicode_decimal": 58636
|
||||
},
|
||||
{
|
||||
"icon_id": "28467059",
|
||||
"name": "openUpLaser",
|
||||
"font_class": "shangdeng",
|
||||
"unicode": "e50d",
|
||||
"unicode_decimal": 58637
|
||||
},
|
||||
{
|
||||
"icon_id": "28467060",
|
||||
"name": "ranging",
|
||||
"font_class": "celiang",
|
||||
"unicode": "e50e",
|
||||
"unicode_decimal": 58638
|
||||
},
|
||||
{
|
||||
"icon_id": "28467061",
|
||||
"name": "mima",
|
||||
"font_class": "mima",
|
||||
"unicode": "e50f",
|
||||
"unicode_decimal": 58639
|
||||
},
|
||||
{
|
||||
"icon_id": "28467064",
|
||||
"name": "calendar",
|
||||
"font_class": "rili",
|
||||
"unicode": "e510",
|
||||
"unicode_decimal": 58640
|
||||
},
|
||||
{
|
||||
"icon_id": "28467066",
|
||||
"name": "cache",
|
||||
"font_class": "huancun",
|
||||
"unicode": "e511",
|
||||
"unicode_decimal": 58641
|
||||
},
|
||||
{
|
||||
"icon_id": "28467067",
|
||||
"name": "mobile",
|
||||
"font_class": "shoujihao",
|
||||
"unicode": "e512",
|
||||
"unicode_decimal": 58642
|
||||
},
|
||||
{
|
||||
"icon_id": "28467068",
|
||||
"name": "compass",
|
||||
"font_class": "luopan",
|
||||
"unicode": "e513",
|
||||
"unicode_decimal": 58643
|
||||
},
|
||||
{
|
||||
"icon_id": "28467069",
|
||||
"name": "edit",
|
||||
"font_class": "yanzhengma",
|
||||
"unicode": "e514",
|
||||
"unicode_decimal": 58644
|
||||
},
|
||||
{
|
||||
"icon_id": "28467070",
|
||||
"name": "taiji1",
|
||||
"font_class": "zaixianqiju-",
|
||||
"unicode": "e515",
|
||||
"unicode_decimal": 58645
|
||||
},
|
||||
{
|
||||
"icon_id": "28467072",
|
||||
"name": "openSideLaser",
|
||||
"font_class": "zuoyoudneg",
|
||||
"unicode": "e516",
|
||||
"unicode_decimal": 58646
|
||||
},
|
||||
{
|
||||
"icon_id": "28467073",
|
||||
"name": "person",
|
||||
"font_class": "shoujihao-1",
|
||||
"unicode": "e517",
|
||||
"unicode_decimal": 58647
|
||||
}
|
||||
]
|
||||
}
|
BIN
assets/fonts/iconfont.ttf
Normal file
BIN
assets/images/arrow.png
Normal file
After Width: | Height: | Size: 466 B |
BIN
assets/images/bg.png
Normal file
After Width: | Height: | Size: 322 KiB |
BIN
assets/images/bg1.png
Normal file
After Width: | Height: | Size: 612 KiB |
BIN
assets/images/clodeSide.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/images/closeUp.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/images/compass.png
Normal file
After Width: | Height: | Size: 902 KiB |
BIN
assets/images/head.png
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
assets/images/ic_launcher.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/openSide.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/openUp.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/pan.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
assets/images/range_input.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/images/water.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
2489
assets/index - 副本.html
Normal file
2492
assets/index.html
Normal file
5
assets/test.js
Normal file
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
function flutterCallJsMethod(yy, mm, dd, hh, m) {
|
||||
return `${yy}年${mm}月${dd}日${hh}时${m}分`;
|
||||
}
|
||||
</script>
|
79
lib/bottom_navigation_widget.dart
Normal file
@ -0,0 +1,79 @@
|
||||
import 'package:fengshui_compass/components/my_icon.dart';
|
||||
import 'package:fengshui_compass/pages/personal_page.dart';
|
||||
import 'package:fengshui_compass/utils/sp_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fengshui_compass/pages/birthcal_page.dart';
|
||||
import 'package:fengshui_compass/pages/compass_page.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class BottomNavigationWidget extends StatefulWidget {
|
||||
const BottomNavigationWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BottomNavigationWidget> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<BottomNavigationWidget> {
|
||||
int currentIndex = 0;
|
||||
List<Widget> list = [];
|
||||
|
||||
@override
|
||||
Future<void> initState() {
|
||||
list
|
||||
..add(const CompassPage())
|
||||
..add(BirthCalPage())
|
||||
..add(const PersonalPage());
|
||||
super.initState();
|
||||
//
|
||||
Future.delayed(Duration.zero, () {
|
||||
initSP();
|
||||
});
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
}
|
||||
|
||||
void initSP() async {
|
||||
await SPUtil.init();
|
||||
}
|
||||
|
||||
void _onItemTapped(int index) {
|
||||
setState(() {
|
||||
currentIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
extendBody: true,
|
||||
body: list[currentIndex],
|
||||
bottomNavigationBar: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(24.0),
|
||||
topRight: Radius.circular(24.0),
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: currentIndex,
|
||||
selectedItemColor: const Color.fromRGBO(237, 173, 89, 1.0), // 抽出单独定义
|
||||
onTap: _onItemTapped,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(MyIcons.icon_luopan, size: 30),
|
||||
label: '在线罗盘',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(MyIcons.icon_zaixianqiju_, size: 30),
|
||||
label: '在线起局',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person, size: 30),
|
||||
label: '我的',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
12
lib/components/controller.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'dart:async';
|
||||
|
||||
|
||||
/// 创建人: Created by zhaolong
|
||||
/// 创建时间:Created by on 2020/12/27.
|
||||
///
|
||||
/// 可关注公众号:我的大前端生涯 获取最新技术分享
|
||||
/// 可关注网易云课堂:https://study.163.com/instructor/1021406098.htm
|
||||
/// 可关注博客:https://blog.csdn.net/zl18603543572
|
||||
///
|
||||
//用于登录的通信
|
||||
StreamController<int> loginStreamController = StreamController.broadcast();
|
103
lib/components/custom_textfield_widget.dart
Normal file
@ -0,0 +1,103 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 创建人: Created by zhaolong
|
||||
/// 创建时间:Created by on 2020/12/25.
|
||||
///
|
||||
/// 可关注公众号:我的大前端生涯 获取最新技术分享
|
||||
/// 可关注网易云课堂:https://study.163.com/instructor/1021406098.htm
|
||||
/// 可关注博客:https://blog.csdn.net/zl18603543572
|
||||
///
|
||||
/// 代码清单
|
||||
///代码清单
|
||||
///自定义文本输入框
|
||||
class TextFieldWidget extends StatelessWidget {
|
||||
//占位提示文本
|
||||
final String hintText;
|
||||
|
||||
//输入框前置图标
|
||||
final IconData prefixIconData;
|
||||
|
||||
//输入框后置图标
|
||||
final IconData suffixIconData;
|
||||
|
||||
//是否隐藏文本
|
||||
final bool obscureText;
|
||||
|
||||
//输入实时回调
|
||||
final Function(String value) onChanged;
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final Function(String value) submit;
|
||||
final Function() onTap;
|
||||
|
||||
const TextFieldWidget({
|
||||
Key key,
|
||||
this.hintText,
|
||||
this.submit,
|
||||
this.focusNode,
|
||||
this.prefixIconData,
|
||||
this.suffixIconData,
|
||||
this.obscureText,
|
||||
this.onChanged,
|
||||
this.controller,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//构建输入框
|
||||
return TextField(
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
//实时输入回调
|
||||
onChanged: onChanged,
|
||||
onSubmitted: submit,
|
||||
//是否隐藏文本
|
||||
obscureText: obscureText ?? false,
|
||||
//隐藏文本小圆点的颜色
|
||||
cursorColor: Theme.of(context).colorScheme.secondary,
|
||||
//文本样式
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
//输入框的边框
|
||||
decoration: InputDecoration(
|
||||
//提示文本
|
||||
labelText: hintText,
|
||||
//提示文本的样式
|
||||
labelStyle: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
//可编辑时的提示文本的颜色
|
||||
focusColor: Theme.of(context).colorScheme.secondary,
|
||||
//填充
|
||||
filled: true,
|
||||
//可编辑时 无边框样式
|
||||
enabledBorder: const UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
|
||||
//获取输入焦点时的边框样式
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
|
||||
//文本前置的图标
|
||||
prefixIcon: Icon(
|
||||
prefixIconData,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
//文本后置的图标
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Icon(
|
||||
suffixIconData,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
24
lib/components/my_icon.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class MyIcons {
|
||||
static const String _family = 'iconfont';
|
||||
|
||||
MyIcons._();
|
||||
|
||||
static const IconData icon_zuoyoudengguanbi = IconData(0xe509, fontFamily: _family);
|
||||
static const IconData icon_shangdnegguanbi = IconData(0xe50a, fontFamily: _family);
|
||||
static const IconData icon_fanhui = IconData(0xe50b, fontFamily: _family);
|
||||
static const IconData icon_jiesuo = IconData(0xe50c, fontFamily: _family);
|
||||
static const IconData icon_shangdeng = IconData(0xe50d, fontFamily: _family);
|
||||
static const IconData icon_celiang = IconData(0xe50e, fontFamily: _family);
|
||||
static const IconData icon_mima = IconData(0xe50f, fontFamily: _family);
|
||||
static const IconData icon_rili = IconData(0xe510, fontFamily: _family);
|
||||
static const IconData icon_huancun = IconData(0xe511, fontFamily: _family);
|
||||
static const IconData icon_shoujihao = IconData(0xe512, fontFamily: _family);
|
||||
static const IconData icon_luopan = IconData(0xe513, fontFamily: _family);
|
||||
static const IconData icon_yanzhengma = IconData(0xe514, fontFamily: _family);
|
||||
static const IconData icon_zaixianqiju_ = IconData(0xe515, fontFamily: _family);
|
||||
static const IconData icon_zuoyoudneg = IconData(0xe516, fontFamily: _family);
|
||||
static const IconData icon_shoujihao_1 = IconData(0xe517, fontFamily: _family);
|
||||
}
|
24
lib/components/rotate_compass.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RotateCompass extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _RotateCompassState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _RotateCompassState extends State<RotateCompass> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text("haha");
|
||||
}
|
||||
}
|
37
lib/main.dart
Normal file
@ -0,0 +1,37 @@
|
||||
import 'package:fengshui_compass/utils/color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:fengshui_compass/bottom_navigation_widget.dart';
|
||||
|
||||
void main() {
|
||||
///屏幕刷新率和显示率不一致时的优化
|
||||
// GestureBinding.instance.resamplingEnabled = true;
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key key}) : super(key: key);
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '风水罗盘',
|
||||
theme: ThemeData(
|
||||
primarySwatch: createMaterialColor(const Color(0xCFA77300)),
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const BottomNavigationWidget(),
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
//此处
|
||||
Locale('zh'),
|
||||
Locale('en'),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
21
lib/models/et_date.dart
Normal file
@ -0,0 +1,21 @@
|
||||
class EtDate {
|
||||
int cYear;
|
||||
int cMonth;
|
||||
int cDay;
|
||||
|
||||
int lYear;
|
||||
int lMonth;
|
||||
int lDay;
|
||||
|
||||
int hour;
|
||||
int minute;
|
||||
|
||||
EtDate({this.cYear, this.cMonth, this.cDay, this.lYear, this.lMonth, this.lDay, this.hour, this.minute});
|
||||
|
||||
getSolar() {
|
||||
return "$cYear年$cMonth月$cDay日$hour时$minute分";
|
||||
}
|
||||
getLunar() {
|
||||
return "$lYear年$lMonth月$lDay日$hour时$minute分";
|
||||
}
|
||||
}
|
13
lib/models/login_bean.dart
Normal file
@ -0,0 +1,13 @@
|
||||
class LoginBean {
|
||||
String token = "";
|
||||
|
||||
LoginBean.fromMap(Map<String, dynamic> map) {
|
||||
token = map["token"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> map = <String, dynamic>{};
|
||||
map["token"] = token;
|
||||
return map;
|
||||
}
|
||||
}
|
50
lib/models/qimen.dart
Normal file
@ -0,0 +1,50 @@
|
||||
import 'package:fengshui_compass/models/qimen_result.dart';
|
||||
|
||||
class Qimen {
|
||||
String jieqi;
|
||||
String dunju;
|
||||
String zhifu;
|
||||
String zhishi;
|
||||
String bazi;
|
||||
List<String> kong;
|
||||
List<QimenResult> qimenResult;
|
||||
|
||||
Qimen(
|
||||
{this.jieqi,
|
||||
this.dunju,
|
||||
this.zhifu,
|
||||
this.zhishi,
|
||||
this.bazi,
|
||||
this.kong,
|
||||
this.qimenResult});
|
||||
|
||||
Qimen.fromJson(Map<String, dynamic> json) {
|
||||
jieqi = json['jieqi'];
|
||||
dunju = json['dunju'];
|
||||
zhifu = json['zhifu'];
|
||||
zhishi = json['zhishi'];
|
||||
bazi = json['bazi'];
|
||||
kong = json['kong'].cast<String>();
|
||||
if (json['qimenResult'] != null) {
|
||||
qimenResult = [];
|
||||
json['qimenResult'].forEach((v) {
|
||||
qimenResult.add(QimenResult.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['jieqi'] = jieqi;
|
||||
data['dunju'] = dunju;
|
||||
data['zhifu'] = zhifu;
|
||||
data['zhishi'] = zhishi;
|
||||
data['bazi'] = bazi;
|
||||
data['kong'] = kong;
|
||||
if (this.qimenResult != null) {
|
||||
data['qimenResult'] = qimenResult.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
28
lib/models/qimen_result.dart
Normal file
@ -0,0 +1,28 @@
|
||||
// 计算天、地、星、神、门 五盘
|
||||
class QimenResult {
|
||||
String sky;
|
||||
String earth;
|
||||
String star;
|
||||
String gate;
|
||||
String god;
|
||||
|
||||
QimenResult({this.sky, this.earth, this.star, this.gate, this.god});
|
||||
|
||||
QimenResult.fromJson(Map<String, dynamic> json) {
|
||||
sky = json['sky'];
|
||||
earth = json['earth'];
|
||||
star = json['star'];
|
||||
gate = json['gate'];
|
||||
god = json['god'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['sky'] = sky;
|
||||
data['earth'] = earth;
|
||||
data['star'] = star;
|
||||
data['gate'] = gate;
|
||||
data['god'] = god;
|
||||
return data;
|
||||
}
|
||||
}
|
13
lib/models/user_bean.dart
Normal file
@ -0,0 +1,13 @@
|
||||
class UserBean {
|
||||
String userName = "";
|
||||
|
||||
UserBean.fromMap(Map<String, dynamic> map) {
|
||||
userName = map["userName"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> map = <String, dynamic>{};
|
||||
map["userName"] = userName;
|
||||
return map;
|
||||
}
|
||||
}
|
331
lib/net/dio_utils.dart
Normal file
@ -0,0 +1,331 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/adapter.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
|
||||
import 'log_interceptor.dart';
|
||||
|
||||
export 'http_helper.dart';
|
||||
|
||||
class DioUtils {
|
||||
Dio _dio;
|
||||
|
||||
// 工厂模式
|
||||
factory DioUtils() => _getInstance();
|
||||
|
||||
static DioUtils get instance => _getInstance();
|
||||
static DioUtils _instance;
|
||||
|
||||
//配置代理标识 false 不配置
|
||||
bool isProxy = false;
|
||||
|
||||
//网络代理地址
|
||||
String proxyIp = "192.168.0.107";
|
||||
|
||||
//网络代理端口
|
||||
String proxyPort = "8888";
|
||||
|
||||
DioUtils._internal() {
|
||||
BaseOptions options = new BaseOptions();
|
||||
//请求时间
|
||||
options.connectTimeout = 20000;
|
||||
options.receiveTimeout = 2 * 60 * 1000;
|
||||
options.sendTimeout = 2 * 60 * 1000;
|
||||
// 初始化
|
||||
_dio = new Dio(options);
|
||||
//当App运行在Release环境时,inProduction为true;
|
||||
// 当App运行在Debug和Profile环境时,inProduction为false。
|
||||
bool inProduction = bool.fromEnvironment("dart.vm.product");
|
||||
if (!inProduction) {
|
||||
debugFunction();
|
||||
}
|
||||
}
|
||||
|
||||
static DioUtils _getInstance() {
|
||||
_instance ??= DioUtils._internal();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void debugFunction() {
|
||||
// 添加log
|
||||
_dio.interceptors.add(LogsInterceptors());
|
||||
//配置代理
|
||||
if (isProxy) {
|
||||
_setupPROXY();
|
||||
}
|
||||
}
|
||||
|
||||
/// 配置代理
|
||||
void _setupPROXY() {
|
||||
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
||||
(HttpClient client) {
|
||||
client.findProxy = (uri) {
|
||||
//proxyIp 地址 proxyPort 端口
|
||||
return 'PROXY $proxyIp : $proxyPort';
|
||||
};
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) {
|
||||
//忽略证书
|
||||
return true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/// get 请求
|
||||
///[url]请求链接
|
||||
///[queryParameters]请求参数
|
||||
///[cancelTag] 取消网络请求的标识
|
||||
Future<ResponseInfo> getRequest({
|
||||
String url,
|
||||
Map<String, dynamic> queryParameters,
|
||||
CancelToken cancelTag,
|
||||
}) async {
|
||||
//发起get请求
|
||||
try {
|
||||
_dio.options = await buildOptions(_dio.options);
|
||||
_dio.options.headers["content-type"] = "application/json";
|
||||
Response response = await _dio.get(url,
|
||||
queryParameters: queryParameters, cancelToken: cancelTag);
|
||||
//响应数据
|
||||
dynamic responseData = response.data;
|
||||
//数据解析
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
//转换
|
||||
Map<String, dynamic> responseMap = responseData;
|
||||
//
|
||||
int code = responseMap["code"];
|
||||
if (code == 200) {
|
||||
//业务代码处理正常
|
||||
//获取数据
|
||||
dynamic data = responseMap["data"];
|
||||
return ResponseInfo(data: data);
|
||||
} else {
|
||||
//业务代码异常
|
||||
return ResponseInfo.error(
|
||||
code: responseMap["code"], message: responseMap["message"]);
|
||||
}
|
||||
} else {
|
||||
return ResponseInfo.error(code: 503, message: "数据格式无法识别");
|
||||
}
|
||||
} catch (e, s) {
|
||||
//异常
|
||||
return errorController(e, s);
|
||||
}
|
||||
}
|
||||
|
||||
/// post 请求
|
||||
///[url]请求链接
|
||||
///[formDataMap]formData 请求参数
|
||||
///[jsonMap] JSON 格式
|
||||
Future<ResponseInfo> postRequest({
|
||||
String url,
|
||||
Map<String, dynamic> formDataMap,
|
||||
Map<String, dynamic> jsonMap,
|
||||
CancelToken cancelTag,
|
||||
}) async {
|
||||
FormData form;
|
||||
if (formDataMap != null) {
|
||||
form = FormData.fromMap(formDataMap);
|
||||
}
|
||||
|
||||
_dio.options = await buildOptions(_dio.options);
|
||||
_dio.options.headers["content-type"] = "application/json";
|
||||
|
||||
// _dio.options.headers["content-type"]="multipart/form-data";
|
||||
//发起post请求
|
||||
try {
|
||||
Response response = await _dio.post(url,
|
||||
data: form ?? jsonMap, cancelToken: cancelTag);
|
||||
//响应数据
|
||||
dynamic responseData = response.data;
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
Map<String, dynamic> responseMap = responseData;
|
||||
int code = responseMap["code"];
|
||||
if (code == 200) {
|
||||
//业务代码处理正常
|
||||
//获取数据
|
||||
dynamic data = responseMap["data"];
|
||||
return ResponseInfo(data: data);
|
||||
} else {
|
||||
//业务代码异常
|
||||
return ResponseInfo.error(
|
||||
code: responseMap["code"], message: responseMap["message"]);
|
||||
}
|
||||
} else {
|
||||
return ResponseInfo.error(code: 503, message: "数据格式无法识别");
|
||||
}
|
||||
} catch (e, s) {
|
||||
return errorController(e, s);
|
||||
}
|
||||
}
|
||||
|
||||
Future<ResponseInfo> errorController(e, StackTrace s) {
|
||||
ResponseInfo responseInfo = ResponseInfo();
|
||||
responseInfo.success = false;
|
||||
|
||||
//网络处理错误
|
||||
if (e is DioError) {
|
||||
DioError dioError = e;
|
||||
switch (dioError.type) {
|
||||
case DioErrorType.connectTimeout:
|
||||
responseInfo.message = "连接超时";
|
||||
break;
|
||||
case DioErrorType.sendTimeout:
|
||||
responseInfo.message = "请求超时";
|
||||
break;
|
||||
case DioErrorType.receiveTimeout:
|
||||
responseInfo.message = "响应超时";
|
||||
break;
|
||||
case DioErrorType.response:
|
||||
// 响应错误
|
||||
responseInfo.message = "响应错误";
|
||||
break;
|
||||
case DioErrorType.cancel:
|
||||
// 取消操作
|
||||
responseInfo.message = "已取消";
|
||||
break;
|
||||
case DioErrorType.other:
|
||||
// 默认自定义其他异常
|
||||
responseInfo.message = "网络请求异常";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//其他错误
|
||||
responseInfo.message = "未知错误";
|
||||
}
|
||||
responseInfo.success = false;
|
||||
return Future.value(responseInfo);
|
||||
}
|
||||
|
||||
Future<LoginInfo> loginRequest({
|
||||
String url,
|
||||
Map<String, dynamic> formDataMap,
|
||||
Map<String, dynamic> jsonMap,
|
||||
CancelToken cancelTag,
|
||||
}) async {
|
||||
FormData form;
|
||||
if (formDataMap != null) {
|
||||
form = FormData.fromMap(formDataMap);
|
||||
}
|
||||
|
||||
_dio.options = await buildOptions(_dio.options);
|
||||
_dio.options.headers["content-type"] = "application/json";
|
||||
|
||||
// _dio.options.headers["content-type"]="multipart/form-data";
|
||||
//发起post请求
|
||||
try {
|
||||
Response response = await _dio.post(url,
|
||||
data: form ?? jsonMap, cancelToken: cancelTag);
|
||||
//响应数据
|
||||
dynamic responseData = response.data;
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
Map<String, dynamic> responseMap = responseData;
|
||||
int code = responseMap["code"];
|
||||
if (code == 200) {
|
||||
//业务代码处理正常
|
||||
//获取数据
|
||||
dynamic token = responseMap["token"];
|
||||
return LoginInfo(token: token);
|
||||
} else {
|
||||
//业务代码异常
|
||||
return LoginInfo.error(
|
||||
code: responseMap["code"], msg: responseMap["message"]);
|
||||
}
|
||||
} else {
|
||||
return LoginInfo.error(code: 503, msg: "数据格式无法识别");
|
||||
}
|
||||
} catch (e, s) {
|
||||
return errorCtl(e, s);
|
||||
}
|
||||
}
|
||||
|
||||
Future<LoginInfo> errorCtl(e, StackTrace s) {
|
||||
LoginInfo loginInfo = LoginInfo();
|
||||
loginInfo.success = false;
|
||||
|
||||
//网络处理错误
|
||||
if (e is DioError) {
|
||||
DioError dioError = e;
|
||||
switch (dioError.type) {
|
||||
case DioErrorType.connectTimeout:
|
||||
loginInfo.msg = "连接超时";
|
||||
break;
|
||||
case DioErrorType.sendTimeout:
|
||||
loginInfo.msg = "请求超时";
|
||||
break;
|
||||
case DioErrorType.receiveTimeout:
|
||||
loginInfo.msg = "响应超时";
|
||||
break;
|
||||
case DioErrorType.response:
|
||||
// 响应错误
|
||||
loginInfo.msg = "响应错误";
|
||||
break;
|
||||
case DioErrorType.cancel:
|
||||
// 取消操作
|
||||
loginInfo.msg = "已取消";
|
||||
break;
|
||||
case DioErrorType.other:
|
||||
// 默认自定义其他异常
|
||||
loginInfo.msg = "网络请求异常";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//其他错误
|
||||
loginInfo.msg = "未知错误";
|
||||
}
|
||||
loginInfo.success = false;
|
||||
return Future.value(loginInfo);
|
||||
}
|
||||
|
||||
Future<BaseOptions> buildOptions(BaseOptions options) async {
|
||||
///请求header的配置
|
||||
options.headers["productId"] = Platform.isAndroid ? "Android" : "IOS";
|
||||
options.headers["application"] = "coalx";
|
||||
|
||||
//获取当前App的版本信息
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
String appName = packageInfo.appName;
|
||||
String packageName = packageInfo.packageName;
|
||||
String version = packageInfo.version;
|
||||
String buildNumber = packageInfo.buildNumber;
|
||||
options.headers["appVersion"] = "$version";
|
||||
|
||||
return Future.value(options);
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseInfo {
|
||||
bool success;
|
||||
int code;
|
||||
String message;
|
||||
dynamic data;
|
||||
|
||||
ResponseInfo(
|
||||
{this.success = true, this.code = 200, this.data, this.message = "请求成功"});
|
||||
|
||||
ResponseInfo.error(
|
||||
{this.success = false, this.code = 201, this.message = "请求异常"});
|
||||
}
|
||||
|
||||
class LoginInfo {
|
||||
bool success;
|
||||
int code;
|
||||
String msg;
|
||||
String token;
|
||||
|
||||
LoginInfo({this.success = true, this.code = 200, this.token, this.msg="登陆成功"});
|
||||
LoginInfo.error({this.success = false, this.code = 201, this.msg="请求异常"});
|
||||
}
|
||||
|
||||
class RegisterInfo {
|
||||
bool success;
|
||||
int code;
|
||||
String msg;
|
||||
|
||||
|
||||
RegisterInfo({this.success = true, this.code = 200, this.msg="注册成功"});
|
||||
RegisterInfo.error({this.success = false, this.code = 201, this.msg="请求异常"});
|
||||
}
|
9
lib/net/http_helper.dart
Normal file
@ -0,0 +1,9 @@
|
||||
class HttpHelper {
|
||||
static const String BASE_HOST = "http://192.168.40.50:8080/";
|
||||
//用户密码登录
|
||||
static const String login = BASE_HOST + "user/login";
|
||||
//检查更新
|
||||
static const String appVersion = BASE_HOST + "app/version";
|
||||
//获取文章列表
|
||||
static const String artList = BASE_HOST + "article/list";
|
||||
}
|
61
lib/net/log_interceptor.dart
Normal file
@ -0,0 +1,61 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
/*
|
||||
* 创建人: zhaollong
|
||||
* 创建时间:2019-09-02
|
||||
* 页面说明:dio 网络请求拦截日志
|
||||
* 功能性修改记录:
|
||||
*/
|
||||
class LogsInterceptors extends InterceptorsWrapper {
|
||||
@override
|
||||
void onRequest(
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) {
|
||||
super.onRequest(options, handler);
|
||||
print("\n================== 请求数据 ==========================");
|
||||
print("|请求url:${options.path}");
|
||||
print('|请求头: ' + options.headers.toString());
|
||||
print('|请求参数: ' + options.queryParameters.toString());
|
||||
print('|请求方法: ' + options.method);
|
||||
print("|contentType = ${options.contentType}");
|
||||
print('|请求时间: ' + DateTime.now().toString());
|
||||
if (options.data != null) {
|
||||
print('|请求数据: ' + options.data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
onResponse(
|
||||
Response response,
|
||||
ResponseInterceptorHandler handler,
|
||||
) {
|
||||
super.onResponse(response, handler);
|
||||
print("\n|================== 响应数据 ==========================");
|
||||
if (response != null) {
|
||||
print("|url = ${response.realUri.path}");
|
||||
print("|code = ${response.statusCode}");
|
||||
print("|data = ${response.data}");
|
||||
print('|返回时间: ' + DateTime.now().toString());
|
||||
print("\n");
|
||||
} else {
|
||||
print("|data = 请求错误 E409");
|
||||
print('|返回时间: ' + DateTime.now().toString());
|
||||
print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
onError(
|
||||
DioError e,
|
||||
ErrorInterceptorHandler handler,
|
||||
) {
|
||||
super.onError(e, handler);
|
||||
print("\n================== 错误响应数据 ======================");
|
||||
print("|type = ${e.type}");
|
||||
print("|message = ${e.message}");
|
||||
|
||||
print('|response = ${e.response}');
|
||||
print("\n");
|
||||
}
|
||||
}
|
597
lib/pages/birthcal_page.dart
Normal file
@ -0,0 +1,597 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fengshui_compass/models/et_date.dart';
|
||||
import 'package:fengshui_compass/pages/pan_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fengshui_compass/components/my_icon.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
import '../models/qimen.dart';
|
||||
|
||||
class BirthCalPage extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => _BirthCalState();
|
||||
}
|
||||
|
||||
class _BirthCalState extends State<BirthCalPage> {
|
||||
bool isLunar = false;
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
int hour;
|
||||
int minute;
|
||||
|
||||
int lunar_year;
|
||||
int lunar_month;
|
||||
int lunar_day;
|
||||
|
||||
// 阴盘或者拆补无闰, 奇门类型
|
||||
int qmType = 0;
|
||||
|
||||
Qimen qm;
|
||||
|
||||
WebViewController _controllerWebView;
|
||||
|
||||
final TextEditingController _controllerDate =
|
||||
TextEditingController(text: "点击右侧按钮选择日期");
|
||||
final TextEditingController _controllerTime =
|
||||
TextEditingController(text: "点击右侧按钮选择时间");
|
||||
|
||||
_showDatePicker() async {
|
||||
var date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(2100),
|
||||
locale: const Locale('zh'),
|
||||
);
|
||||
if (date == null) return;
|
||||
|
||||
setState(() {
|
||||
if (isLunar) {
|
||||
lunar_year = date.year;
|
||||
lunar_month = date.month;
|
||||
lunar_day = date.day;
|
||||
|
||||
_controllerDate.text = "${lunar_year}年${lunar_month}月${lunar_day}日";
|
||||
} else {
|
||||
year = date.year;
|
||||
month = date.month;
|
||||
day = date.day;
|
||||
|
||||
_controllerDate.text = "${year}年${month}月${day}日";
|
||||
}
|
||||
// _controllerDate.text = year.toString()+"年"+month.toString()+"月"+day.toString()+"日";
|
||||
});
|
||||
}
|
||||
|
||||
_showTimePicker() async {
|
||||
DateTime dd = DateTime.now();
|
||||
var hhh = dd.hour;
|
||||
var mmm = dd.minute;
|
||||
|
||||
var time = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay(hour: hhh, minute: mmm),
|
||||
builder: (BuildContext context, Widget child) {
|
||||
return Localizations(
|
||||
locale: const Locale('zh'),
|
||||
child: child,
|
||||
delegates: const <LocalizationsDelegate>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (time == null) return;
|
||||
print(time);
|
||||
setState(() {
|
||||
hour = time.hour;
|
||||
minute = time.minute;
|
||||
// _controllerTime.text = hour.toString()+"时"+minute.toString()+"分";
|
||||
_controllerTime.text = "${hour}时${minute}分";
|
||||
});
|
||||
}
|
||||
|
||||
// 通过公历算农历
|
||||
calLunar() {
|
||||
if (_controllerWebView != null) {
|
||||
_controllerWebView
|
||||
.runJavascriptReturningResult("flutterCallLunar($year,$month,$day)")
|
||||
.then((value) {
|
||||
String tmp = value
|
||||
.replaceAll(r'\"', '"')
|
||||
.replaceAll('"{', '{')
|
||||
.replaceAll('}"', '}');
|
||||
|
||||
setState(() {
|
||||
lunar_year = json.decode(tmp)['lYear'];
|
||||
lunar_month = json.decode(tmp)['lMonth'];
|
||||
lunar_day = json.decode(tmp)['lDay'];
|
||||
year = json.decode(tmp)['cYear'];
|
||||
|
||||
month = json.decode(tmp)['cMonth'];
|
||||
day = json.decode(tmp)['cDay'];
|
||||
|
||||
_controllerDate.text = "${lunar_year}年${lunar_month}月${lunar_day}日";
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 通过农历算公历
|
||||
calSolar() {
|
||||
if (_controllerWebView != null) {
|
||||
_controllerWebView
|
||||
.runJavascriptReturningResult(
|
||||
"flutterCallSolar($lunar_year,$lunar_month,$lunar_day)")
|
||||
.then((value) {
|
||||
String tmp = value
|
||||
.replaceAll(r'\"', '"')
|
||||
.replaceAll('"{', '{')
|
||||
.replaceAll('}"', '}');
|
||||
|
||||
setState(() {
|
||||
year = json.decode(tmp)['cYear'];
|
||||
month = json.decode(tmp)['cMonth'];
|
||||
day = json.decode(tmp)['cDay'];
|
||||
lunar_year = json.decode(tmp)['lYear'];
|
||||
lunar_month = json.decode(tmp)['lMonth'];
|
||||
lunar_day = json.decode(tmp)['lDay'];
|
||||
|
||||
_controllerDate.text = "${year}年${month}月${day}日";
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
calPan() {
|
||||
if (_controllerWebView != null) {
|
||||
if(qmType == 0) {
|
||||
_controllerWebView
|
||||
.runJavascriptReturningResult(
|
||||
"flutterCallPaiPan($year, $month, $day, $hour, $minute)")
|
||||
.then((value) {
|
||||
String tmp = value
|
||||
.replaceAll(r'\"', '"')
|
||||
.replaceAll('"{', '{')
|
||||
.replaceAll('}"', '}');
|
||||
qm = Qimen.fromJson(json.decode(tmp));
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PanPage(
|
||||
solarDate: EtDate(
|
||||
cYear: year,
|
||||
cMonth: month,
|
||||
cDay: day,
|
||||
lYear: lunar_year,
|
||||
lMonth: lunar_month,
|
||||
lDay: lunar_day,
|
||||
hour: hour,
|
||||
minute: minute)
|
||||
.getSolar(),
|
||||
lunarDate: EtDate(
|
||||
cYear: year,
|
||||
cMonth: month,
|
||||
cDay: day,
|
||||
lYear: lunar_year,
|
||||
lMonth: lunar_month,
|
||||
lDay: lunar_day,
|
||||
hour: hour,
|
||||
minute: minute)
|
||||
.getLunar(),
|
||||
jieqi: qm.jieqi,
|
||||
dunju: qm.dunju,
|
||||
bazi: qm.bazi,
|
||||
zhishi: qm.zhishi,
|
||||
zhifu: qm.zhifu,
|
||||
kong: qm.kong,
|
||||
qimenResult: qm.qimenResult
|
||||
)));
|
||||
});
|
||||
} else if (qmType == 1) {
|
||||
_controllerWebView
|
||||
.runJavascriptReturningResult(
|
||||
"flutterCallYinPan($year, $month, $day, $hour, $minute)")
|
||||
.then((value) {
|
||||
String tmp = value
|
||||
.replaceAll(r'\"', '"')
|
||||
.replaceAll('"{', '{')
|
||||
.replaceAll('}"', '}');
|
||||
qm = Qimen.fromJson(json.decode(tmp));
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PanPage(
|
||||
solarDate: EtDate(
|
||||
cYear: year,
|
||||
cMonth: month,
|
||||
cDay: day,
|
||||
lYear: lunar_year,
|
||||
lMonth: lunar_month,
|
||||
lDay: lunar_day,
|
||||
hour: hour,
|
||||
minute: minute)
|
||||
.getSolar(),
|
||||
lunarDate: EtDate(
|
||||
cYear: year,
|
||||
cMonth: month,
|
||||
cDay: day,
|
||||
lYear: lunar_year,
|
||||
lMonth: lunar_month,
|
||||
lDay: lunar_day,
|
||||
hour: hour,
|
||||
minute: minute)
|
||||
.getLunar(),
|
||||
jieqi: qm.jieqi,
|
||||
dunju: qm.dunju,
|
||||
bazi: qm.bazi,
|
||||
zhishi: qm.zhishi,
|
||||
zhifu: qm.zhifu,
|
||||
kong: qm.kong,
|
||||
qimenResult: qm.qimenResult
|
||||
)));
|
||||
});
|
||||
} else {
|
||||
_controllerWebView
|
||||
.runJavascriptReturningResult(
|
||||
"flutterCallPaiPan($year, $month, $day, $hour, $minute)")
|
||||
.then((value) {
|
||||
String tmp = value
|
||||
.replaceAll(r'\"', '"')
|
||||
.replaceAll('"{', '{')
|
||||
.replaceAll('}"', '}');
|
||||
qm = Qimen.fromJson(json.decode(tmp));
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PanPage(
|
||||
solarDate: EtDate(
|
||||
cYear: year,
|
||||
cMonth: month,
|
||||
cDay: day,
|
||||
lYear: lunar_year,
|
||||
lMonth: lunar_month,
|
||||
lDay: lunar_day,
|
||||
hour: hour,
|
||||
minute: minute)
|
||||
.getSolar(),
|
||||
lunarDate: EtDate(
|
||||
cYear: year,
|
||||
cMonth: month,
|
||||
cDay: day,
|
||||
lYear: lunar_year,
|
||||
lMonth: lunar_month,
|
||||
lDay: lunar_day,
|
||||
hour: hour,
|
||||
minute: minute)
|
||||
.getLunar(),
|
||||
jieqi: qm.jieqi,
|
||||
dunju: qm.dunju,
|
||||
bazi: qm.bazi,
|
||||
zhishi: qm.zhishi,
|
||||
zhifu: qm.zhifu,
|
||||
kong: qm.kong,
|
||||
qimenResult: qm.qimenResult
|
||||
)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// webview
|
||||
String url = "file:///android_asset/flutter_assets/assets/index.html";
|
||||
|
||||
///JS显示弹窗使用
|
||||
// WebView.platform = SurfaceAndroidWebView();
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage("assets/images/bg1.png"), fit: BoxFit.cover)),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
title: const Text(
|
||||
'在线起局',
|
||||
style: TextStyle(color: Color(0xCFA77300)),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: Column(
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.only(top: 150)),
|
||||
Container(
|
||||
width: 400,
|
||||
height: 400,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30)),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
// 阳历按钮
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isLunar = !isLunar;
|
||||
});
|
||||
// 如果有农历数据,将农历数据转换成公历,并显示到输入框
|
||||
// calLunar();
|
||||
if (_controllerWebView != null) {
|
||||
_controllerWebView
|
||||
.runJavascriptReturningResult(
|
||||
"flutterCallSolar($lunar_year,$lunar_month,$lunar_day)")
|
||||
.then((value) {
|
||||
String tmp = value
|
||||
.replaceAll(r'\"', '"')
|
||||
.replaceAll('"{', '{')
|
||||
.replaceAll('}"', '}');
|
||||
|
||||
setState(() {
|
||||
year = json.decode(tmp)['cYear'];
|
||||
month = json.decode(tmp)['cMonth'];
|
||||
day = json.decode(tmp)['cDay'];
|
||||
lunar_year = json.decode(tmp)['lYear'];
|
||||
lunar_month = json.decode(tmp)['lMonth'];
|
||||
lunar_day = json.decode(tmp)['lDay'];
|
||||
|
||||
_controllerDate.text =
|
||||
"${year}年${month}月${day}日";
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"阳历",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: isLunar ? Colors.black : Colors.white),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
isLunar
|
||||
? Colors.transparent
|
||||
: Color(0xCFA77300)),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(30)))),
|
||||
),
|
||||
)),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
// 阴历按钮
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isLunar = !isLunar;
|
||||
});
|
||||
// 如果有公历数据,将公历数据转换为农历
|
||||
// calLunar();
|
||||
if (_controllerWebView != null) {
|
||||
_controllerWebView
|
||||
.runJavascriptReturningResult(
|
||||
"flutterCallLunar($year,$month,$day)")
|
||||
.then((value) {
|
||||
String tmp = value
|
||||
.replaceAll(r'\"', '"')
|
||||
.replaceAll('"{', '{')
|
||||
.replaceAll('}"', '}');
|
||||
|
||||
setState(() {
|
||||
lunar_year = json.decode(tmp)['lYear'];
|
||||
lunar_month = json.decode(tmp)['lMonth'];
|
||||
lunar_day = json.decode(tmp)['lDay'];
|
||||
year = json.decode(tmp)['cYear'];
|
||||
|
||||
month = json.decode(tmp)['cMonth'];
|
||||
day = json.decode(tmp)['cDay'];
|
||||
|
||||
_controllerDate.text =
|
||||
"${lunar_year}年${lunar_month}月${lunar_day}日";
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"阴历",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: isLunar ? Colors.white : Colors.black),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
isLunar
|
||||
? Color(0xCFA77300)
|
||||
: Colors.transparent),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(30)))),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 40)),
|
||||
Row(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: Text("出生日期:", style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controllerDate,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.all(10.0),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
)),
|
||||
readOnly: true,
|
||||
)),
|
||||
IconButton(
|
||||
onPressed: _showDatePicker,
|
||||
icon: Icon(MyIcons.icon_rili))
|
||||
],
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 40)),
|
||||
Row(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: Text(
|
||||
"出生时间:",
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controllerTime,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(10.0),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
)),
|
||||
readOnly: true,
|
||||
)),
|
||||
IconButton(
|
||||
onPressed: _showTimePicker,
|
||||
icon: Icon(Icons.timelapse_outlined))
|
||||
],
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 40)),
|
||||
// 切换奇门类型,单选框
|
||||
Row(children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Radio(value: 0, groupValue: qmType, onChanged: (v){
|
||||
setState(() {
|
||||
qmType = v;
|
||||
});
|
||||
}),
|
||||
const Text("拆补法")
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Radio(value: 1, groupValue: qmType, onChanged: (v){
|
||||
setState(() {
|
||||
qmType = v;
|
||||
});
|
||||
}),
|
||||
const Text("阴盘")
|
||||
],
|
||||
)
|
||||
],),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20),
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
if ((year != null) &&
|
||||
(month != null) &&
|
||||
(day != null) &&
|
||||
(hour != null) &&
|
||||
(minute != null) &&
|
||||
(lunar_year == null)) {
|
||||
// 有公历没农历,先转化农历,再起局
|
||||
calLunar();
|
||||
calPan();
|
||||
} else if ((lunar_year != null) &&
|
||||
(lunar_month != null) &&
|
||||
(lunar_day != null) &&
|
||||
(hour != null) &&
|
||||
(minute != null) &&
|
||||
(year == null)) {
|
||||
// 有农历没公历,先转化公历,再起局
|
||||
calSolar();
|
||||
calPan();
|
||||
} else if ((year != null) &&
|
||||
(lunar_year != null) &&
|
||||
(hour != null)) {
|
||||
// 直接起局
|
||||
calPan();
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: "请输入出生日期和时间",
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
fontSize: 20.0);
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
"奇门遁甲起局",
|
||||
style:
|
||||
TextStyle(color: Colors.white, fontSize: 22),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
Color(0xCFA77300))),
|
||||
),
|
||||
))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage: true,
|
||||
child: Container(
|
||||
width: 1,
|
||||
height: 1,
|
||||
child: WebView(
|
||||
initialUrl: url,
|
||||
javascriptMode: JavascriptMode.unrestricted,
|
||||
javascriptChannels: {
|
||||
JavascriptChannel(
|
||||
name: "toast",
|
||||
onMessageReceived: (message) {
|
||||
// Fluttertoast.showToast(msg: message.message.toString());
|
||||
print(message.message.toString());
|
||||
}),
|
||||
},
|
||||
onWebViewCreated: (controller) {
|
||||
_controllerWebView = controller;
|
||||
},
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
430
lib/pages/compass_page.dart
Normal file
@ -0,0 +1,430 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fengshui_compass/components/my_icon.dart';
|
||||
import 'package:fengshui_compass/pages/login_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_serial_port_api/flutter_serial_port_api.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
|
||||
import '../utils/recv_parse.dart';
|
||||
|
||||
class CompassPage extends StatefulWidget {
|
||||
const CompassPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _CompassState();
|
||||
}
|
||||
|
||||
class _CompassState extends State<CompassPage> {
|
||||
// 串口相关
|
||||
bool isPortOpened = false;
|
||||
SerialPort _serialPort;
|
||||
StreamSubscription _subscription;
|
||||
|
||||
// 传感器状态
|
||||
bool isLock = true;
|
||||
bool isUpClose = true;
|
||||
bool isSideClose = true;
|
||||
|
||||
double w_x = 0.5;
|
||||
double w_y = 0.5;
|
||||
// 与磁北极夹角
|
||||
double myaw = 0.0;
|
||||
|
||||
String distance = "";
|
||||
|
||||
var lista = [];
|
||||
var listb = [];
|
||||
var listc = [];
|
||||
|
||||
void initDevice() {
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initPort();
|
||||
}
|
||||
|
||||
Future<void> initPort() async {
|
||||
// 20ms接收一次串口数据,防抖,定义接收缓存
|
||||
final debounceTransformer = StreamTransformer<Uint8List, dynamic>.fromBind(
|
||||
(s) => s.transform(debounceBuffer(const Duration(milliseconds: 20))));
|
||||
|
||||
if (!isPortOpened) {
|
||||
Device theDevice = Device("/dev/ttyS2", "/dev/ttyS2");
|
||||
var serialPort = await FlutterSerialPortApi.createSerialPort(
|
||||
theDevice, 115200,
|
||||
parity: 0, dataBits: 8, stopBit: 1);
|
||||
|
||||
bool openResult = await serialPort.open();
|
||||
setState(() {
|
||||
_serialPort = serialPort;
|
||||
isPortOpened = openResult;
|
||||
});
|
||||
await openRange();
|
||||
|
||||
_subscription = _serialPort.receiveStream
|
||||
.transform(debounceTransformer)
|
||||
.listen((recv) {
|
||||
// recvData - 9E01040100000000000000065E
|
||||
String recvData = formatReceivedData(recv);
|
||||
print("Receive format: $recvData");
|
||||
parsingRecvCom(recvData);
|
||||
// 解析收到的结果,得到陀螺、地磁、测距结果
|
||||
});
|
||||
|
||||
await openCompass();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> closePort() async {
|
||||
bool closeResult = await _serialPort.close();
|
||||
setState(() {
|
||||
isPortOpened = !closeResult;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> openCompass() async {
|
||||
print("compass open");
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7E01020100000000000000010D0A")));
|
||||
}
|
||||
|
||||
Future<void> closeCompass() async {
|
||||
print("compass close");
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7E01020000000000000000010D0A")));
|
||||
}
|
||||
|
||||
Future<void> openSideLaser() async {
|
||||
print("side open");
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7E01010100000000000000010D0A")));
|
||||
}
|
||||
|
||||
Future<void> closeSideLaser() async {
|
||||
print("side close");
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7E01010000000000000000010D0A")));
|
||||
}
|
||||
|
||||
Future<void> openUpLaser() async {
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7e01010300000000000000010d0a")));
|
||||
}
|
||||
|
||||
Future<void> closeUpLaser() async {
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7e01010200000000000000010d0a")));
|
||||
}
|
||||
|
||||
Future<void> openRange() async {
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7e01040100000000000000010d0a")));
|
||||
}
|
||||
|
||||
Future<void> raging() async {
|
||||
if (!isLock) {
|
||||
switchCompass();
|
||||
}
|
||||
_serialPort
|
||||
.write(Uint8List.fromList(hexToUnits("7e01040200000000000000010d0a")));
|
||||
}
|
||||
|
||||
Future<void> switchCompass() async {
|
||||
if (isLock) {
|
||||
await openCompass();
|
||||
} else {
|
||||
await closeCompass();
|
||||
setState(() {
|
||||
w_x = 0.5;
|
||||
w_y = 0.5;
|
||||
});
|
||||
}
|
||||
setState(() {
|
||||
isLock = !isLock;
|
||||
});
|
||||
}
|
||||
|
||||
loginAction() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LoginPage()));
|
||||
}
|
||||
|
||||
parsingRecvCom(str) {
|
||||
if (str.contains("9E010201") &&
|
||||
(str.length > (str.indexOf("9E010201") + 50))) {
|
||||
var roll_l = hexToInt(str.substring(8, 10));
|
||||
var roll_h = hexToInt(str.substring(10, 12));
|
||||
var pitch_l = hexToInt(str.substring(12, 14));
|
||||
var pitch_h = hexToInt(str.substring(14, 16));
|
||||
// var yaw_l = hexToInt(str.substring(16, 18));
|
||||
// var yaw_h = hexToInt(str.substring(18, 20));
|
||||
|
||||
var pos = str.indexOf("9E010301");
|
||||
// var mx_h = hexToInt(str.substring(pos+8, pos+10));
|
||||
// var mx_l = hexToInt(str.substring(pos+10, pos+12));
|
||||
// var my_h = hexToInt(str.substring(pos+12, pos+14));
|
||||
|
||||
var myaw_flag = hexToInt(str.substring(pos+14, pos+16));
|
||||
var myaw_h = hexToInt(str.substring(pos+16, pos+18));
|
||||
var myaw_l = hexToInt(str.substring(pos+18, pos+20));
|
||||
|
||||
var roll_tmp = (roll_h * 256 + roll_l) * 180 /32768;
|
||||
var pitch_tmp = (pitch_h * 256 + pitch_l) * 180 /32768;
|
||||
// var yaw = (yaw_h * 256 + yaw_l) * 180 /32768;
|
||||
|
||||
// -180~180
|
||||
var ff = myaw_flag == 1 ? -1 : 1;
|
||||
var temp_myaw = (myaw_h *256 + myaw_l) * 0.01 * ff + 180;
|
||||
|
||||
if (roll_tmp>180) roll_tmp = roll_tmp -360;
|
||||
if (pitch_tmp>180) pitch_tmp = pitch_tmp -360;
|
||||
|
||||
|
||||
|
||||
var w_x_tmp = 0.0;
|
||||
var w_y_tmp = 0.0;
|
||||
|
||||
// 倾角<30度
|
||||
var w_total = sqrt(roll_tmp.abs()*roll_tmp.abs() + pitch_tmp.abs()*pitch_tmp.abs());
|
||||
|
||||
if (w_total <= 30) {
|
||||
w_y_tmp = 0.5 - 0.07 * roll_tmp / 30.0;
|
||||
w_x_tmp = 0.5 - 0.07 * pitch_tmp / 30.0;
|
||||
} else if (w_total > 30) {
|
||||
//todo
|
||||
w_y_tmp = 0.5 - 0.07 * w_total /30.0;
|
||||
w_x_tmp = 0.5 - 0.07 * w_total / 30.0;
|
||||
}
|
||||
|
||||
// todo 其他情况
|
||||
|
||||
|
||||
var meanValue;
|
||||
|
||||
if (lista.length < 20) {
|
||||
lista.add(temp_myaw);
|
||||
meanValue = null;
|
||||
} else {
|
||||
lista.removeAt(0);
|
||||
lista.add(temp_myaw);
|
||||
meanValue = lista.map((e) => e).reduce((a, b) => a+b) / lista.length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setState(() {
|
||||
// print("roll: $roll_tmp pitch: $pitch_tmp");
|
||||
// print("w_x: $w_x_tmp, w_y: $w_y_tmp");
|
||||
// if (meanValue != null) {
|
||||
// myaw = meanValue;
|
||||
// } else {
|
||||
// myaw = 0;
|
||||
// }
|
||||
myaw = temp_myaw;
|
||||
|
||||
w_x = w_x_tmp;
|
||||
w_y = w_y_tmp;
|
||||
|
||||
// 水平仪 0.5+-0.07范围
|
||||
// x旋转y在动 y旋转x在动 roll改变y坐标,pitch改变x坐标
|
||||
|
||||
});
|
||||
} else if (str.contains("9E010401") &&
|
||||
(str.length >= ((str.indexOf("9E010401") + 26)))) {
|
||||
var pos = str.indexOf("9E010401");
|
||||
int rh = hexToInt(str.substring(pos + 16, pos + 18));
|
||||
int rl = hexToInt(str.substring(pos + 18, pos + 20));
|
||||
int result = rh * 256 + rl;
|
||||
print("测距$result");
|
||||
|
||||
setState(() {
|
||||
distance = (result.toDouble() / 1000).toStringAsFixed(2);
|
||||
});
|
||||
} else {}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage("assets/images/bg.png"), fit: BoxFit.cover)),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
title: const Text(
|
||||
'定盘星',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
leading: IconButton(
|
||||
color: Colors.amber,
|
||||
icon: Icon(isLock ? MyIcons.icon_mima : MyIcons.icon_jiesuo),
|
||||
onPressed: switchCompass,
|
||||
),
|
||||
actions: [
|
||||
//todo
|
||||
// 更改背景图
|
||||
IconButton(
|
||||
color: Colors.amber,
|
||||
icon: Icon(Icons.person, size: 22,),
|
||||
onPressed: loginAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 600),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// 罗盘
|
||||
Column(
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.only(top: 145)),
|
||||
Row(
|
||||
children: [
|
||||
Spacer(flex: 1),
|
||||
Container(
|
||||
width: 700,
|
||||
height: 700,
|
||||
child: Stack(
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: myaw * pi /360,
|
||||
child: const Image(
|
||||
image:
|
||||
AssetImage("assets/images/compass.png"),
|
||||
fit: BoxFit.fill),
|
||||
),
|
||||
Align(
|
||||
alignment: FractionalOffset(w_x, w_y),
|
||||
child: const Image(
|
||||
image: AssetImage("assets/images/water.png"),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(flex: 1),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
// 最上面一行, lock azimuth login
|
||||
Positioned(
|
||||
top: 5,
|
||||
child: Column(
|
||||
children: [
|
||||
const Image(
|
||||
width: 15,
|
||||
height: 15,
|
||||
image: AssetImage("assets/images/arrow.png"),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
Text(
|
||||
// "${azimuth.toStringAsFixed(2)}",
|
||||
myaw.toStringAsFixed(2),
|
||||
style: const TextStyle(
|
||||
color: Colors.amber, fontSize: 36),
|
||||
),
|
||||
],
|
||||
)),
|
||||
// 最下面一行,ranging value openlaser
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
left: 50,
|
||||
child: IconButton(
|
||||
onPressed: raging,
|
||||
icon: const Icon(
|
||||
MyIcons.icon_celiang,
|
||||
size: 34,
|
||||
),
|
||||
color: Colors.amber)),
|
||||
const Positioned(
|
||||
width: 180,
|
||||
height: 90,
|
||||
bottom: 60,
|
||||
child: Image(
|
||||
image: AssetImage("assets/images/range_input.png"),
|
||||
fit: BoxFit.contain,
|
||||
)),
|
||||
Positioned(
|
||||
width: 180,
|
||||
height: 90,
|
||||
bottom: 60,
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 15),
|
||||
child: Text(
|
||||
"${distance} m",
|
||||
style: const TextStyle(
|
||||
color: Colors.amber, fontSize: 28),
|
||||
),
|
||||
),
|
||||
)),
|
||||
|
||||
Positioned(
|
||||
bottom: 60,
|
||||
right: 40,
|
||||
height: 120,
|
||||
width: 100,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
color: Colors.amber,
|
||||
onPressed: () {
|
||||
if (isUpClose) {
|
||||
openUpLaser();
|
||||
} else {
|
||||
closeUpLaser();
|
||||
}
|
||||
setState(() {
|
||||
isUpClose = !isUpClose;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
isUpClose
|
||||
? MyIcons.icon_shangdeng
|
||||
: MyIcons.icon_shangdnegguanbi,
|
||||
size: 36)),
|
||||
IconButton(
|
||||
color: Colors.amber,
|
||||
onPressed: () {
|
||||
if (isSideClose) {
|
||||
openSideLaser();
|
||||
} else {
|
||||
closeSideLaser();
|
||||
}
|
||||
setState(() {
|
||||
isSideClose = !isSideClose;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
isSideClose
|
||||
? MyIcons.icon_zuoyoudneg
|
||||
: MyIcons.icon_zuoyoudengguanbi,
|
||||
size: 32,
|
||||
))
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
229
lib/pages/login_page.dart
Normal file
@ -0,0 +1,229 @@
|
||||
import 'package:fengshui_compass/components/custom_textfield_widget.dart';
|
||||
import 'package:fengshui_compass/models/login_bean.dart';
|
||||
import 'package:fengshui_compass/net/dio_utils.dart';
|
||||
import 'package:fengshui_compass/pages/register_page.dart';
|
||||
import 'package:fengshui_compass/utils/token_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import '../components/controller.dart';
|
||||
import '../models/user_bean.dart';
|
||||
import '../utils/user_helper.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
|
||||
final String username;
|
||||
const LoginPage({Key key, this.username}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
final FocusNode _userNameFocusNode = FocusNode();
|
||||
final FocusNode _passwordFocusNode = FocusNode();
|
||||
|
||||
final TextEditingController _userNameEditController = TextEditingController();
|
||||
final TextEditingController _passwordEditController = TextEditingController();
|
||||
|
||||
bool _passwordShow = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if(widget.username != null) {
|
||||
setState(() {
|
||||
_userNameEditController.text = widget.username;
|
||||
});
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// elevation: 0,
|
||||
// centerTitle: true,
|
||||
// title: const Text(W
|
||||
// '登陆',
|
||||
// style: TextStyle(color: Colors.white),
|
||||
// ),
|
||||
// ),
|
||||
body: SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_userNameFocusNode.unfocus();
|
||||
_passwordFocusNode.unfocus();
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
const Hero(
|
||||
tag: "logo",
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Image(
|
||||
width: double.infinity,
|
||||
image: AssetImage("assets/images/head.png"),
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 250,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Text(
|
||||
"登录",
|
||||
style: TextStyle(
|
||||
fontSize: 32, fontWeight: FontWeight.w600),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 25,
|
||||
top: 25,
|
||||
child: CloseButton(
|
||||
color: Colors.white,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)),
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
right: 180,
|
||||
left: 180,
|
||||
child: Column(
|
||||
children: [
|
||||
// 输入表单
|
||||
TextFieldWidget(
|
||||
hintText: "手机号",
|
||||
submit: (value) {
|
||||
if (value.length != 11) {
|
||||
Fluttertoast.showToast(msg: "请输入11位手机号");
|
||||
FocusScope.of(context).requestFocus(_userNameFocusNode);
|
||||
return;
|
||||
}
|
||||
_userNameFocusNode.unfocus();
|
||||
FocusScope.of(context).requestFocus(_passwordFocusNode);
|
||||
},
|
||||
focusNode: _userNameFocusNode,
|
||||
prefixIconData: Icons.phone_android_outlined,
|
||||
controller: _userNameEditController,
|
||||
obscureText: false,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFieldWidget(
|
||||
hintText: "密码",
|
||||
submit: (value) {
|
||||
if(value.length < 5) {
|
||||
Fluttertoast.showToast(msg: "请输入5位以上密码");
|
||||
FocusScope.of(context).requestFocus(_passwordFocusNode);
|
||||
return;
|
||||
}
|
||||
_userNameFocusNode.unfocus();
|
||||
_passwordFocusNode.unfocus();
|
||||
submitFunction();
|
||||
},
|
||||
focusNode: _passwordFocusNode,
|
||||
prefixIconData: Icons.lock_open_outlined,
|
||||
suffixIconData: _passwordShow
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
obscureText: _passwordShow,
|
||||
controller: _passwordEditController,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_passwordShow = !_passwordShow;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
// 登录
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
submitFunction();
|
||||
},
|
||||
child: const Text("登录"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text("没有账号, "),
|
||||
TextButton(onPressed: (){
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RegisterPage()));
|
||||
}, child: const Text("点击注册"))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void submitFunction() async{
|
||||
// 获取输入用户名密码
|
||||
String userName = _userNameEditController.text;
|
||||
String password = _passwordEditController.text;
|
||||
|
||||
if(userName.trim().length != 11) {
|
||||
Fluttertoast.showToast(msg: "请输入11位手机号");
|
||||
return;
|
||||
}
|
||||
|
||||
if(password.trim().length < 5) {
|
||||
Fluttertoast.showToast(msg: "请输入5位以上密码");
|
||||
}
|
||||
|
||||
Map<String, dynamic> map = {
|
||||
"username": userName,
|
||||
"password": password,
|
||||
"uuid": "",
|
||||
"code": ""
|
||||
};
|
||||
|
||||
LoginInfo responseInfo = await DioUtils.instance.loginRequest(
|
||||
url: "http://120.26.107.74:1619/login",
|
||||
jsonMap: map,
|
||||
);
|
||||
|
||||
if (responseInfo.success) {
|
||||
Map<String, dynamic> res = {
|
||||
"token": responseInfo.token.toString()
|
||||
};
|
||||
LoginBean loginBean = LoginBean.fromMap(res);
|
||||
TokenHelper.getInstance.loginBean = loginBean;
|
||||
|
||||
Fluttertoast.showToast(msg: "登录成功");
|
||||
|
||||
//关闭当前页面
|
||||
Navigator.of(context).pop(true);
|
||||
//发送消息更新我的页面显示内容
|
||||
loginStreamController.add(0);
|
||||
|
||||
} else {
|
||||
//登录失败页面小提示
|
||||
Fluttertoast.showToast(msg: responseInfo.msg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
1036
lib/pages/pan_page.dart
Normal file
83
lib/pages/personal_login_page.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:fengshui_compass/pages/personal_page.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../bottom_navigation_widget.dart';
|
||||
import '../utils/navigator_utils.dart';
|
||||
import '../utils/token_helper.dart';
|
||||
|
||||
// 用户未登录页面
|
||||
class PersonalLoginPage extends StatefulWidget {
|
||||
const PersonalLoginPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PersonalLoginPage> createState() => _PersonalLoginPageState();
|
||||
}
|
||||
|
||||
class _PersonalLoginPageState extends State<PersonalLoginPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isLogin = TokenHelper.getInstance.isLogin;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("个人中心"),
|
||||
),
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children:[
|
||||
Padding(padding: EdgeInsets.only(top: 80)),
|
||||
SizedBox(height: 120,child: ClipRRect(borderRadius: BorderRadius.circular(60),child: Image.asset("assets/images/ic_launcher.png"),),),
|
||||
Padding(padding: EdgeInsets.only(top: 30)),
|
||||
ListTile(
|
||||
title: Text("关于我们"),
|
||||
trailing: const Icon(Icons.arrow_forward_ios_sharp),
|
||||
leading: const Icon(Icons.account_circle),
|
||||
onTap: (){},
|
||||
),
|
||||
ListTile(
|
||||
title: Text("退出登陆"),
|
||||
trailing: const Icon(Icons.arrow_forward_ios_sharp),
|
||||
leading: const Icon(Icons.cancel_rounded),
|
||||
onTap: () async {
|
||||
bool isExit = await showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context){
|
||||
return CupertinoAlertDialog(
|
||||
title: const Text("温馨提示"),
|
||||
content: Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: const Text("是否确定退出个人中心?"),
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text("取消"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text("退出"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
if (isExit) {
|
||||
TokenHelper.getInstance.clear();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) {
|
||||
return BottomNavigationWidget();
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
100
lib/pages/personal_nologin_page.dart
Normal file
@ -0,0 +1,100 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/scheduler.dart' show timeDilation;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'login_page.dart';
|
||||
|
||||
// 用户未登录页面
|
||||
class PersonalNoLoginPage extends StatefulWidget {
|
||||
const PersonalNoLoginPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PersonalNoLoginPage> createState() => _PersonalNoLoginPageState();
|
||||
}
|
||||
|
||||
class _PersonalNoLoginPageState extends State<PersonalNoLoginPage> {
|
||||
bool isDown = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
timeDilation = 2.0;
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage("assets/images/bg1.png"), fit: BoxFit.cover)),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
title: const Text(
|
||||
'我的',
|
||||
style: TextStyle(color: Color(0xCFA77300)),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTapDown: (TapDownDetails details) {
|
||||
// 按下
|
||||
setState(() {
|
||||
isDown = true;
|
||||
});
|
||||
},
|
||||
onTapCancel: () {
|
||||
setState(() {
|
||||
isDown = false;
|
||||
});
|
||||
},
|
||||
onTap: () {
|
||||
// 跳转登录页面
|
||||
setState(() {
|
||||
isDown = false;
|
||||
});
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) => const LoginPage()));
|
||||
},
|
||||
child: buildHero(),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Hero buildHero() {
|
||||
return Hero(
|
||||
tag: "logo",
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: buildContainer(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Container buildContainer() {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
width: 96,
|
||||
height: 96,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xCFA77300),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
boxShadow: isDown
|
||||
? [
|
||||
const BoxShadow(
|
||||
offset: Offset(3, 4), color: Colors.black, blurRadius: 13)
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: const Text(
|
||||
"登录",
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
42
lib/pages/personal_page.dart
Normal file
@ -0,0 +1,42 @@
|
||||
import 'package:fengshui_compass/components/controller.dart';
|
||||
import 'package:fengshui_compass/pages/personal_login_page.dart';
|
||||
import 'package:fengshui_compass/pages/personal_nologin_page.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../utils/token_helper.dart';
|
||||
|
||||
class PersonalPage extends StatefulWidget {
|
||||
const PersonalPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PersonalPage> createState() => _PersonalPageState();
|
||||
}
|
||||
|
||||
class _PersonalPageState extends State<PersonalPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
loginStreamController.stream.listen((event) {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
loginStreamController.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 判断用户是否登陆
|
||||
if (TokenHelper.getInstance.isLogin) {
|
||||
// 构建已登录页面
|
||||
return PersonalLoginPage();
|
||||
} else {
|
||||
// 构建未登录页面
|
||||
return PersonalNoLoginPage();
|
||||
}
|
||||
}
|
||||
}
|
223
lib/pages/register_page.dart
Normal file
@ -0,0 +1,223 @@
|
||||
import 'package:fengshui_compass/pages/login_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import '../components/controller.dart';
|
||||
import '../components/custom_textfield_widget.dart';
|
||||
import '../net/dio_utils.dart';
|
||||
|
||||
class RegisterPage extends StatefulWidget {
|
||||
const RegisterPage({Key key}) : super(key: key);
|
||||
|
||||
|
||||
@override
|
||||
State<RegisterPage> createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
final FocusNode _userNameFocusNode = FocusNode();
|
||||
final FocusNode _passwordFocusNode = FocusNode();
|
||||
final FocusNode _rePasswordFocusNode = FocusNode();
|
||||
|
||||
final TextEditingController _userNameEditController = TextEditingController();
|
||||
final TextEditingController _passwordEditController = TextEditingController();
|
||||
final TextEditingController _rePasswordEditController = TextEditingController();
|
||||
|
||||
bool _passwordShow = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// elevation: 0,
|
||||
// centerTitle: true,
|
||||
// title: const Text(W
|
||||
// '登陆',
|
||||
// style: TextStyle(color: Colors.white),
|
||||
// ),
|
||||
// ),
|
||||
body: SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_userNameFocusNode.unfocus();
|
||||
_passwordFocusNode.unfocus();
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
const Hero(
|
||||
tag: "logo",
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Image(
|
||||
width: double.infinity,
|
||||
image: AssetImage("assets/images/head.png"),
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 250,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Text(
|
||||
"注册",
|
||||
style: TextStyle(
|
||||
fontSize: 32, fontWeight: FontWeight.w600),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 25,
|
||||
top: 25,
|
||||
child: CloseButton(
|
||||
color: Colors.white,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)),
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
right: 180,
|
||||
left: 180,
|
||||
child: Column(
|
||||
children: [
|
||||
// 输入表单
|
||||
TextFieldWidget(
|
||||
hintText: "手机号",
|
||||
submit: (value) {
|
||||
if (value.length != 11) {
|
||||
Fluttertoast.showToast(msg: "请输入11位手机号");
|
||||
FocusScope.of(context).requestFocus(_userNameFocusNode);
|
||||
return;
|
||||
}
|
||||
_userNameFocusNode.unfocus();
|
||||
FocusScope.of(context).requestFocus(_passwordFocusNode);
|
||||
},
|
||||
focusNode: _userNameFocusNode,
|
||||
prefixIconData: Icons.phone_android_outlined,
|
||||
controller: _userNameEditController,
|
||||
obscureText: false,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFieldWidget(
|
||||
hintText: "密码",
|
||||
submit: (value) {
|
||||
if(value.length < 5) {
|
||||
Fluttertoast.showToast(msg: "请输入5位以上密码");
|
||||
FocusScope.of(context).requestFocus(_passwordFocusNode);
|
||||
return;
|
||||
}
|
||||
_userNameFocusNode.unfocus();
|
||||
_passwordFocusNode.unfocus();
|
||||
FocusScope.of(context).requestFocus(_rePasswordFocusNode);
|
||||
},
|
||||
focusNode: _passwordFocusNode,
|
||||
prefixIconData: Icons.lock_open_outlined,
|
||||
suffixIconData: _passwordShow
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
obscureText: _passwordShow,
|
||||
controller: _passwordEditController,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_passwordShow = !_passwordShow;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFieldWidget(
|
||||
hintText: "再次输入密码",
|
||||
submit: (value) {
|
||||
// 校验两次密码是否一致
|
||||
if(value != _passwordEditController.text) {
|
||||
Fluttertoast.showToast(msg: "两次密码不一致");
|
||||
FocusScope.of(context).requestFocus(_rePasswordFocusNode);
|
||||
return;
|
||||
}
|
||||
_userNameFocusNode.unfocus();
|
||||
_passwordFocusNode.unfocus();
|
||||
_rePasswordFocusNode.unfocus();
|
||||
submitFunction();
|
||||
},
|
||||
focusNode: _rePasswordFocusNode,
|
||||
prefixIconData: Icons.lock_open_outlined,
|
||||
suffixIconData: _passwordShow
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
obscureText: _passwordShow,
|
||||
controller: _rePasswordEditController,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_passwordShow = !_passwordShow;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
// 注册
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
submitFunction();
|
||||
},
|
||||
child: const Text("注册"),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> submitFunction() async {
|
||||
// 获取输入用户名密码
|
||||
String userName = _userNameEditController.text;
|
||||
String password = _passwordEditController.text;
|
||||
|
||||
if(userName.trim().length != 11) {
|
||||
Fluttertoast.showToast(msg: "请输入11位手机号");
|
||||
return;
|
||||
}
|
||||
|
||||
if(password.trim().length < 5) {
|
||||
Fluttertoast.showToast(msg: "请输入5位以上密码");
|
||||
}
|
||||
|
||||
Map<String, dynamic> map = {
|
||||
"username": userName,
|
||||
"password": password,
|
||||
"uuid": "",
|
||||
"code": ""
|
||||
};
|
||||
|
||||
// 注册接口,成功跳转登陆界面
|
||||
ResponseInfo responseInfo = await DioUtils.instance.postRequest(
|
||||
url: "http://120.26.107.74:1619/register",
|
||||
jsonMap: map,
|
||||
);
|
||||
|
||||
if (responseInfo.success) {
|
||||
|
||||
Fluttertoast.showToast(msg: "注册成功");
|
||||
|
||||
//关闭当前页面
|
||||
Navigator.of(context).pop(true);
|
||||
//发送消息更新我的页面显示内容
|
||||
loginStreamController.add(0);
|
||||
} else {
|
||||
//登录失败页面小提示
|
||||
Fluttertoast.showToast(msg: responseInfo.message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
19
lib/utils/angle.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'dart:math';
|
||||
|
||||
/// 角度处理工具类
|
||||
class RadianUtils{
|
||||
|
||||
///弧度是角的度量单位 单位缩写是rad 360°角=2π弧度
|
||||
///在Flutter中,π 使用 [pi] 来表示 1弧度约为57.3°,1°为π/180弧度
|
||||
///弧度换算成角度 参数[radian]为弧度
|
||||
static double radianToAngle(double radian) {
|
||||
return radian * 180 / (pi);
|
||||
}
|
||||
|
||||
|
||||
///角度换算成弧度 参数[angle]为角度
|
||||
static double angleToRadian(double angle) {
|
||||
return angle * pi / 180;
|
||||
}
|
||||
|
||||
}
|
25
lib/utils/color.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
//调用的时候需要把hex改一下,比如#223344 needs change to 0xFF223344
|
||||
//即把#换成0xFF即可
|
||||
|
||||
MaterialColor createMaterialColor(Color color) {
|
||||
List strengths = <double>[.05];
|
||||
Map swatch = <int, Color>{};
|
||||
final int r = color.red, g = color.green, b = color.blue;
|
||||
|
||||
for (int i = 1; i < 10; i++) {
|
||||
strengths.add(0.1 * i);
|
||||
}
|
||||
strengths.forEach((strength) {
|
||||
final double ds = 0.5 - strength;
|
||||
swatch[(strength * 1000).round()] = Color.fromRGBO(
|
||||
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
|
||||
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
|
||||
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
|
||||
1,
|
||||
);
|
||||
});
|
||||
return MaterialColor(color.value, swatch);
|
||||
}
|
5
lib/utils/data_parse.dart
Normal file
@ -0,0 +1,5 @@
|
||||
import 'package:fengshui_compass/utils/recv_parse.dart';
|
||||
|
||||
|
||||
|
||||
|
163
lib/utils/navigator_utils.dart
Normal file
@ -0,0 +1,163 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 创建人: Created by zhaolong
|
||||
/// 创建时间:Created by on 2020/12/9.
|
||||
///
|
||||
/// 可关注公众号:我的大前端生涯 获取最新技术分享
|
||||
/// 可关注网易云课堂:https://study.163.com/instructor/1021406098.htm
|
||||
/// 可关注博客:https://blog.csdn.net/zl18603543572
|
||||
///
|
||||
|
||||
class NavigatorUtils {
|
||||
///普通打开页面的方法
|
||||
///[context] 上下文对象
|
||||
///[targPage] 目标页面
|
||||
///[isReplace] 是否替换当前页面 A -B
|
||||
static void pushPage({
|
||||
BuildContext context,
|
||||
Widget targPage,
|
||||
bool isReplace = false,
|
||||
Function(dynamic value) dismissCallBack,
|
||||
}) {
|
||||
PageRoute pageRoute;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
pageRoute = MaterialPageRoute(builder: (BuildContext context) {
|
||||
return targPage;
|
||||
});
|
||||
} else {
|
||||
pageRoute = CupertinoPageRoute(builder: (BuildContext context) {
|
||||
return targPage;
|
||||
});
|
||||
}
|
||||
|
||||
if (isReplace) {
|
||||
Navigator.of(context).pushReplacement(pageRoute).then((value) {
|
||||
if (dismissCallBack != null) {
|
||||
dismissCallBack(value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Navigator.of(context).push(pageRoute).then((value) {
|
||||
if (dismissCallBack != null) {
|
||||
dismissCallBack(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
///普通打开页面的方法
|
||||
///[context] 上下文对象
|
||||
///[targPage] 目标页面
|
||||
///[isReplace] 是否替换当前页面 A -B
|
||||
///[opaque] 是否以背景透明的方式打开页面
|
||||
static void pushPageByFade({
|
||||
BuildContext context,
|
||||
Widget targPage,
|
||||
bool isReplace = false,
|
||||
int startMills = 400,
|
||||
bool opaque = false,
|
||||
Function(dynamic value) dismissCallBack,
|
||||
}) {
|
||||
PageRoute pageRoute = PageRouteBuilder(
|
||||
//背景透明 方式打开新的页面
|
||||
opaque: opaque,
|
||||
pageBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation) {
|
||||
return targPage;
|
||||
},
|
||||
transitionDuration: Duration(milliseconds: startMills),
|
||||
//动画
|
||||
transitionsBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (isReplace) {
|
||||
Navigator.of(context).pushReplacement(pageRoute).then((value) {
|
||||
if (dismissCallBack != null) {
|
||||
dismissCallBack(value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Navigator.of(context).push(pageRoute).then((value) {
|
||||
if (dismissCallBack != null) {
|
||||
dismissCallBack(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
///中间缩放的形式打开页面
|
||||
/// 开源项目 Flutter-HO 早起的年轻人
|
||||
/// github https://github.com/zhaolongs/flutter-ho
|
||||
static void pushPageByCenterScale({
|
||||
BuildContext context,
|
||||
Widget targPage,
|
||||
bool isReplace = false,
|
||||
int startMills = 400,
|
||||
int reversMills = 400,
|
||||
bool opaque = false,
|
||||
Function(dynamic value) dismissCallBack,
|
||||
}) {
|
||||
if (isReplace) {
|
||||
Navigator.of(context)
|
||||
.pushReplacement(
|
||||
_createRoute(context, targPage, startMills, reversMills))
|
||||
.then((value) {
|
||||
if (dismissCallBack != null) {
|
||||
dismissCallBack(value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Navigator.of(context)
|
||||
.push(_createRoute(context, targPage, startMills, reversMills))
|
||||
.then((value) {
|
||||
if (dismissCallBack != null) {
|
||||
dismissCallBack(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static Route _createRoute(BuildContext parentContext, Widget targPage,
|
||||
int startMills, int reversMills) {
|
||||
return PageRouteBuilder<void>(
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return targPage;
|
||||
},
|
||||
transitionDuration: Duration(milliseconds: startMills),
|
||||
reverseTransitionDuration: Duration(milliseconds: reversMills),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
var rectAnimation = _createTween(parentContext)
|
||||
.chain(CurveTween(curve: Curves.ease))
|
||||
.animate(animation);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
PositionedTransition(rect: rectAnimation, child: child),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Tween<RelativeRect> _createTween(BuildContext context) {
|
||||
var windowSize = MediaQuery.of(context).size;
|
||||
var box = context.findRenderObject() as RenderBox;
|
||||
var rect = box.localToGlobal(Offset.zero) & box.size;
|
||||
var relativeRect = RelativeRect.fromSize(rect, windowSize);
|
||||
|
||||
return RelativeRectTween(
|
||||
begin: relativeRect,
|
||||
end: RelativeRect.fill,
|
||||
);
|
||||
}
|
||||
}
|
23
lib/utils/recv_parse.dart
Normal file
@ -0,0 +1,23 @@
|
||||
// 处理串口接收字节数组转换
|
||||
String intToHex(int i, {int pad = 2}) {
|
||||
return i.toRadixString(16).padLeft(pad, '0').toUpperCase();
|
||||
}
|
||||
|
||||
List<int> hexToUnits(String hexStr, {int combine = 2}) {
|
||||
hexStr = hexStr.replaceAll(" ", "");
|
||||
List<int> hexUnits = [];
|
||||
for (int i = 0; i < hexStr.length; i += combine) {
|
||||
hexUnits.add(hexToInt(hexStr.substring(i, i + combine)));
|
||||
}
|
||||
return hexUnits;
|
||||
}
|
||||
|
||||
int hexToInt(String hex) {
|
||||
return int.parse(hex, radix: 16);
|
||||
}
|
||||
|
||||
String formatReceivedData(recv) {
|
||||
return recv
|
||||
.map((List<int> char) => char.map((c) => intToHex(c)).join())
|
||||
.join();
|
||||
}
|
91
lib/utils/sp_utils.dart
Normal file
@ -0,0 +1,91 @@
|
||||
///lib/utils/sp_utils.dart
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SPUtil {
|
||||
///静态实例
|
||||
static SharedPreferences _sharedPreferences;
|
||||
|
||||
///应用启动时需要调用
|
||||
///初始化
|
||||
static Future<bool> init() async {
|
||||
_sharedPreferences = await SharedPreferences.getInstance();
|
||||
return true;
|
||||
}
|
||||
|
||||
//清除数据
|
||||
static void remove(String key) async {
|
||||
if (_sharedPreferences.containsKey(key)) {
|
||||
_sharedPreferences.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 异步保存基本数据类型
|
||||
static Future save(String key, dynamic value) async {
|
||||
if (value is String) {
|
||||
_sharedPreferences.setString(key, value);
|
||||
} else if (value is bool) {
|
||||
_sharedPreferences.setBool(key, value);
|
||||
} else if (value is double) {
|
||||
_sharedPreferences.setDouble(key, value);
|
||||
} else if (value is int) {
|
||||
_sharedPreferences.setInt(key, value);
|
||||
} else if (value is List<String>) {
|
||||
_sharedPreferences.setStringList(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// 异步读取
|
||||
static Future<String> getString(String key) async {
|
||||
return _sharedPreferences.getString(key);
|
||||
}
|
||||
|
||||
static Future<int> getInt(String key) async {
|
||||
return _sharedPreferences.getInt(key);
|
||||
}
|
||||
|
||||
static Future<bool> getBool(String key) async {
|
||||
return _sharedPreferences.getBool(key);
|
||||
}
|
||||
|
||||
static Future<double> getDouble(String key) async {
|
||||
return _sharedPreferences.getDouble(key);
|
||||
}
|
||||
|
||||
///保存自定义对象
|
||||
static Future saveObject(String key, dynamic value) async {
|
||||
///通过 json 将Object对象编译成String类型保存
|
||||
_sharedPreferences.setString(key, json.encode(value));
|
||||
}
|
||||
|
||||
///获取自定义对象
|
||||
///返回的是 Map<String,dynamic> 类型数据
|
||||
static dynamic getObject(String key) {
|
||||
String _data = _sharedPreferences.getString(key);
|
||||
if (_data == null) {
|
||||
return null;
|
||||
}
|
||||
return (_data.isEmpty) ? null : json.decode(_data);
|
||||
}
|
||||
|
||||
///保存列表数据
|
||||
static Future<bool> putObjectList(String key, List<Object> list) {
|
||||
///将Object的数据类型转换为String类型
|
||||
List<String> _dataList = list?.map((value) {
|
||||
return json.encode(value);
|
||||
})?.toList();
|
||||
return _sharedPreferences.setStringList(key, _dataList);
|
||||
}
|
||||
|
||||
///获取对象集合数据
|
||||
///返回的是List<Map<String,dynamic>>类型
|
||||
static List<Map> getObjectList(String key) {
|
||||
if (_sharedPreferences == null) return null;
|
||||
List<String> dataLis = _sharedPreferences.getStringList(key);
|
||||
return dataLis?.map((value) {
|
||||
Map _dataMap = json.decode(value);
|
||||
return _dataMap;
|
||||
})?.toList();
|
||||
}
|
||||
}
|
31
lib/utils/token_helper.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:fengshui_compass/utils/sp_utils.dart';
|
||||
|
||||
import '../models/login_bean.dart';
|
||||
|
||||
class TokenHelper {
|
||||
TokenHelper._();
|
||||
|
||||
static TokenHelper getInstance = TokenHelper._();
|
||||
|
||||
LoginBean _loginBean;
|
||||
|
||||
bool get isLogin => _loginBean != null;
|
||||
|
||||
set loginBean(LoginBean bean) {
|
||||
_loginBean = bean;
|
||||
SPUtil.saveObject("token_bean", _loginBean);
|
||||
}
|
||||
|
||||
void init() {
|
||||
Map<String, dynamic> map = SPUtil.getObject("token_bean");
|
||||
if (map != null) {
|
||||
//加载缓存
|
||||
_loginBean = LoginBean.fromMap(map);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_loginBean = null;
|
||||
SPUtil.remove("token_bean");
|
||||
}
|
||||
}
|
31
lib/utils/user_helper.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:fengshui_compass/models/user_bean.dart';
|
||||
import 'package:fengshui_compass/utils/sp_utils.dart';
|
||||
|
||||
class UserHelper {
|
||||
UserHelper._();
|
||||
|
||||
static UserHelper getInstance = UserHelper._();
|
||||
|
||||
UserBean _userBean;
|
||||
|
||||
bool get isLogin => _userBean != null;
|
||||
|
||||
set userBean(UserBean bean) {
|
||||
_userBean = bean;
|
||||
SPUtil.saveObject("user_bean", _userBean);
|
||||
}
|
||||
|
||||
void init() {
|
||||
Map<String, dynamic> map = SPUtil.getObject("user_bean");
|
||||
if (map != null) {
|
||||
//加载缓存
|
||||
_userBean = UserBean.fromMap(map);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_userBean = null;
|
||||
SPUtil.remove("user_bean");
|
||||
}
|
||||
|
||||
}
|
397
pubspec.lock
Normal file
@ -0,0 +1,397 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.0.6"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_serial_port_api:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: fad1cb5f2ebf9c57baebc373ce0b1227342479bb
|
||||
url: "https://gitee.com/liang-fu/flutter_serial_port_api.git"
|
||||
source: git
|
||||
version: "0.0.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fluttertoast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluttertoast
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.1.8"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
package_info:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
shared_preferences_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_ios
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
stream_transform:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: stream_transform
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.0.20"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
webview_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.8.3"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
webview_flutter_wkwebview:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.7.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.0+1"
|
||||
sdks:
|
||||
dart: ">=2.17.0-0 <3.0.0"
|
||||
flutter: ">=2.8.0"
|
71
pubspec.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
name: fengshui_compass
|
||||
description: 定盘星
|
||||
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.8.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
# Stream Utility
|
||||
stream_transform: ^0.0.19
|
||||
# showToast(弹窗提示)
|
||||
fluttertoast: ^7.1.6
|
||||
#App版本信息
|
||||
package_info: ^2.0.2
|
||||
#用来加载网络数据
|
||||
dio: ^4.0.4
|
||||
#偏好设置
|
||||
shared_preferences: ^2.0.13
|
||||
|
||||
webview_flutter: ^3.0.1
|
||||
cupertino_icons: ^1.0.2
|
||||
flutter_serial_port_api:
|
||||
git:
|
||||
url: https://gitee.com/liang-fu/flutter_serial_port_api.git
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter_lints: ^1.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/index.html
|
||||
- assets/test.js
|
||||
- assets/images/
|
||||
|
||||
fonts:
|
||||
- family: iconfont # 从 iconfont.json 中获取
|
||||
fonts:
|
||||
- asset: assets/fonts/iconfont.ttf
|
||||
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
30
test/widget_test.dart
Normal file
@ -0,0 +1,30 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:fengshui_compass/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
BIN
web/favicon.png
Normal file
After Width: | Height: | Size: 917 B |
BIN
web/icons/Icon-192.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
web/icons/Icon-512.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
web/icons/Icon-maskable-192.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
web/icons/Icon-maskable-512.png
Normal file
After Width: | Height: | Size: 20 KiB |
104
web/index.html
Normal file
@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="风水罗盘">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="fengshui_compass">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>fengshui_compass</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||
application. For more information, see:
|
||||
https://developers.google.com/web/fundamentals/primers/service-workers -->
|
||||
<script>
|
||||
var serviceWorkerVersion = null;
|
||||
var scriptLoaded = false;
|
||||
function loadMainDartJs() {
|
||||
if (scriptLoaded) {
|
||||
return;
|
||||
}
|
||||
scriptLoaded = true;
|
||||
var scriptTag = document.createElement('script');
|
||||
scriptTag.src = 'main.dart.js';
|
||||
scriptTag.type = 'application/javascript';
|
||||
document.body.append(scriptTag);
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
// Service workers are supported. Use them.
|
||||
window.addEventListener('load', function () {
|
||||
// Wait for registration to finish before dropping the <script> tag.
|
||||
// Otherwise, the browser will load the script multiple times,
|
||||
// potentially different versions.
|
||||
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
|
||||
navigator.serviceWorker.register(serviceWorkerUrl)
|
||||
.then((reg) => {
|
||||
function waitForActivation(serviceWorker) {
|
||||
serviceWorker.addEventListener('statechange', () => {
|
||||
if (serviceWorker.state == 'activated') {
|
||||
console.log('Installed new service worker.');
|
||||
loadMainDartJs();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!reg.active && (reg.installing || reg.waiting)) {
|
||||
// No active web worker and we have installed or are installing
|
||||
// one for the first time. Simply wait for it to activate.
|
||||
waitForActivation(reg.installing || reg.waiting);
|
||||
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
||||
// When the app updates the serviceWorkerVersion changes, so we
|
||||
// need to ask the service worker to update.
|
||||
console.log('New service worker available.');
|
||||
reg.update();
|
||||
waitForActivation(reg.installing);
|
||||
} else {
|
||||
// Existing service worker is still good.
|
||||
console.log('Loading app from service worker.');
|
||||
loadMainDartJs();
|
||||
}
|
||||
});
|
||||
|
||||
// If service worker doesn't succeed in a reasonable amount of time,
|
||||
// fallback to plain <script> tag.
|
||||
setTimeout(() => {
|
||||
if (!scriptLoaded) {
|
||||
console.warn(
|
||||
'Failed to load app from service worker. Falling back to plain <script> tag.',
|
||||
);
|
||||
loadMainDartJs();
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
} else {
|
||||
// Service workers not supported. Just drop the <script> tag.
|
||||
loadMainDartJs();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
35
web/manifest.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "fengshui_compass",
|
||||
"short_name": "fengshui_compass",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"description": "风水罗盘",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/Icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
17
windows/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
flutter/ephemeral/
|
||||
|
||||
# Visual Studio user-specific files.
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Visual Studio build-related files.
|
||||
x64/
|
||||
x86/
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
101
windows/CMakeLists.txt
Normal file
@ -0,0 +1,101 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(fengshui_compass LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "fengshui_compass")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Define build configuration option.
|
||||
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(IS_MULTICONFIG)
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||
CACHE STRING "" FORCE)
|
||||
else()
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
endif()
|
||||
# Define settings for the Profile build mode.
|
||||
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
# Use Unicode for all projects.
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# 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
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
||||
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
||||
target_compile_options(${TARGET} PRIVATE /EHsc)
|
||||
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# Support files are copied into place next to the executable, so that it can
|
||||
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||
# so that building and running from within Visual Studio will work.
|
||||
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||
# Make the "install" step default, as it's required to run.
|
||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
CONFIGURATIONS Profile;Release
|
||||
COMPONENT Runtime)
|
104
windows/flutter/CMakeLists.txt
Normal file
@ -0,0 +1,104 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
||||
|
||||
# === Flutter Library ===
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"flutter_export.h"
|
||||
"flutter_windows.h"
|
||||
"flutter_messenger.h"
|
||||
"flutter_plugin_registrar.h"
|
||||
"flutter_texture_registrar.h"
|
||||
)
|
||||
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Wrapper ===
|
||||
list(APPEND CPP_WRAPPER_SOURCES_CORE
|
||||
"core_implementations.cc"
|
||||
"standard_codec.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
|
||||
"plugin_registrar.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_APP
|
||||
"flutter_engine.cc"
|
||||
"flutter_view_controller.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
|
||||
|
||||
# Wrapper sources needed for a plugin.
|
||||
add_library(flutter_wrapper_plugin STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_plugin)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden)
|
||||
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_plugin PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_plugin flutter_assemble)
|
||||
|
||||
# Wrapper sources needed for the runner.
|
||||
add_library(flutter_wrapper_app STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_app)
|
||||
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_app PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_app flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
|
||||
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
${PHONY_OUTPUT}
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||
windows-x64 $<CONFIG>
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
11
windows/flutter/generated_plugin_registrant.cc
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
}
|
15
windows/flutter/generated_plugin_registrant.h
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter/plugin_registry.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
23
windows/flutter/generated_plugins.cmake
Normal file
@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
32
windows/runner/CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# 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
|
||||
# work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME} WIN32
|
||||
"flutter_window.cpp"
|
||||
"main.cpp"
|
||||
"utils.cpp"
|
||||
"win32_window.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
"Runner.rc"
|
||||
"runner.exe.manifest"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Disable Windows macros that collide with C++ standard library functions.
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||
|
||||
# Add dependency libraries and include directories. Add any application-specific
|
||||
# dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
121
windows/runner/Runner.rc
Normal file
@ -0,0 +1,121 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
#ifdef FLUTTER_BUILD_NUMBER
|
||||
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
|
||||
#else
|
||||
#define VERSION_AS_NUMBER 1,0,0
|
||||
#endif
|
||||
|
||||
#ifdef FLUTTER_BUILD_NAME
|
||||
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
|
||||
#else
|
||||
#define VERSION_AS_STRING "1.0.0"
|
||||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_AS_NUMBER
|
||||
PRODUCTVERSION VERSION_AS_NUMBER
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "cn.com.motse" "\0"
|
||||
VALUE "FileDescription", "fengshui_compass" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "fengshui_compass" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2022 cn.com.motse. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "fengshui_compass.exe" "\0"
|
||||
VALUE "ProductName", "fengshui_compass" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
61
windows/runner/flutter_window.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "flutter_window.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||
: project_(project) {}
|
||||
|
||||
FlutterWindow::~FlutterWindow() {}
|
||||
|
||||
bool FlutterWindow::OnCreate() {
|
||||
if (!Win32Window::OnCreate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
// The size here must match the window dimensions to avoid unnecessary surface
|
||||
// creation / destruction in the startup path.
|
||||
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
||||
frame.right - frame.left, frame.bottom - frame.top, project_);
|
||||
// Ensure that basic setup of the controller was successful.
|
||||
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
||||
return false;
|
||||
}
|
||||
RegisterPlugins(flutter_controller_->engine());
|
||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlutterWindow::OnDestroy() {
|
||||
if (flutter_controller_) {
|
||||
flutter_controller_ = nullptr;
|
||||
}
|
||||
|
||||
Win32Window::OnDestroy();
|
||||
}
|
||||
|
||||
LRESULT
|
||||
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
// Give Flutter, including plugins, an opportunity to handle window messages.
|
||||
if (flutter_controller_) {
|
||||
std::optional<LRESULT> result =
|
||||
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
||||
lparam);
|
||||
if (result) {
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_FONTCHANGE:
|
||||
flutter_controller_->engine()->ReloadSystemFonts();
|
||||
break;
|
||||
}
|
||||
|
||||
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
||||
}
|
33
windows/runner/flutter_window.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
||||
#define RUNNER_FLUTTER_WINDOW_H_
|
||||
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "win32_window.h"
|
||||
|
||||
// A window that does nothing but host a Flutter view.
|
||||
class FlutterWindow : public Win32Window {
|
||||
public:
|
||||
// Creates a new FlutterWindow hosting a Flutter view running |project|.
|
||||
explicit FlutterWindow(const flutter::DartProject& project);
|
||||
virtual ~FlutterWindow();
|
||||
|
||||
protected:
|
||||
// Win32Window:
|
||||
bool OnCreate() override;
|
||||
void OnDestroy() override;
|
||||
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept override;
|
||||
|
||||
private:
|
||||
// The project to run.
|
||||
flutter::DartProject project_;
|
||||
|
||||
// The Flutter instance hosted by this window.
|
||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||
};
|
||||
|
||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
43
windows/runner/main.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
CreateAndAttachConsole();
|
||||
}
|
||||
|
||||
// Initialize COM, so that it is available for use in the library and/or
|
||||
// plugins.
|
||||
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
flutter::DartProject project(L"data");
|
||||
|
||||
std::vector<std::string> command_line_arguments =
|
||||
GetCommandLineArguments();
|
||||
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.CreateAndShow(L"fengshui_compass", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
||||
::MSG msg;
|
||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||
::TranslateMessage(&msg);
|
||||
::DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
::CoUninitialize();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
16
windows/runner/resource.h
Normal file
@ -0,0 +1,16 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by Runner.rc
|
||||
//
|
||||
#define IDI_APP_ICON 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
BIN
windows/runner/resources/app_icon.ico
Normal file
After Width: | Height: | Size: 33 KiB |
20
windows/runner/runner.exe.manifest
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
64
windows/runner/utils.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void CreateAndAttachConsole() {
|
||||
if (::AllocConsole()) {
|
||||
FILE *unused;
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
||||
_dup2(_fileno(stdout), 1);
|
||||
}
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
||||
_dup2(_fileno(stdout), 2);
|
||||
}
|
||||
std::ios::sync_with_stdio();
|
||||
FlutterDesktopResyncOutputStreams();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> GetCommandLineArguments() {
|
||||
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
|
||||
int argc;
|
||||
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||
if (argv == nullptr) {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
|
||||
std::vector<std::string> command_line_arguments;
|
||||
|
||||
// Skip the first argument as it's the binary name.
|
||||
for (int i = 1; i < argc; i++) {
|
||||
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
|
||||
}
|
||||
|
||||
::LocalFree(argv);
|
||||
|
||||
return command_line_arguments;
|
||||
}
|
||||
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
||||
if (utf16_string == nullptr) {
|
||||
return std::string();
|
||||
}
|
||||
int target_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
-1, nullptr, 0, nullptr, nullptr);
|
||||
if (target_length == 0) {
|
||||
return std::string();
|
||||
}
|
||||
std::string utf8_string;
|
||||
utf8_string.resize(target_length);
|
||||
int converted_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
-1, utf8_string.data(),
|
||||
target_length, nullptr, nullptr);
|
||||
if (converted_length == 0) {
|
||||
return std::string();
|
||||
}
|
||||
return utf8_string;
|
||||
}
|
19
windows/runner/utils.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef RUNNER_UTILS_H_
|
||||
#define RUNNER_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Creates a console for the process, and redirects stdout and stderr to
|
||||
// it for both the runner and the Flutter library.
|
||||
void CreateAndAttachConsole();
|
||||
|
||||
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
|
||||
// encoded in UTF-8. Returns an empty std::string on failure.
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string);
|
||||
|
||||
// Gets the command line arguments passed in as a std::vector<std::string>,
|
||||
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
|
||||
std::vector<std::string> GetCommandLineArguments();
|
||||
|
||||
#endif // RUNNER_UTILS_H_
|
245
windows/runner/win32_window.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
#include "win32_window.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||
|
||||
// The number of Win32Window objects that currently exist.
|
||||
static int g_active_window_count = 0;
|
||||
|
||||
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||||
|
||||
// Scale helper to convert logical scaler values to physical using passed in
|
||||
// scale factor
|
||||
int Scale(int source, double scale_factor) {
|
||||
return static_cast<int>(source * scale_factor);
|
||||
}
|
||||
|
||||
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||||
// This API is only needed for PerMonitor V1 awareness mode.
|
||||
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||||
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||||
if (!user32_module) {
|
||||
return;
|
||||
}
|
||||
auto enable_non_client_dpi_scaling =
|
||||
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||||
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||||
if (enable_non_client_dpi_scaling != nullptr) {
|
||||
enable_non_client_dpi_scaling(hwnd);
|
||||
FreeLibrary(user32_module);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Manages the Win32Window's window class registration.
|
||||
class WindowClassRegistrar {
|
||||
public:
|
||||
~WindowClassRegistrar() = default;
|
||||
|
||||
// Returns the singleton registar instance.
|
||||
static WindowClassRegistrar* GetInstance() {
|
||||
if (!instance_) {
|
||||
instance_ = new WindowClassRegistrar();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
// Returns the name of the window class, registering the class if it hasn't
|
||||
// previously been registered.
|
||||
const wchar_t* GetWindowClass();
|
||||
|
||||
// Unregisters the window class. Should only be called if there are no
|
||||
// instances of the window.
|
||||
void UnregisterWindowClass();
|
||||
|
||||
private:
|
||||
WindowClassRegistrar() = default;
|
||||
|
||||
static WindowClassRegistrar* instance_;
|
||||
|
||||
bool class_registered_ = false;
|
||||
};
|
||||
|
||||
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||||
|
||||
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||||
if (!class_registered_) {
|
||||
WNDCLASS window_class{};
|
||||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
window_class.lpszClassName = kWindowClassName;
|
||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||
window_class.cbClsExtra = 0;
|
||||
window_class.cbWndExtra = 0;
|
||||
window_class.hInstance = GetModuleHandle(nullptr);
|
||||
window_class.hIcon =
|
||||
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||||
window_class.hbrBackground = 0;
|
||||
window_class.lpszMenuName = nullptr;
|
||||
window_class.lpfnWndProc = Win32Window::WndProc;
|
||||
RegisterClass(&window_class);
|
||||
class_registered_ = true;
|
||||
}
|
||||
return kWindowClassName;
|
||||
}
|
||||
|
||||
void WindowClassRegistrar::UnregisterWindowClass() {
|
||||
UnregisterClass(kWindowClassName, nullptr);
|
||||
class_registered_ = false;
|
||||
}
|
||||
|
||||
Win32Window::Win32Window() {
|
||||
++g_active_window_count;
|
||||
}
|
||||
|
||||
Win32Window::~Win32Window() {
|
||||
--g_active_window_count;
|
||||
Destroy();
|
||||
}
|
||||
|
||||
bool Win32Window::CreateAndShow(const std::wstring& title,
|
||||
const Point& origin,
|
||||
const Size& size) {
|
||||
Destroy();
|
||||
|
||||
const wchar_t* window_class =
|
||||
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||||
|
||||
const POINT target_point = {static_cast<LONG>(origin.x),
|
||||
static_cast<LONG>(origin.y)};
|
||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||
double scale_factor = dpi / 96.0;
|
||||
|
||||
HWND window = CreateWindow(
|
||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||
|
||||
if (!window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return OnCreate();
|
||||
}
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
if (message == WM_NCCREATE) {
|
||||
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
SetWindowLongPtr(window, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||||
|
||||
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||||
EnableFullDpiSupportIfAvailable(window);
|
||||
that->window_handle_ = window;
|
||||
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||||
return that->MessageHandler(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
LRESULT
|
||||
Win32Window::MessageHandler(HWND hwnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
switch (message) {
|
||||
case WM_DESTROY:
|
||||
window_handle_ = nullptr;
|
||||
Destroy();
|
||||
if (quit_on_close_) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DPICHANGED: {
|
||||
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||
|
||||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case WM_SIZE: {
|
||||
RECT rect = GetClientArea();
|
||||
if (child_content_ != nullptr) {
|
||||
// Size and position the child window.
|
||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, TRUE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_ACTIVATE:
|
||||
if (child_content_ != nullptr) {
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void Win32Window::Destroy() {
|
||||
OnDestroy();
|
||||
|
||||
if (window_handle_) {
|
||||
DestroyWindow(window_handle_);
|
||||
window_handle_ = nullptr;
|
||||
}
|
||||
if (g_active_window_count == 0) {
|
||||
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||||
}
|
||||
}
|
||||
|
||||
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||||
return reinterpret_cast<Win32Window*>(
|
||||
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
void Win32Window::SetChildContent(HWND content) {
|
||||
child_content_ = content;
|
||||
SetParent(content, window_handle_);
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||
frame.bottom - frame.top, true);
|
||||
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
|
||||
RECT Win32Window::GetClientArea() {
|
||||
RECT frame;
|
||||
GetClientRect(window_handle_, &frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
HWND Win32Window::GetHandle() {
|
||||
return window_handle_;
|
||||
}
|
||||
|
||||
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||||
quit_on_close_ = quit_on_close;
|
||||
}
|
||||
|
||||
bool Win32Window::OnCreate() {
|
||||
// No-op; provided for subclasses.
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win32Window::OnDestroy() {
|
||||
// No-op; provided for subclasses.
|
||||
}
|