fix msg
@@ -4,7 +4,7 @@
|
|||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: f7a6a7906be96d2288f5d63a5a54c515a6e987fe
|
revision: f4abaa0735eba4dfd8f33f73363911d63931fe03
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|||||||
14
README.md
@@ -1,2 +1,16 @@
|
|||||||
# fcs
|
# fcs
|
||||||
|
|
||||||
|
A new Flutter project.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter, view our
|
||||||
|
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
||||||
|
|||||||
4
android/.gitignore
vendored
@@ -5,3 +5,7 @@ gradle-wrapper.jar
|
|||||||
/gradlew.bat
|
/gradlew.bat
|
||||||
/local.properties
|
/local.properties
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>android</name>
|
|
||||||
<comment>Project android created by Buildship.</comment>
|
|
||||||
<projects>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
|
||||||
</natures>
|
|
||||||
</projectDescription>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
arguments=
|
|
||||||
auto.sync=false
|
|
||||||
build.scans.enabled=false
|
|
||||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
|
||||||
connection.project.dir=
|
|
||||||
eclipse.preferences.version=1
|
|
||||||
gradle.user.home=
|
|
||||||
java.home=/usr/lib/jvm/java-8-openjdk-amd64
|
|
||||||
jvm.arguments=
|
|
||||||
offline.mode=false
|
|
||||||
override.workspace.settings=true
|
|
||||||
show.console.view=true
|
|
||||||
show.executions.view=true
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<classpath>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
|
||||||
<classpathentry kind="output" path="bin/default"/>
|
|
||||||
</classpath>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>app</name>
|
|
||||||
<comment>Project app created by Buildship.</comment>
|
|
||||||
<projects>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
|
||||||
</natures>
|
|
||||||
</projectDescription>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
connection.project.dir=..
|
|
||||||
eclipse.preferences.version=1
|
|
||||||
@@ -24,26 +24,17 @@ if (flutterVersionName == null) {
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
|
||||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
|
||||||
if (keystorePropertiesFile.exists()) {
|
|
||||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 30
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
disable 'InvalidPackage'
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.mokkon.fcs.fcs"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 30
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -92,17 +83,8 @@ flutter {
|
|||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
|
||||||
implementation 'com.google.firebase:firebase-auth:19.0.0'
|
|
||||||
implementation 'com.google.firebase:firebase-messaging:20.1.0'
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.mokkon.fcs_dev.fcs">
|
package="com.mokkon.fcs.fcs">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,43 +1,41 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.mokkon.fcs_dev.fcs">
|
package="com.mokkon.fcs.fcs">
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
|
||||||
|
|
||||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
|
||||||
In most cases you can leave this as-is, but you if you want to provide
|
|
||||||
additional functionality it is fine to subclass or reimplement
|
|
||||||
FlutterApplication and put your custom class here. -->
|
|
||||||
<application
|
<application
|
||||||
android:name=".Application"
|
android:label="fcs"
|
||||||
android:label="@string/app_name"
|
android:icon="@mipmap/ic_launcher">
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:usesCleartextTraffic="true">
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- This keeps the window background of the activity showing
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
until Flutter renders its first frame. It can be removed if
|
the Android process has started. This theme is visible to the user
|
||||||
there is no splash screen (such as the default splash screen
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
defined in @style/LaunchTheme). -->
|
to determine the Window background behind the Flutter UI. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:value="true" />
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<!-- Displays an Android View that continues showing the launch screen
|
||||||
|
Drawable until Flutter paints its first frame, then this splash
|
||||||
|
screen fades out. A splash screen is useful to avoid any visual
|
||||||
|
gap between the end of Android's launch screen and the painting of
|
||||||
|
Flutter's first frame. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||||
|
android:resource="@drawable/launch_background"
|
||||||
|
/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.mokkon.fcs.fcs;
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity;
|
||||||
|
|
||||||
|
public class MainActivity extends FlutterActivity {
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.mokkon.fcs_dev.fcs;
|
|
||||||
|
|
||||||
import io.flutter.app.FlutterApplication;
|
|
||||||
import io.flutter.plugin.common.PluginRegistry;
|
|
||||||
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
|
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
|
||||||
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService;
|
|
||||||
|
|
||||||
public class Application extends FlutterApplication implements PluginRegistrantCallback {
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
FlutterFirebaseMessagingService.setPluginRegistrant(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerWith(PluginRegistry registry) {
|
|
||||||
GeneratedPluginRegistrant.registerWith(registry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.mokkon.fcs_dev.fcs;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import io.flutter.app.FlutterActivity;
|
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
|
||||||
|
|
||||||
public class MainActivity extends FlutterActivity {
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
GeneratedPluginRegistrant.registerWith(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">FCS</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
running.
|
running.
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:windowBackground">@android:color/white</item>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.mokkon.fcs_dev.fcs">
|
package="com.mokkon.fcs.fcs">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.61'
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||||
classpath 'com.google.gms:google-services:4.3.3'
|
classpath 'com.google.gms:google-services:4.3.8'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' //add this line
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +20,6 @@ allprojects {
|
|||||||
rootProject.buildDir = '../build'
|
rootProject.buildDir = '../build'
|
||||||
subprojects {
|
subprojects {
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
}
|
|
||||||
subprojects {
|
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
android.enableR8=true
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||||
|
|||||||
7
android/proguard-rules.pro
vendored
@@ -1,7 +0,0 @@
|
|||||||
## Flutter wrapper
|
|
||||||
-keep class io.flutter.app.** { *; }
|
|
||||||
-keep class io.flutter.plugin.** { *; }
|
|
||||||
-keep class io.flutter.util.** { *; }
|
|
||||||
-keep class io.flutter.view.** { *; }
|
|
||||||
-keep class io.flutter.** { *; }
|
|
||||||
-keep class io.flutter.plugins.** { *; }
|
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
include ':app'
|
include ':app'
|
||||||
|
|
||||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
def plugins = new Properties()
|
assert localPropertiesFile.exists()
|
||||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
if (pluginsFile.exists()) {
|
|
||||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.each { name, path ->
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
include ":$name"
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||||
project(":$name").projectDir = pluginDirectory
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
include ':app'
|
|
||||||
@@ -1,57 +1,53 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:fcs/data/services/messaging_service.dart';
|
import 'package:fcs/data/services/messaging_service.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
final msgLog = Logger('backgroundMessageHandler');
|
final msgLog = Logger('backgroundMessageHandler');
|
||||||
|
|
||||||
Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) async {
|
Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
||||||
if (message.containsKey('data')) {
|
await Firebase.initializeApp();
|
||||||
// Handle data message
|
|
||||||
final dynamic data = message['data'];
|
|
||||||
msgLog.info("background onMessage: $message");
|
msgLog.info("background onMessage: $message");
|
||||||
}
|
|
||||||
|
|
||||||
if (message.containsKey('notification')) {
|
|
||||||
// Handle notification message
|
|
||||||
final dynamic notification = message['notification'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessagingFCM {
|
class MessagingFCM {
|
||||||
final log = Logger('MessagingFCM');
|
final log = Logger('MessagingFCM');
|
||||||
|
|
||||||
FirebaseMessaging _firebaseMessaging;
|
late FirebaseMessaging _firebaseMessaging;
|
||||||
|
|
||||||
MessagingFCM(OnNotify onMessage,
|
MessagingFCM(OnNotify onMessage,
|
||||||
{OnNotify onLaunch, OnNotify onResume, OnSetupComplete onSetupComplete}) {
|
{OnNotify? onLaunch,
|
||||||
_firebaseMessaging = FirebaseMessaging();
|
OnNotify? onResume,
|
||||||
_firebaseMessaging.configure(
|
OnSetupComplete? onSetupComplete}) {
|
||||||
onMessage: (Map<String, dynamic> message) async {
|
_firebaseMessaging = FirebaseMessaging.instance;
|
||||||
log.info("onMessage: $message");
|
init(onMessage: onMessage, onSetupComplete: onSetupComplete);
|
||||||
if (onMessage != null) _onNotify(message, onMessage);
|
}
|
||||||
},
|
|
||||||
onBackgroundMessage: Platform.isIOS ? null : backgroundMessageHandler,
|
init({OnNotify? onMessage, OnSetupComplete? onSetupComplete}) async {
|
||||||
onLaunch: (Map<String, dynamic> message) async {
|
NotificationSettings settings = await _firebaseMessaging.requestPermission(
|
||||||
log.info("onLaunch: $message");
|
alert: true,
|
||||||
if (onLaunch != null) _onNotify(message, onLaunch);
|
announcement: false,
|
||||||
},
|
badge: true,
|
||||||
onResume: (Map<String, dynamic> message) async {
|
carPlay: false,
|
||||||
log.info("onResume: $message");
|
criticalAlert: false,
|
||||||
if (onResume != null) _onNotify(message, onResume);
|
provisional: false,
|
||||||
},
|
sound: true,
|
||||||
);
|
);
|
||||||
_firebaseMessaging
|
|
||||||
.requestNotificationPermissions(const IosNotificationSettings());
|
print('User granted permission: ${settings.authorizationStatus}');
|
||||||
_firebaseMessaging.onIosSettingsRegistered
|
|
||||||
.listen((IosNotificationSettings settings) {
|
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||||
log.info("Settings registered: $settings");
|
log.info("onMessage: $message");
|
||||||
|
if (onMessage != null) _onNotify(message.data, onMessage);
|
||||||
|
|
||||||
|
if (message.notification != null) {
|
||||||
|
print('Message also contained a notification: ${message.notification}');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
_firebaseMessaging.getToken().then((String token) {
|
|
||||||
if (onSetupComplete != null) onSetupComplete(token);
|
String? token = await _firebaseMessaging.getToken();
|
||||||
|
if (onSetupComplete != null && token != null) onSetupComplete(token);
|
||||||
log.info("Messaging Token:$token");
|
log.info("Messaging Token:$token");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> subscribeToTopic(String topic) {
|
Future<void> subscribeToTopic(String topic) {
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import 'messaging_service.dart';
|
|||||||
class MessagingServiceImp implements MessagingService {
|
class MessagingServiceImp implements MessagingService {
|
||||||
MessagingServiceImp();
|
MessagingServiceImp();
|
||||||
|
|
||||||
static MessagingFCM messagingFCM;
|
late MessagingFCM messagingFCM;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void init(onMessage,
|
void init(onMessage,
|
||||||
{OnNotify onLaunch, OnNotify onResume, OnSetupComplete onSetupComplete}) {
|
{OnNotify? onLaunch,
|
||||||
|
OnNotify? onResume,
|
||||||
|
OnSetupComplete? onSetupComplete}) {
|
||||||
messagingFCM = MessagingFCM(onMessage,
|
messagingFCM = MessagingFCM(onMessage,
|
||||||
onLaunch: onLaunch,
|
onLaunch: onLaunch,
|
||||||
onResume: onResume,
|
onResume: onResume,
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'config.dart';
|
import 'config.dart';
|
||||||
|
import 'data/provider/messaging_fcm.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
|
||||||
|
|
||||||
Config(
|
Config(
|
||||||
flavor: Flavor.DEV,
|
flavor: Flavor.DEV,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
|
|||||||
113
lib/main.dart
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
// This widget is the root of your application.
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Flutter Demo',
|
||||||
|
theme: ThemeData(
|
||||||
|
// This is the theme of your application.
|
||||||
|
//
|
||||||
|
// Try running your application with "flutter run". You'll see the
|
||||||
|
// application has a blue toolbar. Then, without quitting the app, try
|
||||||
|
// changing the primarySwatch below to Colors.green and then invoke
|
||||||
|
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||||
|
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||||
|
// Notice that the counter didn't reset back to zero; the application
|
||||||
|
// is not restarted.
|
||||||
|
primarySwatch: Colors.blue,
|
||||||
|
),
|
||||||
|
home: MyHomePage(title: 'Flutter Demo Home Page'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyHomePage extends StatefulWidget {
|
||||||
|
MyHomePage({Key? key, required this.title}) : super(key: key);
|
||||||
|
|
||||||
|
// This widget is the home page of your application. It is stateful, meaning
|
||||||
|
// that it has a State object (defined below) that contains fields that affect
|
||||||
|
// how it looks.
|
||||||
|
|
||||||
|
// This class is the configuration for the state. It holds the values (in this
|
||||||
|
// case the title) provided by the parent (in this case the App widget) and
|
||||||
|
// used by the build method of the State. Fields in a Widget subclass are
|
||||||
|
// always marked "final".
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MyHomePageState createState() => _MyHomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
|
int _counter = 0;
|
||||||
|
|
||||||
|
void _incrementCounter() {
|
||||||
|
setState(() {
|
||||||
|
// This call to setState tells the Flutter framework that something has
|
||||||
|
// changed in this State, which causes it to rerun the build method below
|
||||||
|
// so that the display can reflect the updated values. If we changed
|
||||||
|
// _counter without calling setState(), then the build method would not be
|
||||||
|
// called again, and so nothing would appear to happen.
|
||||||
|
_counter++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// This method is rerun every time setState is called, for instance as done
|
||||||
|
// by the _incrementCounter method above.
|
||||||
|
//
|
||||||
|
// The Flutter framework has been optimized to make rerunning build methods
|
||||||
|
// fast, so that you can just rebuild anything that needs updating rather
|
||||||
|
// than having to individually change instances of widgets.
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
// Here we take the value from the MyHomePage object that was created by
|
||||||
|
// the App.build method, and use it to set our appbar title.
|
||||||
|
title: Text(widget.title),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
// Center is a layout widget. It takes a single child and positions it
|
||||||
|
// in the middle of the parent.
|
||||||
|
child: Column(
|
||||||
|
// Column is also a layout widget. It takes a list of children and
|
||||||
|
// arranges them vertically. By default, it sizes itself to fit its
|
||||||
|
// children horizontally, and tries to be as tall as its parent.
|
||||||
|
//
|
||||||
|
// Invoke "debug painting" (press "p" in the console, choose the
|
||||||
|
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
||||||
|
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
||||||
|
// to see the wireframe for each widget.
|
||||||
|
//
|
||||||
|
// Column has various properties to control how it sizes itself and
|
||||||
|
// how it positions its children. Here we use mainAxisAlignment to
|
||||||
|
// center the children vertically; the main axis here is the vertical
|
||||||
|
// axis because Columns are vertical (the cross axis would be
|
||||||
|
// horizontal).
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
'You have pushed the button this many times:',
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'$_counter',
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: _incrementCounter,
|
||||||
|
tooltip: 'Increment',
|
||||||
|
child: Icon(Icons.add),
|
||||||
|
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -180,7 +180,7 @@ class MessageDetail extends StatelessWidget {
|
|||||||
message.messageID != "") {
|
message.messageID != "") {
|
||||||
PackageModel packageModel =
|
PackageModel packageModel =
|
||||||
Provider.of<PackageModel>(context, listen: false);
|
Provider.of<PackageModel>(context, listen: false);
|
||||||
Package p = await packageModel.getPackage(message.messageID!);
|
Package? p = await packageModel.getPackage(message.messageID!);
|
||||||
if (p == null) return;
|
if (p == null) return;
|
||||||
Navigator.push<bool>(context,
|
Navigator.push<bool>(context,
|
||||||
CupertinoPageRoute(builder: (context) => PackageInfo(package: p)));
|
CupertinoPageRoute(builder: (context) => PackageInfo(package: p)));
|
||||||
@@ -206,7 +206,7 @@ class MessageDetail extends StatelessWidget {
|
|||||||
message.messageID != "") {
|
message.messageID != "") {
|
||||||
ShipmentModel shipmentModel =
|
ShipmentModel shipmentModel =
|
||||||
Provider.of<ShipmentModel>(context, listen: false);
|
Provider.of<ShipmentModel>(context, listen: false);
|
||||||
Shipment s = await shipmentModel.getShipment(message.messageID!);
|
Shipment? s = await shipmentModel.getShipment(message.messageID!);
|
||||||
if (s == null) return;
|
if (s == null) return;
|
||||||
await Navigator.push<bool>(
|
await Navigator.push<bool>(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -8,27 +8,28 @@ import 'package:fcs/pages/widgets/local_text.dart';
|
|||||||
import 'package:fcs/pages/widgets/progress.dart';
|
import 'package:fcs/pages/widgets/progress.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:zefyr/zefyr.dart';
|
// import 'package:zefyr/zefyr.dart';
|
||||||
|
|
||||||
typedef void ProfileCallback();
|
typedef void ProfileCallback();
|
||||||
|
|
||||||
class TermEdit extends StatefulWidget {
|
class TermEdit extends StatefulWidget {
|
||||||
final Term term;
|
final Term term;
|
||||||
TermEdit({this.term});
|
TermEdit({required this.term});
|
||||||
@override
|
@override
|
||||||
_TermEditState createState() => _TermEditState();
|
_TermEditState createState() => _TermEditState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TermEditState extends State<TermEdit> {
|
class _TermEditState extends State<TermEdit> {
|
||||||
/// Allows to control the editor and the document.
|
/// Allows to control the editor and the document.
|
||||||
ZefyrController _controllerEng;
|
// ZefyrController _controllerEng;
|
||||||
ZefyrController _controllerMm;
|
// ZefyrController _controllerMm;
|
||||||
|
|
||||||
/// Zefyr editor like any other input field requires a focus node.
|
/// Zefyr editor like any other input field requires a focus node.
|
||||||
FocusNode _focusNodeEng;
|
// FocusNode _focusNodeEng;
|
||||||
FocusNode _focusNodeMm;
|
// FocusNode _focusNodeMm;
|
||||||
bool _isLoading;
|
bool _isLoading = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -36,23 +37,23 @@ class _TermEditState extends State<TermEdit> {
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
|
|
||||||
// Here we must load the document and pass it to Zefyr controller.
|
// Here we must load the document and pass it to Zefyr controller.
|
||||||
_controllerEng = ZefyrController(_loadDocument(widget.term.termEng));
|
// _controllerEng = ZefyrController(_loadDocument(widget.term.termEng));
|
||||||
_controllerMm = ZefyrController(_loadDocument(widget.term.termMm));
|
// _controllerMm = ZefyrController(_loadDocument(widget.term.termMm));
|
||||||
_focusNodeEng = FocusNode();
|
// _focusNodeEng = FocusNode();
|
||||||
_focusNodeMm = FocusNode();
|
// _focusNodeMm = FocusNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the document to be edited in Zefyr.
|
/// Loads the document to be edited in Zefyr.
|
||||||
NotusDocument _loadDocument(String data) {
|
// NotusDocument _loadDocument(String data) {
|
||||||
NotusDocument doc;
|
// NotusDocument doc;
|
||||||
try {
|
// try {
|
||||||
doc = NotusDocument.fromJson(jsonDecode(data));
|
// doc = NotusDocument.fromJson(jsonDecode(data));
|
||||||
} catch (e) {}
|
// } catch (e) {}
|
||||||
if (doc == null) {
|
// if (doc == null) {
|
||||||
doc = NotusDocument();
|
// doc = NotusDocument();
|
||||||
}
|
// }
|
||||||
return doc;
|
// return doc;
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -115,8 +116,8 @@ class _TermEditState extends State<TermEdit> {
|
|||||||
height: MediaQuery.of(context).size.height - 200,
|
height: MediaQuery.of(context).size.height - 200,
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
children: [
|
children: [
|
||||||
textEditor(_controllerEng, _focusNodeEng),
|
// textEditor(_controllerEng, _focusNodeEng),
|
||||||
textEditor(_controllerMm, _focusNodeMm),
|
// textEditor(_controllerMm, _focusNodeMm),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -128,35 +129,35 @@ class _TermEditState extends State<TermEdit> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget textEditor(ZefyrController controller, FocusNode focusNode) {
|
// Widget textEditor(ZefyrController controller, FocusNode focusNode) {
|
||||||
return ListView(
|
// return ListView(
|
||||||
children: [
|
// children: [
|
||||||
Container(
|
// Container(
|
||||||
height: MediaQuery.of(context).size.height - 200,
|
// height: MediaQuery.of(context).size.height - 200,
|
||||||
child: ZefyrScaffold(
|
// child: ZefyrScaffold(
|
||||||
child: ZefyrTheme(
|
// child: ZefyrTheme(
|
||||||
data: ZefyrThemeData().copyWith(
|
// data: ZefyrThemeData().copyWith(
|
||||||
defaultLineTheme: LineTheme(
|
// defaultLineTheme: LineTheme(
|
||||||
padding: EdgeInsets.all(0),
|
// padding: EdgeInsets.all(0),
|
||||||
textStyle: TextStyle(fontFamily: "Myanmar3"),
|
// textStyle: TextStyle(fontFamily: "Myanmar3"),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
child: ZefyrEditor(
|
// child: ZefyrEditor(
|
||||||
autofocus: false,
|
// autofocus: false,
|
||||||
padding: EdgeInsets.all(16),
|
// padding: EdgeInsets.all(16),
|
||||||
controller: controller,
|
// controller: controller,
|
||||||
focusNode: focusNode,
|
// focusNode: focusNode,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
// savebtn,
|
// // savebtn,
|
||||||
SizedBox(
|
// SizedBox(
|
||||||
height: 10,
|
// height: 10,
|
||||||
)
|
// )
|
||||||
],
|
// ],
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
_unfocus() {
|
_unfocus() {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
@@ -167,11 +168,11 @@ class _TermEditState extends State<TermEdit> {
|
|||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final contentsEng = jsonEncode(_controllerEng.document);
|
// final contentsEng = jsonEncode(_controllerEng.document);
|
||||||
final contentsMm = jsonEncode(_controllerMm.document);
|
// final contentsMm = jsonEncode(_controllerMm.document);
|
||||||
print('contents => $contentsEng');
|
// print('contents => $contentsEng');
|
||||||
TermModel termModel = Provider.of<TermModel>(context, listen: false);
|
// TermModel termModel = Provider.of<TermModel>(context, listen: false);
|
||||||
await termModel.saveTerm(Term(termEng: contentsEng, termMm: contentsMm));
|
// await termModel.saveTerm(Term(termEng: contentsEng, termMm: contentsMm));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showMsgDialog(context, "Error", e.toString());
|
showMsgDialog(context, "Error", e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -10,46 +10,46 @@ import 'package:fcs/pages/widgets/local_text.dart';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:zefyr/zefyr.dart';
|
// import 'package:zefyr/zefyr.dart';
|
||||||
|
|
||||||
typedef void ProfileCallback();
|
typedef void ProfileCallback();
|
||||||
|
|
||||||
class TermPage extends StatefulWidget {
|
class TermPage extends StatefulWidget {
|
||||||
const TermPage({
|
const TermPage({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
_TermPageState createState() => _TermPageState();
|
_TermPageState createState() => _TermPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TermPageState extends State<TermPage> {
|
class _TermPageState extends State<TermPage> {
|
||||||
ZefyrController _controller;
|
// ZefyrController _controller;
|
||||||
FocusNode _focusNode;
|
// FocusNode _focusNode;
|
||||||
NotusDocument document = new NotusDocument();
|
// NotusDocument document = new NotusDocument();
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_focusNode = FocusNode();
|
// _focusNode = FocusNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
NotusDocument _loadDocument(Setting setting) {
|
// NotusDocument _loadDocument(Setting setting) {
|
||||||
bool isEng = Provider.of<LanguageModel>(context).isEng;
|
// bool isEng = Provider.of<LanguageModel>(context).isEng;
|
||||||
String term = isEng ? setting.termsEng : setting.termsMm;
|
// String term = isEng ? setting.termsEng : setting.termsMm;
|
||||||
NotusDocument doc;
|
// NotusDocument doc;
|
||||||
try {
|
// try {
|
||||||
doc = NotusDocument.fromJson(jsonDecode(term));
|
// doc = NotusDocument.fromJson(jsonDecode(term));
|
||||||
} catch (e) {}
|
// } catch (e) {}
|
||||||
if (doc == null) {
|
// if (doc == null) {
|
||||||
doc = NotusDocument();
|
// doc = NotusDocument();
|
||||||
}
|
// }
|
||||||
return doc;
|
// return doc;
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Setting setting = Provider.of<MainModel>(context).setting;
|
Setting? setting = Provider.of<MainModel>(context).setting;
|
||||||
bool isEditable = context.select((MainModel m) => m.termEditable());
|
bool isEditable = context.select((MainModel m) => m.termEditable());
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -76,7 +76,7 @@ class _TermPageState extends State<TermPage> {
|
|||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(context).push<void>(CupertinoPageRoute(
|
Navigator.of(context).push<void>(CupertinoPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
TermEdit(term: Term.fromSetting(setting)),
|
TermEdit(term: Term.fromSetting(setting!)),
|
||||||
)),
|
)),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
CupertinoIcons.pen,
|
CupertinoIcons.pen,
|
||||||
@@ -85,25 +85,25 @@ class _TermPageState extends State<TermPage> {
|
|||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
),
|
),
|
||||||
body: ZefyrTheme(
|
// body: ZefyrTheme(
|
||||||
data: ZefyrThemeData().copyWith(
|
|
||||||
defaultLineTheme: LineTheme(
|
|
||||||
padding: EdgeInsets.all(0),
|
|
||||||
textStyle: TextStyle(fontFamily: "Myanmar3"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// data: ZefyrThemeData().copyWith(
|
// data: ZefyrThemeData().copyWith(
|
||||||
// defaultLineTheme: LineTheme(
|
// defaultLineTheme: LineTheme(
|
||||||
// textStyle: TextStyle(color: Colors.black),
|
// padding: EdgeInsets.all(0),
|
||||||
// padding: EdgeInsets.all(0))),
|
// textStyle: TextStyle(fontFamily: "Myanmar3"),
|
||||||
child: ZefyrScaffold(
|
// ),
|
||||||
child: ZefyrEditor(
|
// ),
|
||||||
mode: ZefyrMode.view,
|
// // data: ZefyrThemeData().copyWith(
|
||||||
padding: EdgeInsets.all(16),
|
// // defaultLineTheme: LineTheme(
|
||||||
controller: ZefyrController(_loadDocument(setting)),
|
// // textStyle: TextStyle(color: Colors.black),
|
||||||
focusNode: _focusNode,
|
// // padding: EdgeInsets.all(0))),
|
||||||
),
|
// child: ZefyrScaffold(
|
||||||
)),
|
// child: ZefyrEditor(
|
||||||
|
// mode: ZefyrMode.view,
|
||||||
|
// padding: EdgeInsets.all(16),
|
||||||
|
// controller: ZefyrController(_loadDocument(setting)),
|
||||||
|
// focusNode: _focusNode,
|
||||||
|
// ),
|
||||||
|
// )),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
linux/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
flutter/ephemeral
|
||||||
116
linux/CMakeLists.txt
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(BINARY_NAME "fcs")
|
||||||
|
set(APPLICATION_ID "com.mokkon.fcs.fcs")
|
||||||
|
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||||
|
|
||||||
|
# Root filesystem for cross-building.
|
||||||
|
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||||
|
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Configure build options.
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Compilation settings that should be applied to most targets.
|
||||||
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
|
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||||
|
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||||
|
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
|
|
||||||
|
# Flutter library and tool build rules.
|
||||||
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|
||||||
|
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||||
|
|
||||||
|
# Application build
|
||||||
|
add_executable(${BINARY_NAME}
|
||||||
|
"main.cc"
|
||||||
|
"my_application.cc"
|
||||||
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
|
)
|
||||||
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||||
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
# Only the install-generated bundle's copy of the executable will launch
|
||||||
|
# correctly, since the resources must in the right relative locations. To avoid
|
||||||
|
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||||
|
# the default top-level location.
|
||||||
|
set_target_properties(${BINARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
|
# them to the application.
|
||||||
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
# By default, "installing" just makes a relocatable bundle in the build
|
||||||
|
# directory.
|
||||||
|
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Start with a clean build bundle directory every time.
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
|
||||||
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||||
|
|
||||||
|
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.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||||
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endif()
|
||||||
87
linux/flutter/CMakeLists.txt
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||||
|
# which isn't available in 3.10.
|
||||||
|
function(list_prepend LIST_NAME PREFIX)
|
||||||
|
set(NEW_LIST "")
|
||||||
|
foreach(element ${${LIST_NAME}})
|
||||||
|
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||||
|
endforeach(element)
|
||||||
|
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# === Flutter Library ===
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||||
|
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||||
|
|
||||||
|
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||||
|
|
||||||
|
# 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/lib/libapp.so" PARENT_SCOPE)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||||
|
"fl_basic_message_channel.h"
|
||||||
|
"fl_binary_codec.h"
|
||||||
|
"fl_binary_messenger.h"
|
||||||
|
"fl_dart_project.h"
|
||||||
|
"fl_engine.h"
|
||||||
|
"fl_json_message_codec.h"
|
||||||
|
"fl_json_method_codec.h"
|
||||||
|
"fl_message_codec.h"
|
||||||
|
"fl_method_call.h"
|
||||||
|
"fl_method_channel.h"
|
||||||
|
"fl_method_codec.h"
|
||||||
|
"fl_method_response.h"
|
||||||
|
"fl_plugin_registrar.h"
|
||||||
|
"fl_plugin_registry.h"
|
||||||
|
"fl_standard_message_codec.h"
|
||||||
|
"fl_standard_method_codec.h"
|
||||||
|
"fl_string_codec.h"
|
||||||
|
"fl_value.h"
|
||||||
|
"fl_view.h"
|
||||||
|
"flutter_linux.h"
|
||||||
|
)
|
||||||
|
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||||
|
add_library(flutter INTERFACE)
|
||||||
|
target_include_directories(flutter INTERFACE
|
||||||
|
"${EPHEMERAL_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||||
|
target_link_libraries(flutter INTERFACE
|
||||||
|
PkgConfig::GTK
|
||||||
|
PkgConfig::GLIB
|
||||||
|
PkgConfig::GIO
|
||||||
|
)
|
||||||
|
add_dependencies(flutter 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.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
${FLUTTER_TOOL_ENVIRONMENT}
|
||||||
|
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||||
|
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(flutter_assemble DEPENDS
|
||||||
|
"${FLUTTER_LIBRARY}"
|
||||||
|
${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
)
|
||||||
13
linux/flutter/generated_plugin_registrant.cc
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
}
|
||||||
13
linux/flutter/generated_plugin_registrant.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
|
||||||
|
// Registers Flutter plugins.
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry);
|
||||||
|
|
||||||
|
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||||
16
linux/flutter/generated_plugins.cmake
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#
|
||||||
|
# Generated file, do not edit.
|
||||||
|
#
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
url_launcher_linux
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
|
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux 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)
|
||||||
6
linux/main.cc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
g_autoptr(MyApplication) app = my_application_new();
|
||||||
|
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
|
}
|
||||||
105
linux/my_application.cc
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
struct _MyApplication {
|
||||||
|
GtkApplication parent_instance;
|
||||||
|
char** dart_entrypoint_arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
|
// Implements GApplication::activate.
|
||||||
|
static void my_application_activate(GApplication* application) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
GtkWindow* window =
|
||||||
|
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||||
|
|
||||||
|
// Use a header bar when running in GNOME as this is the common style used
|
||||||
|
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||||
|
// desktop).
|
||||||
|
// If running on X and not using GNOME then just use a traditional title bar
|
||||||
|
// in case the window manager does more exotic layout, e.g. tiling.
|
||||||
|
// If running on Wayland assume the header bar will work (may need changing
|
||||||
|
// if future cases occur).
|
||||||
|
gboolean use_header_bar = TRUE;
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
GdkScreen *screen = gtk_window_get_screen(window);
|
||||||
|
if (GDK_IS_X11_SCREEN(screen)) {
|
||||||
|
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||||
|
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||||
|
use_header_bar = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (use_header_bar) {
|
||||||
|
GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
|
gtk_header_bar_set_title(header_bar, "fcs");
|
||||||
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gtk_window_set_title(window, "fcs");
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
|
FlView* view = fl_view_new(project);
|
||||||
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
|
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::local_command_line.
|
||||||
|
static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
// Strip out the first argument as it is the binary name.
|
||||||
|
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||||
|
|
||||||
|
g_autoptr(GError) error = nullptr;
|
||||||
|
if (!g_application_register(application, nullptr, &error)) {
|
||||||
|
g_warning("Failed to register: %s", error->message);
|
||||||
|
*exit_status = 1;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_application_activate(application);
|
||||||
|
*exit_status = 0;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GObject::dispose.
|
||||||
|
static void my_application_dispose(GObject *object) {
|
||||||
|
MyApplication* self = MY_APPLICATION(object);
|
||||||
|
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||||
|
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_class_init(MyApplicationClass* klass) {
|
||||||
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
|
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||||
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_init(MyApplication* self) {}
|
||||||
|
|
||||||
|
MyApplication* my_application_new() {
|
||||||
|
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||||
|
"application-id", APPLICATION_ID,
|
||||||
|
"flags", G_APPLICATION_NON_UNIQUE,
|
||||||
|
nullptr));
|
||||||
|
}
|
||||||
18
linux/my_application.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||||
|
#define FLUTTER_MY_APPLICATION_H_
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||||
|
GtkApplication)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* my_application_new:
|
||||||
|
*
|
||||||
|
* Creates a new Flutter-based application.
|
||||||
|
*
|
||||||
|
* Returns: a new #MyApplication.
|
||||||
|
*/
|
||||||
|
MyApplication* my_application_new();
|
||||||
|
|
||||||
|
#endif // FLUTTER_MY_APPLICATION_H_
|
||||||
61
pubspec.lock
@@ -70,7 +70,7 @@ packages:
|
|||||||
name: camera
|
name: camera
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.0"
|
version: "0.9.2+2"
|
||||||
camera_platform_interface:
|
camera_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -196,7 +196,7 @@ packages:
|
|||||||
name: cross_file
|
name: cross_file
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1+4"
|
version: "0.3.1+5"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -259,21 +259,21 @@ packages:
|
|||||||
name: firebase_auth
|
name: firebase_auth
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.1.1"
|
||||||
firebase_auth_platform_interface:
|
firebase_auth_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_auth_platform_interface
|
name: firebase_auth_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.1"
|
version: "6.1.0"
|
||||||
firebase_auth_web:
|
firebase_auth_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_auth_web
|
name: firebase_auth_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.1.0"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -295,27 +295,48 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
firebase_messaging:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_messaging
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.7"
|
||||||
|
firebase_messaging_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.5"
|
||||||
|
firebase_messaging_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
firebase_storage:
|
firebase_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_storage
|
name: firebase_storage
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.2"
|
version: "10.0.3"
|
||||||
firebase_storage_platform_interface:
|
firebase_storage_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_storage_platform_interface
|
name: firebase_storage_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "4.0.2"
|
||||||
firebase_storage_web:
|
firebase_storage_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_storage_web
|
name: firebase_storage_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -388,7 +409,7 @@ packages:
|
|||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -454,7 +475,7 @@ packages:
|
|||||||
name: image_picker
|
name: image_picker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.3+3"
|
version: "0.8.4"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -468,7 +489,7 @@ packages:
|
|||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.4.1"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -573,7 +594,7 @@ packages:
|
|||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -615,7 +636,7 @@ packages:
|
|||||||
name: permission_handler
|
name: permission_handler
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.4+2"
|
version: "8.1.6"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -706,7 +727,7 @@ packages:
|
|||||||
name: rxdart
|
name: rxdart
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.27.1"
|
version: "0.27.2"
|
||||||
share:
|
share:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -823,7 +844,7 @@ packages:
|
|||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1+1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -893,7 +914,7 @@ packages:
|
|||||||
name: timeline_list
|
name: timeline_list
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.5"
|
version: "0.0.6"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -921,21 +942,21 @@ packages:
|
|||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.9"
|
version: "6.0.10"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.2"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.2"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1005,7 +1026,7 @@ packages:
|
|||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.6"
|
version: "2.2.9"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
cupertino_icons: ^1.0.3
|
cupertino_icons: ^1.0.3
|
||||||
firebase_core: ^1.5.0
|
firebase_core: ^1.6.0
|
||||||
cloud_firestore: ^2.5.2
|
cloud_firestore: ^2.5.2
|
||||||
firebase_storage: ^10.0.2
|
firebase_storage: ^10.0.3
|
||||||
firebase_auth: "^3.0.2"
|
firebase_auth: ^3.1.1
|
||||||
# firebase_messaging: ^6.0.15
|
firebase_messaging: ^10.0.7
|
||||||
|
|
||||||
provider: ^6.0.0
|
provider: ^6.0.0
|
||||||
image_picker: ^0.8.3+3
|
image_picker: ^0.8.3+3
|
||||||
@@ -73,4 +73,3 @@ flutter:
|
|||||||
- family: Myanmar3
|
- family: Myanmar3
|
||||||
fonts:
|
fonts:
|
||||||
- asset: assets/fonts/Myanmar3_2018.ttf
|
- asset: assets/fonts/Myanmar3_2018.ttf
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import 'package:fcs/domain/entities/cargo_type.dart';
|
|
||||||
import 'package:fcs/domain/entities/carton.dart';
|
|
||||||
import 'package:fcs/domain/entities/discount_by_weight.dart';
|
|
||||||
import 'package:fcs/domain/entities/rate.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
@TestOn('vm')
|
|
||||||
void main() {
|
|
||||||
var rate = Rate();
|
|
||||||
rate.discountByWeights = [
|
|
||||||
DiscountByWeight(weight: 25, discount: 0.25),
|
|
||||||
DiscountByWeight(weight: 50, discount: 0.5),
|
|
||||||
];
|
|
||||||
rate.volumetricRatio = 166.36;
|
|
||||||
rate.diffDiscountWeight = 5;
|
|
||||||
rate.diffWeightRate = 1.5;
|
|
||||||
|
|
||||||
test('Calculate carton price 1', () {
|
|
||||||
var ct = CargoType(name: "General", weight: 10, rate: 6);
|
|
||||||
var carton = Carton(cargoTypes: [ct], length: 15, width: 15, height: 15);
|
|
||||||
expect(carton.calAmount(rate), equals(67.50));
|
|
||||||
});
|
|
||||||
test('Calculate carton price 2', () {
|
|
||||||
var ct = CargoType(name: "General", weight: 10, rate: 6);
|
|
||||||
var carton = Carton(cargoTypes: [ct], length: 10, width: 10, height: 10);
|
|
||||||
expect(carton.calAmount(rate), equals(60));
|
|
||||||
});
|
|
||||||
test('Calculate carton price 3', () {
|
|
||||||
var ct = CargoType(name: "General", weight: 10, rate: 6);
|
|
||||||
var carton = Carton(cargoTypes: [ct], length: 15, width: 15, height: 10);
|
|
||||||
expect(carton.calAmount(rate), equals(60));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
30
test/widget_test.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// This is a basic Flutter widget test.
|
||||||
|
//
|
||||||
|
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||||
|
// utility that Flutter provides. For example, you can send tap and scroll
|
||||||
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:fcs/main.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
|
// Build our app and trigger a frame.
|
||||||
|
await tester.pumpWidget(MyApp());
|
||||||
|
|
||||||
|
// Verify that our counter starts at 0.
|
||||||
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
expect(find.text('1'), findsNothing);
|
||||||
|
|
||||||
|
// Tap the '+' icon and trigger a frame.
|
||||||
|
await tester.tap(find.byIcon(Icons.add));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify that our counter has incremented.
|
||||||
|
expect(find.text('0'), findsNothing);
|
||||||
|
expect(find.text('1'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
BIN
web/favicon.png
Normal file
|
After Width: | Height: | Size: 917 B |
BIN
web/icons/Icon-192.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
web/icons/Icon-512.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
98
web/index.html
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<!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
|
||||||
|
-->
|
||||||
|
<base href="/">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- 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="fcs">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<title>fcs</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 plaint <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>
|
||||||
23
web/manifest.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "fcs",
|
||||||
|
"short_name": "fcs",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||