This commit is contained in:
cxc
2022-06-27 09:51:30 +08:00
commit 8cb96b4c5e
101 changed files with 11110 additions and 0 deletions

46
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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"
}

View 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>

View 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>

View File

@ -0,0 +1,6 @@
package cn.com.motse.fengshui_compass
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View 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>

View 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>

View 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
View 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
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View 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
View 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
View 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
View 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

Binary file not shown.

BIN
assets/images/arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

BIN
assets/images/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

BIN
assets/images/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 KiB

BIN
assets/images/clodeSide.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/images/closeUp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/images/compass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

BIN
assets/images/head.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
assets/images/openSide.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/images/openUp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/images/pan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
assets/images/water.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

2489
assets/index - 副本.html Normal file

File diff suppressed because it is too large Load Diff

2492
assets/index.html Normal file

File diff suppressed because it is too large Load Diff

5
assets/test.js Normal file
View File

@ -0,0 +1,5 @@
<script>
function flutterCallJsMethod(yy, mm, dd, hh, m) {
return `${yy}${mm}${dd}${hh}${m}`;
}
</script>

View 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: '我的',
),
],
),
),
);
}
}

View 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();

View 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,
),
),
),
);
}
}

View 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);
}

View 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
View 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
View 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分";
}
}

View 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
View 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;
}
}

View 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
View 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
View 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
View 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";
}

View 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");
}
}

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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();
})
);
}
})
],
),
));
}
}

View 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),
),
);
}
}

View 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();
}
}
}

View 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
View 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
View 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);
}

View File

@ -0,0 +1,5 @@
import 'package:fengshui_compass/utils/recv_parse.dart';

View 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
View 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
View 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();
}
}

View 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");
}
}

View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
web/icons/Icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
web/icons/Icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

104
web/index.html Normal file
View 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
View 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
View 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
View 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)

View 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}
)

View File

@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}

View 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_

View 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)

View 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
View 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

View 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);
}

View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View 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
View 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
View 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_

View 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.
}

Some files were not shown because too many files have changed in this diff Show More