Dubhe

add: first commit

Showing 100 changed files with 3959 additions and 0 deletions

Too many changes to show.

To preserve performance only 100 of 100+ files are displayed.

# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# 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
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/
... ...
# 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: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: android
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: ios
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
... ...
## 0.0.1
* TODO: Describe initial release.
... ...
TODO: Add your license here.
... ...
# auto_track
Auto Track Plugin
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter development, view the
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
... ...
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
... ...
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
... ...
group 'mobi.iflow.flutter.auto_track.auto_track'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
if (project.android.hasProperty("namespace")) {
namespace 'mobi.iflow.flutter.auto_track.auto_track'
}
compileSdkVersion 33
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
}
defaultConfig {
minSdkVersion 19
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}
... ...
rootProject.name = 'auto_track'
... ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="mobi.iflow.flutter.auto_track.auto_track">
</manifest>
... ...
package mobi.iflow.flutter.auto_track.auto_track
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/** AutoTrackPlugin */
class AutoTrackPlugin: FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "auto_track")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
... ...
package mobi.iflow.flutter.auto_track.auto_track
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlin.test.Test
import org.mockito.Mockito
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class AutoTrackPluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = AutoTrackPlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}
... ...
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# 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
.pub-cache/
.pub/
/build/
# 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
... ...
# auto_track_example
Demonstrates how to use the auto_track plugin.
## 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://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
... ...
# 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.dev/lints.
#
# 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
... ...
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
... ...
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "mobi.iflow.flutter.auto_track.auto_track_example"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
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 "mobi.iflow.flutter.auto_track.auto_track_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
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 {}
... ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool 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>
... ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="auto_track_example"
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:value="2" />
</application>
</manifest>
... ...
package mobi.iflow.flutter.auto_track.auto_track_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
... ...
<?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>
... ...
<?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:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
... ...
<?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
the Flutter engine 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>
... ...
<?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
the Flutter engine 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>
</resources>
... ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool 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>
... ...
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
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')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
... ...
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
... ...
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
... ...
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
}
include ":app"
... ...
// This is a basic Flutter integration test.
//
// Since integration tests run in a full Flutter application, they can interact
// with the host side of a plugin implementation, unlike Dart unit tests.
//
// For more information about Flutter integration tests, please see
// https://docs.flutter.dev/cookbook/testing/integration/introduction
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:auto_track/auto_track.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
final AutoTrack plugin = AutoTrack();
// final String? version = await plugin.getPlatformVersion();
// The version string depends on the host platform running the test, so
// just assert that some non-empty string is returned.
// expect(version?.isNotEmpty, true);
});
}
... ...
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
... ...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
</dict>
</plist>
... ...
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
... ...
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
... ...
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
... ...
PODS:
- auto_track (0.0.1):
- Flutter
- Flutter (1.0.0)
- integration_test (0.0.1):
- Flutter
DEPENDENCIES:
- auto_track (from `.symlinks/plugins/auto_track/ios`)
- Flutter (from `Flutter`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
EXTERNAL SOURCES:
auto_track:
:path: ".symlinks/plugins/auto_track/ios"
Flutter:
:path: Flutter
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
SPEC CHECKSUMS:
auto_track: bca6ccfba824abcc8de9d51ac0eeed3dfb141d5d
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
integration_test: 13825b8a9334a850581300559b8839134b124670
PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189
COCOAPODS: 1.12.1
... ...
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
868313707E054002C9EBBC23 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 830356C44B37234273CFF03C /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
9A4BC01B8C3E977CE74C747D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD602501C62F389F5FFD3029 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
12AD15FE86700BFEC11CC92D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
229DB93165071F31B9EA4231 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
361BA2F76E56261380B96FE7 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
830356C44B37234273CFF03C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D1653FC9B1E82CA1821FD739 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
DD602501C62F389F5FFD3029 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
EB9A8197B759B617DFCCB6F1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
F782189BA737B0E3C9A3C330 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
93DAF74BCB585A23752E0704 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
868313707E054002C9EBBC23 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9A4BC01B8C3E977CE74C747D /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
5F8A6DB13075A4D449F6A685 /* Frameworks */ = {
isa = PBXGroup;
children = (
DD602501C62F389F5FFD3029 /* Pods_Runner.framework */,
830356C44B37234273CFF03C /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
936DC10DBAFF5951E82260FA /* Pods */ = {
isa = PBXGroup;
children = (
D1653FC9B1E82CA1821FD739 /* Pods-Runner.debug.xcconfig */,
229DB93165071F31B9EA4231 /* Pods-Runner.release.xcconfig */,
361BA2F76E56261380B96FE7 /* Pods-Runner.profile.xcconfig */,
EB9A8197B759B617DFCCB6F1 /* Pods-RunnerTests.debug.xcconfig */,
F782189BA737B0E3C9A3C330 /* Pods-RunnerTests.release.xcconfig */,
12AD15FE86700BFEC11CC92D /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
936DC10DBAFF5951E82260FA /* Pods */,
5F8A6DB13075A4D449F6A685 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
E48DE49AC08A847B9639DEEA /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
93DAF74BCB585A23752E0704 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
34BC5E1D1DEA8AEF16370400 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
DE97054EB3448BA88001379F /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
34BC5E1D1DEA8AEF16370400 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
DE97054EB3448BA88001379F /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E48DE49AC08A847B9639DEEA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = FVLFR9QYGC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = mobi.iflow.flutter.autotrack.autoTrackExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = EB9A8197B759B617DFCCB6F1 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = mobi.iflow.flutter.autotrack.autoTrackExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F782189BA737B0E3C9A3C330 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = mobi.iflow.flutter.autotrack.autoTrackExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 12AD15FE86700BFEC11CC92D /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = mobi.iflow.flutter.autotrack.autoTrackExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = FVLFR9QYGC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = mobi.iflow.flutter.autotrack.autoTrackExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = FVLFR9QYGC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = mobi.iflow.flutter.autotrack.autoTrackExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
... ...
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
... ...
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
... ...
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
... ...
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
... ...
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
... ...
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Auto Track</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>auto_track_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
... ...
#import "GeneratedPluginRegistrant.h"
... ...
import Flutter
import UIKit
import XCTest
@testable import auto_track
// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
//
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
class RunnerTests: XCTestCase {
func testGetPlatformVersion() {
let plugin = AutoTrackPlugin()
let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
let resultExpectation = expectation(description: "result block must be called.")
plugin.handle(call) { result in
XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
resultExpectation.fulfill()
}
waitForExpectations(timeout: 1)
}
}
... ...
import 'package:auto_track_example/page_a.dart';
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: [
ListTile(
title: const Text('page a'),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const PageA();
}));
},
)
],
);
}
}
... ...
import 'package:auto_track/auto_track.dart';
import 'package:auto_track/auto_track/config/config.dart';
import 'package:auto_track/auto_track/index.dart';
import 'package:auto_track_example/home.dart';
import 'package:auto_track_example/page_a.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
AutoTrack()
.config(AutoTrackConfig(
pageConfigs: [
AutoTrackPageConfig<PageA>(
pageID: 'page_a',
),
]))
.enable()
.enablePageLeave()
.enablePageView()
.enableClick()
.enableLog();
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('auto track example app'),
),
body: const Center(
child: Home(),
),
),
navigatorObservers: AutoTrackNavigationObserver.wrap([]),
);
}
}
... ...
import 'package:flutter/cupertino.dart';
class PageA extends StatelessWidget {
const PageA({super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print("tap page a");
},
child: const Text('page a'),
);
}
}
... ...
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
auto_track:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.6"
dio:
dependency: transitive
description:
name: dio
sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8"
url: "https://pub.dev"
source: hosted
version: "5.4.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_driver:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
lints:
dependency: transitive
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
meta:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.10.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
platform:
dependency: transitive
description:
name: platform
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
url: "https://pub.dev"
source: hosted
version: "3.1.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
process:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
sync_http:
dependency: transitive
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
uuid:
dependency: transitive
description:
name: uuid
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev"
source: hosted
version: "4.3.3"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
url: "https://pub.dev"
source: hosted
version: "11.10.0"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.3.0"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
sdks:
dart: ">=3.2.3 <4.0.0"
flutter: ">=3.3.0"
... ...
name: auto_track_example
description: "Demonstrates how to use the auto_track plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: '>=3.2.3 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
auto_track:
# When depending on this package from a real application you should use:
# auto_track: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
integration_test:
sdk: flutter
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.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 packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# 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
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
... ...
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. 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:auto_track_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}
... ...
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh
\ No newline at end of file
... ...
import Flutter
import UIKit
public class AutoTrackPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "auto_track", binaryMessenger: registrar.messenger())
let instance = AutoTrackPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}
... ...
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint auto_track.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'auto_track'
s.version = '0.0.1'
s.summary = 'Auto Track Plugin'
s.description = <<-DESC
Auto Track Plugin
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '11.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end
... ...
library autotrack;
export './auto_track/index.dart';
export './auto_track/config/config.dart';
export './auto_track/page_view/navigation_observer.dart';
export './auto_track/click/navigator_key.dart';
export './auto_track/click/element_key.dart';
export './auto_track/log/logger.dart';
\ No newline at end of file
... ...
import 'package:flutter/widgets.dart';
import '../config/manager.dart';
import '../page_view/page_info.dart';
import '../utils/element_util.dart';
import 'element_key.dart';
import 'xpath.dart';
class ClickInfo {
ClickInfo._(this.pageInfo);
factory ClickInfo.from({
required Element gestureElement,
required PointerDownEvent event,
required Element pageElement,
required PageInfo pageInfo,
}) {
ClickInfo clickInfo = ClickInfo._(pageInfo);
clickInfo._touchX = event.position.dx.round();
clickInfo._touchY = event.position.dy.round();
XPath xpath = XPath.createBy(element: gestureElement, pageElement: pageElement);
Element element = xpath.targetElement;
clickInfo._elementType = element.widget.runtimeType.toString();
clickInfo._elementWidth = element.size?.width.round() ?? 0;
clickInfo._elementHeight = element.size?.height.round() ?? 0;
clickInfo._elementPath = xpath.toString();
clickInfo._texts = ElementUtil.findTexts(element);
Key? key = element.widget.key;
if (key != null && key is ValueKey) {
clickInfo._elementManualKey = (key).value;
} else {
clickInfo._elementManualKey = key?.toString() ?? '';
}
clickInfo._ignore = AutoTrackConfigManager.instance.isIgnoreElement(key);
if (key is AutoTrackElementKey && !clickInfo._ignore) {
clickInfo._ignore = key.ignore;
}
return clickInfo;
}
int _touchX = 0;
int get touchX => _touchX;
int _touchY = 0;
int get touchY => _touchY;
int _elementWidth = 0;
int get elementWidth => _elementWidth;
int _elementHeight = 0;
int get elementHeight => _elementHeight;
String _elementType = '';
String get elementType => _elementType;
String _elementManualKey = '';
String get elementManualKey => _elementManualKey;
String _elementPath = '';
String get elementPath => _elementPath;
bool _ignore = false;
bool get ignore => _ignore;
List<String> _texts = [];
List<String> get texts => _texts;
final PageInfo pageInfo;
@override
String toString() {
return [
'elementType: $elementType',
'elementManualKey: $elementManualKey',
'elementPath: $elementPath',
'touchX: $touchX',
'touchY: $touchY',
'elementWidth: $elementWidth',
'elementHeight: $elementHeight',
'texts: ${texts.join(";")}',
'pageInfo: $pageInfo',
].join(', ');
}
}
... ...
import 'package:flutter/widgets.dart';
class AutoTrackElementKey extends Key {
const AutoTrackElementKey(this.name, {
this.ignore = false
}) : super.empty();
final String name;
final bool ignore;
@override
String toString() {
return name;
}
}
... ...
import 'package:flutter/widgets.dart';
class AutoTrackNavigatorKey {
static AutoTrackNavigatorKey? _instance;
AutoTrackNavigatorKey._();
static AutoTrackNavigatorKey _getInstance() {
_instance ??= AutoTrackNavigatorKey._();
return _instance!;
}
GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
static GlobalKey<NavigatorState> navigatorKeyWrap(GlobalKey<NavigatorState>? navigatorKey) {
if (navigatorKey != null) {
_getInstance()._navigatorKey = navigatorKey;
}
return _getInstance()._navigatorKey;
}
static GlobalKey<NavigatorState> get navigatorKey => _getInstance()._navigatorKey;
}
... ...
import 'dart:collection';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../log/logger.dart';
import '../page_view/page_info.dart';
import '../page_view/page_stack.dart';
import '../track/track.dart';
import '../utils/element_util.dart';
import 'click_info.dart';
class PointerEventListener {
static PointerEventListener instance = PointerEventListener._();
PointerEventListener._();
bool _started = false;
_AutoTrackTapGestureRecognizer _gestureRecognizer = _AutoTrackTapGestureRecognizer();
void start() {
if (!_started) {
GestureBinding.instance?.pointerRouter.addGlobalRoute(_pointerRoute);
_gestureRecognizer = _AutoTrackTapGestureRecognizer();
_gestureRecognizer.onTap = (){};
_started = true;
}
}
void stop() {
if (_started) {
GestureBinding.instance?.pointerRouter.removeGlobalRoute(_pointerRoute);
_gestureRecognizer.dispose();
_started = false;
}
}
void _pointerRoute(PointerEvent event) {
try {
if (event is PointerDownEvent) {
_gestureRecognizer.addPointer(event);
} else if (event is PointerUpEvent) {
_gestureRecognizer.checkPointerUp(event);
PointerDownEvent? pointerDownEvent = _gestureRecognizer.lastPointerDownEvent;
if (event.pointer != _gestureRecognizer.rejectPointer && pointerDownEvent != null) {
_checkTapElementAndTrack(pointerDownEvent, event);
}
}
} catch (e) {
AutoTrackLogger.getInstance().error(e);
}
}
void _checkTapElementAndTrack(PointerDownEvent event, PointerUpEvent upEvent) {
final page = PageStack.instance.getCurrentPage();
if (page == null) {
return;
}
LinkedList<_HitEntry> hits = LinkedList();
ElementUtil.walkElement(page.element, (child, _) {
if (child is RenderObjectElement && child.renderObject is RenderBox) {
RenderBox renderBox = child.renderObject as RenderBox;
if (!renderBox.hasSize) {
return false;
}
Offset localPosition = renderBox.globalToLocal(upEvent.position);
if (!renderBox.size.contains(localPosition)) {
return false;
}
if (renderBox is RenderPointerListener) {
hits.add(_HitEntry(child));
}
}
return true;
});
if (hits.isEmpty) {
return;
}
_HitEntry? entry = hits.last;
Element? gestureElement;
while (entry != null) {
gestureElement = ElementUtil.findAncestorElementOfWidgetType<GestureDetector>(entry.element);
if (gestureElement != null) {
break;
}
entry = entry.previous;
}
if (gestureElement != null) {
_trackClick(gestureElement, event, page.element, page.pageInfo);
}
}
void _trackClick(Element gestureElement, PointerDownEvent event, Element pageElement, PageInfo pageInfo) {
ClickInfo clickInfo = ClickInfo.from(
gestureElement: gestureElement,
event: event,
pageElement: pageElement,
pageInfo: pageInfo,
);
if (!clickInfo.ignore) {
Track.instance.click(clickInfo);
}
}
}
class _AutoTrackTapGestureRecognizer extends TapGestureRecognizer {
_AutoTrackTapGestureRecognizer({ Object? debugOwner }) : super(debugOwner: debugOwner);
PointerDownEvent? lastPointerDownEvent;
int rejectPointer = 0;
void checkPointerUp(PointerUpEvent upEvent) {
if (lastPointerDownEvent == null) {
return;
}
Offset downPosition = lastPointerDownEvent!.position;
Offset upPosition = upEvent.position;
double offset = (downPosition.dx - upPosition.dx).abs() + (downPosition.dy - upPosition.dy).abs();
if (offset > 30) {
rejectGesture(upEvent.pointer);
}
}
@override
void addPointer(PointerDownEvent event) {
lastPointerDownEvent = event;
super.addPointer(event);
}
@override
void rejectGesture(int pointer) {
if (lastPointerDownEvent?.pointer == pointer) {
lastPointerDownEvent = null;
}
rejectPointer = pointer;
super.rejectGesture(pointer);
}
}
final class _HitEntry extends LinkedListEntry<_HitEntry> {
_HitEntry(this.element);
final Element element;
}
... ...
import 'dart:collection';
import 'package:flutter/material.dart';
class XPath {
XPath._(this._targetElement);
factory XPath.createBy({
required Element element,
required Element pageElement,
}) {
XPath xpath = XPath._(element);
xpath._targetElement = element;
final highLevelSet = _PathConst.highLevelSet;
LinkedList<_ElementEntry> originalPath = LinkedList();
originalPath.add(_ElementEntry(element));
bool lookForTarget = true;
element.visitAncestorElements((parent) {
if (parent.widget is GestureDetector) {
lookForTarget = false;
}
if (lookForTarget && highLevelSet.contains(parent.widget.runtimeType)) {
xpath._targetElement = parent;
}
originalPath.add(_ElementEntry(parent));
if (pageElement == parent) {
return false;
}
return true;
});
LinkedList<PathNode> path = xpath._buildFromOriginal(xpath._targetElement, originalPath);
xpath._shortPath(path);
if (path.isNotEmpty) {
path.first.isPage = true;
}
for (var node in path) {
node.computeIndex();
}
xpath._path = path;
return xpath;
}
Element _targetElement;
Element get targetElement => _targetElement;
LinkedList<PathNode> _path = LinkedList();
LinkedList<PathNode> get path => _path;
LinkedList<PathNode> _buildFromOriginal(Element targetElement, LinkedList<_ElementEntry> originalPath) {
LinkedList<PathNode> path = LinkedList();
if (originalPath.isEmpty) {
return path;
}
_ElementEntry? entry = originalPath.last;
while (entry != null) {
PathNode node = PathNode(entry.element);
if (!node.ignore) {
node.formatName();
path.add(node);
}
if (entry.element == targetElement) {
break;
}
entry = entry.previous;
}
return path;
}
void _shortPath(LinkedList<PathNode> path) {
if (path.isEmpty) {
return;
}
final shortWidgetMap = _PathConst.shortWidgetMap;
PathNode? node = path.first;
while (node != null) {
if (shortWidgetMap.containsKey(node.name)) {
_ShortWidgetConfig short = shortWidgetMap[node.name]!;
node = _removeInternal(path, node, short);
} else {
node = node.next;
}
}
}
PathNode? _removeInternal(LinkedList<PathNode> path, PathNode node, _ShortWidgetConfig short) {
PathNode? internalNode = node.next;
Element? indexElement;
for (String internalWidgetName in short.internalWidgets) {
if (internalNode == null) {
return null;
}
if (internalNode.name != internalWidgetName) {
return internalNode;
}
if (internalWidgetName == short.indexWidget) {
indexElement = internalNode.indexElement;
}
PathNode tmpNode = internalNode;
internalNode = internalNode.next;
path.remove(tmpNode);
}
if (indexElement != null) {
internalNode?.indexElement = indexElement;
}
return internalNode;
}
@override
String toString() {
return path.join('/');
}
}
final class _ElementEntry extends LinkedListEntry<_ElementEntry> {
_ElementEntry(this.element);
final Element element;
}
final class PathNode extends LinkedListEntry<PathNode> {
PathNode(this.indexElement) {
_name = indexElement.widget.runtimeType.toString();
_checkIgnore(indexElement);
}
String _name = '';
String get name => _name;
int _index = 0;
int get index => _index;
bool _ignore = false;
bool get ignore => _ignore;
bool isPage = false;
Element indexElement;
void formatName() {
String widgetName = _name;
int index = widgetName.indexOf('\<');
if (index > -1) {
_name = widgetName.substring(0, index);
}
}
void _checkIgnore(Element element) {
Widget widget = element.widget;
if (widget is! StatelessWidget && widget is! StatefulWidget) {
_ignore = true;
return;
}
if (_name[0] == '_') {
_ignore = true;
return;
}
}
void computeIndex() {
Element? parent;
indexElement.visitAncestorElements((element) {
parent = element;
return false;
});
if (parent == null) {
isPage = true;
return;
}
bool found = false;
_index = 0;
parent!.visitChildElements((element) {
if (element == indexElement) {
found = true;
}
if (!found) {
_index++;
}
});
}
@override
String toString() {
if (isPage) {
return _name;
}
return '$_name[$_index]';
}
}
class _PathConst {
static final Set<Type> highLevelSet = {
InkWell,
ElevatedButton,
IconButton,
TextButton,
ListTile,
};
/// key: Widget Name, value: Widget Name who handle child/children
static final Map<String, _ShortWidgetConfig> shortWidgetMap = {
'Scaffold': _ShortWidgetConfig([
'ScrollNotificationObserver',
'Material',
'AnimatedPhysicalModel',
'AnimatedDefaultTextStyle',
'AnimatedBuilder',
'Actions'
]),
'AppBar': _ShortWidgetConfig([
'Material',
'AnimatedPhysicalModel',
'AnimatedDefaultTextStyle',
'SafeArea',
'Builder',
'NavigationToolbar',
]),
'BottomNavigationBar': _ShortWidgetConfig([
'Material',
'AnimatedPhysicalModel',
'AnimatedDefaultTextStyle',
'Material',
'AnimatedDefaultTextStyle',
'Builder',
]),
'ListView': _ShortWidgetConfig([
'Scrollable',
'RawGestureDetector',
'KeyedSubtree',
'AutomaticKeepAlive',
], indexWidget: 'KeyedSubtree'),
'PageView': _ShortWidgetConfig([
'Scrollable',
'RawGestureDetector',
'SliverFillViewport',
'KeyedSubtree',
'AutomaticKeepAlive',
], indexWidget: 'KeyedSubtree'),
'Card': _ShortWidgetConfig([
'Container',
'Material',
'AnimatedDefaultTextStyle',
]),
'IconButton': _ShortWidgetConfig([
'InkResponse',
'Actions',
'Focus',
'GestureDetector',
'RawGestureDetector',
]),
'InkResponse': _ShortWidgetConfig([
'Actions',
'Focus',
'GestureDetector',
]),
};
}
class _ShortWidgetConfig {
_ShortWidgetConfig(this.internalWidgets, { this.indexWidget });
final List<String> internalWidgets;
final String? indexWidget;
}
... ...
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/widgets.dart';
import 'package:uuid/uuid.dart';
class AutoTrackConfig {
AutoTrackConfig({
this.host,
this.appKey = '',
this.appSecret = '',
this.trackId,
this.userId,
this.signature,
this.pageConfigs = const [],
this.useCustomRoute = false,
this.ignoreElementKeys = const [],
this.ignoreElementStringKeys = const [],
this.enablePageView = true,
this.enablePageLeave = false,
this.enableClick = true,
this.enableUpload = false
}) {
trackId ??= const Uuid().v4().replaceAll('-', '');
signature ??= (t) => sha256.convert(utf8.encode('$appKey$t$appSecret')).toString();
}
String? host;
String? appKey;
String? appSecret;
String? trackId;
String? userId;
Function? signature;
List<AutoTrackPageConfig> pageConfigs;
/// 如果使用 MaterialPageRoute/PageRoute/ModalRoute 之外的 Route,
/// 请打开该开关,并保证所有页面都配置在 pageConfigs 中
bool useCustomRoute;
/// 推荐使用 [ElementKey]
List<Key> ignoreElementKeys;
List<String> ignoreElementStringKeys;
Set<Key> getIgnoreElementKeySet() => Set.from(ignoreElementKeys);
Set<String> getIgnoreElementStringKeySet() =>
Set.from(ignoreElementStringKeys);
bool enablePageView;
bool enablePageLeave;
bool enableClick;
bool enableUpload;
}
typedef PageWidgetFunc = bool Function(Widget);
class AutoTrackPageConfig<T extends Widget> {
AutoTrackPageConfig({
this.pageID,
this.pagePath,
this.ignore = false,
this.pageTitle,
this.isPageWidget
}) {
isPageWidget ??= (pageWidget) => pageWidget is T;
}
String? pageID;
String? pagePath;
bool ignore;
String? pageTitle;
PageWidgetFunc? isPageWidget;
// bool isPageWidget(Widget pageWidget) => pageWidget is T;
}
... ...
import 'package:auto_track/auto_track/config/queue.dart';
import 'package:flutter/widgets.dart';
import 'config.dart';
class AutoTrackConfigManager {
static final AutoTrackConfigManager instance = AutoTrackConfigManager._();
AutoTrackConfigManager._();
AutoTrackConfig _config = AutoTrackConfig();
AutoTrackConfig get config => _config;
bool _autoTrackEnable = false;
bool get autoTrackEnable => _autoTrackEnable;
void updateConfig(AutoTrackConfig config) {
_config = config;
if (config.enableUpload) {
AutoTrackQueue.instance.start();
} else {
AutoTrackQueue.instance.stop();
}
}
void updateUserId(String userId) {
_config.userId = userId;
}
void updatePageConfigs(List<AutoTrackPageConfig> pageConfigs) {
_config.pageConfigs = pageConfigs;
}
void updateIgnoreElementKeys(List<Key> ignoreElementKeys) {
_config.ignoreElementKeys = ignoreElementKeys;
}
void updateIgnoreElementStringKeys(List<String> ignoreElementStringKeys) {
_config.ignoreElementStringKeys = ignoreElementStringKeys;
}
void enablePageView(bool enable) {
_config.enablePageView = enable;
}
void enablePageLeave(bool enable) {
_config.enablePageLeave = enable;
}
void enableClick(bool enable) {
_config.enableClick = enable;
}
void enableAutoTrack(bool enable) {
_autoTrackEnable = enable;
}
void enableUpload(bool enable) {
_config.enableUpload = enable;
if (enable) {
AutoTrackQueue.instance.start();
} else {
AutoTrackQueue.instance.stop();
}
}
List<AutoTrackPageConfig> get pageConfigs => _config.pageConfigs;
bool get useCustomRoute => _config.useCustomRoute;
AutoTrackPageConfig getPageConfig(Widget pageWidget) {
return _config.pageConfigs.firstWhere(
(pageConfig) => pageConfig.isPageWidget!(pageWidget),
orElse: () => AutoTrackPageConfig()
);
}
Set<Key> getIgnoreElementKeySet() => _config.getIgnoreElementKeySet();
Set<String> getIgnoreElementStringKeySet() => _config.getIgnoreElementStringKeySet();
bool isIgnoreElement(Key? key) {
if (key == null) {
return false;
}
if (getIgnoreElementKeySet().contains(key)) {
return true;
}
if (getIgnoreElementStringKeySet().contains(key.toString())) {
return true;
}
return false;
}
bool get pageViewEnabled => _config.enablePageView;
bool get pageLeaveEnable => _config.enablePageLeave;
bool get clickEnable => _config.enableClick;
}
... ...
import 'dart:async';
import 'package:auto_track/auto_track/config/manager.dart';
import 'package:auto_track/auto_track/utils/track_model.dart';
import 'package:dio/dio.dart';
import '../log/logger.dart';
class AutoTrackQueue {
static final AutoTrackQueue instance = AutoTrackQueue._();
AutoTrackQueue._();
Timer? _timer;
final List<TrackModel> _queue = [];
final dio = Dio();
void appendQueue(TrackModel model) {
if (_timer == null) return;
_queue.add(model);
}
void start() {
if (_timer != null) return;
_timer = Timer.periodic(const Duration(seconds: 10), (timer) {
flush();
});
}
void stop() {
_timer?.cancel();
_timer = null;
}
void flush() {
if (_queue.isEmpty) return;
final uploadList = List.from(_queue);
_queue.clear();
final config = AutoTrackConfigManager.instance.config;
final host = config.host;
if (host != null) {
final t = DateTime.now().millisecond;
dio.post(host, data: {
'app_key': config.appKey ?? '',
'signature': config.signature!(t),
't': t,
'user_id': config.userId ?? '',
'track_id': config.trackId ?? '',
'data_list': uploadList.map((e) => e.toMap()).toList(),
}).onError((error, stackTrace) {
AutoTrackLogger.getInstance().error(error!);
return Future.value(Response(statusCode: 500, requestOptions: RequestOptions(path: host)));
});
}
}
}
... ...
import 'package:flutter/foundation.dart';
import 'click/pointer_event_listener.dart';
import 'config/config.dart';
import 'config/manager.dart';
import 'log/logger.dart';
class AutoTrack {
static final AutoTrack _instance = AutoTrack._();
AutoTrack._();
factory AutoTrack({ AutoTrackConfig? config }) {
_instance.config(config);
return _instance;
}
void updateUserId(String id) {
AutoTrackConfigManager.instance.updateUserId(id);
}
AutoTrack config(AutoTrackConfig? config) {
if (config != null) {
AutoTrackConfigManager.instance.updateConfig(config);
}
return _instance;
}
AutoTrack pageConfigs(List<AutoTrackPageConfig>? pageConfigs) {
if (pageConfigs != null) {
AutoTrackConfigManager.instance.updatePageConfigs(pageConfigs);
}
return _instance;
}
AutoTrack ignoreElementKeys(List<Key>? ignoreElementKeys) {
if (ignoreElementKeys != null) {
AutoTrackConfigManager.instance.updateIgnoreElementKeys(ignoreElementKeys);
}
return _instance;
}
AutoTrack ignoreElementStringKeys(List<String>? ignoreElementStringKeys) {
if (ignoreElementStringKeys != null) {
AutoTrackConfigManager.instance.updateIgnoreElementStringKeys(ignoreElementStringKeys);
}
return _instance;
}
AutoTrack enablePageView() {
AutoTrackConfigManager.instance.enablePageView(true);
return _instance;
}
AutoTrack disablePageView() {
AutoTrackConfigManager.instance.enablePageView(false);
return _instance;
}
AutoTrack enablePageLeave() {
AutoTrackConfigManager.instance.enablePageLeave(true);
return _instance;
}
AutoTrack disablePageLeave() {
AutoTrackConfigManager.instance.enablePageLeave(false);
return _instance;
}
AutoTrack enableUpload() {
AutoTrackConfigManager.instance.enableUpload(true);
return _instance;
}
AutoTrack disableUpload() {
AutoTrackConfigManager.instance.enableUpload(false);
return _instance;
}
AutoTrack enableClick() {
AutoTrackConfigManager.instance.enableClick(true);
return _instance;
}
AutoTrack disableClick() {
AutoTrackConfigManager.instance.enableClick(false);
return _instance;
}
AutoTrack enable() {
AutoTrackConfigManager.instance.enableAutoTrack(true);
PointerEventListener.instance.start();
return _instance;
}
AutoTrack disable() {
AutoTrackConfigManager.instance.enableAutoTrack(false);
PointerEventListener.instance.stop();
return _instance;
}
AutoTrack enableLog() {
final logger = AutoTrackLogger.getInstance();
if (!logger.hasHandler) {
logger.setHandler((level, message) {
if (kDebugMode) {
print('AutoTrack [$level] - $message');
}
});
}
return _instance;
}
}
... ...
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
typedef AutoTrackLoggerHandler = void Function(AutoTrackLoggerLevel level, String message);
class AutoTrackLogger {
static final AutoTrackLogger _instance = AutoTrackLogger();
static AutoTrackLogger getInstance() => _instance;
List<_LoggerData> _data = [];
AutoTrackLoggerHandler? _handler;
bool _isPrinting = false;
bool get hasHandler => _handler != null;
void info(String message) {
_print(AutoTrackLoggerLevel.Info, message);
}
void debug(String message) {
_print(AutoTrackLoggerLevel.Debug, message);
}
void error(Object e) {
String message = Error.safeToString(e);
if (e is FlutterError) {
message = e.message;
} else if (e is Error) {
message = e.stackTrace.toString();
} else if (e is DioException) {
message = (e).message ?? 'dio exception with unknown message';
}
_print(AutoTrackLoggerLevel.Error, message);
}
void setHandler(AutoTrackLoggerHandler handler) {
_handler = handler;
}
void _print(AutoTrackLoggerLevel level, String message) {
if (_handler == null) {
return;
}
_data.add(_LoggerData(level, message));
if (_isPrinting) {
return;
}
_isPrinting = true;
Future.delayed(const Duration(milliseconds: 300)).then((_) {
List<_LoggerData> data = _data;
_data = [];
if (_handler != null) {
for (var log in data) {
_handler!(log.level, log.message);
}
}
_isPrinting = false;
});
}
}
enum AutoTrackLoggerLevel {
Info,
Debug,
Error,
}
class _LoggerData {
const _LoggerData(this.level, this.message);
final AutoTrackLoggerLevel level;
final String message;
}
... ...
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../config/config.dart';
import '../config/manager.dart';
import '../log/logger.dart';
import '../utils/element_util.dart';
import 'page_stack.dart';
class AutoTrackNavigationObserver extends NavigatorObserver {
static List<NavigatorObserver> wrap(List<NavigatorObserver>? navigatorObservers) {
if (navigatorObservers == null) {
return [AutoTrackNavigationObserver()];
}
bool found = false;
List<NavigatorObserver> removeList = [];
for (NavigatorObserver observer in navigatorObservers) {
if (observer is AutoTrackNavigationObserver) {
if (found) {
removeList.add(observer);
}
found = true;
}
}
for (NavigatorObserver observer in removeList) {
navigatorObservers.remove(observer);
}
if (!found) {
navigatorObservers.insert(0, AutoTrackNavigationObserver());
}
return navigatorObservers;
}
static List<NavigatorObserver> defaultNavigatorObservers() => [AutoTrackNavigationObserver()];
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
// print('didPop -> $route, previousRoute -> $previousRoute');
try {
PageStack.instance.pop(route, previousRoute);
} catch (e) {
AutoTrackLogger.getInstance().error(e);
}
}
@override
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
// print('didPush -> $route, previousRoute -> $previousRoute');
try {
_findElement(route, (element) {
PageStack.instance.push(route, element, previousRoute);
});
} catch (e) {
AutoTrackLogger.getInstance().error(e);
}
}
@override
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
// print('didRemove -> $route, previousRoute -> $previousRoute');
try {
PageStack.instance.remove(route, previousRoute);
} catch (e) {
AutoTrackLogger.getInstance().error(e);
}
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
// print('didReplace -> $newRoute, oldRoute -> $oldRoute');
try {
if (newRoute != null) {
_findElement(newRoute, (element) {
PageStack.instance.replace(newRoute, element, oldRoute);
});
}
} catch (e) {
AutoTrackLogger.getInstance().error(e);
}
}
void _findElement(Route route, Function(Element) callback) {
SchedulerBinding.instance?.addPostFrameCallback((_) {
if (route is ModalRoute) {
ModalRoute pageRoute = route;
ElementUtil.walk(pageRoute.subtreeContext, (element, parent) {
if (parent != null && parent.widget is Semantics) {
callback(element);
return false;
}
return true;
});
} else if (AutoTrackConfigManager.instance.useCustomRoute) {
List<AutoTrackPageConfig> pageConfigs = AutoTrackConfigManager.instance.pageConfigs;
if (pageConfigs.isEmpty) {
return;
}
Element? lastPageElement;
ElementUtil.walk(route.navigator?.context, (element, parent) {
if (pageConfigs.last.isPageWidget!(element.widget)) {
lastPageElement = element;
return false;
}
return true;
});
if (lastPageElement != null) {
callback(lastPageElement!);
}
}
});
}
}
... ...
import 'package:flutter/material.dart';
import '../config/config.dart';
import '../config/manager.dart';
import '../utils/element_util.dart';
class PageInfo {
PageInfo._(this.timer);
factory PageInfo.fromElement(Element element, Route route) {
AutoTrackPageConfig pageConfig = AutoTrackConfigManager.instance.getPageConfig(element.widget);
PageInfo pageInfo = PageInfo._(PageTimer());
pageInfo._pageKey = element.widget.runtimeType.toString();
pageInfo._pagePath = pageConfig.pagePath ?? route.settings.name ?? '';
pageInfo._pageManualKey = pageConfig.pageID ?? '';
pageInfo._pageTitle = pageConfig.pageTitle ?? pageInfo._findTitle(element) ?? '';
pageInfo.ignore = pageConfig.ignore;
return pageInfo;
}
final PageTimer timer;
bool isBack = false;
bool ignore = false;
String _pageKey = '';
String get pageKey => _pageKey;
String _pageTitle = '';
String get pageTitle => _pageTitle;
String _pageManualKey = '';
String get pageManualKey => _pageManualKey;
String _pagePath = '';
String get pagePath => _pagePath;
String? _findTitle(Element element) {
String? title;
ElementUtil.walkElement(element, (child, _) {
if (child.widget is NavigationToolbar) {
NavigationToolbar toolBar = child.widget as NavigationToolbar;
if (toolBar.middle == null) {
return false;
}
if (toolBar.middle is Text) {
title = (toolBar.middle as Text).data;
return false;
}
int layoutIndex = 0;
if (toolBar.leading != null) {
layoutIndex += 1;
}
title = _findTextsInMiddle(child, layoutIndex);
return false;
}
return true;
});
return title;
}
String? _findTextsInMiddle(Element element, int layoutIndex) {
String? text;
int index = 0;
ElementUtil.walkElement(element, ((child, _) {
if (child.widget is LayoutId) {
if (index == layoutIndex) {
text = ElementUtil.findTexts(child).join('');
return false;
}
index += 1;
}
return true;
}));
return text;
}
@override
String toString() => '{ pageKey: $pageKey, pageTitle: $pageTitle, pageManualKey: $pageManualKey, pagePath: $pagePath, isBack: $isBack }';
}
enum PageTimerState {
Init,
Start,
Pause,
Resume,
End,
}
class PageTimer {
PageTimer();
PageTimerState _state = PageTimerState.Init;
PageTimerState get state => _state;
int _lastTimeStamp = 0;
Duration _duration = const Duration();
Duration get duration => _duration;
int _computeMilliseconds() {
return DateTime.now().millisecondsSinceEpoch - _lastTimeStamp;
}
start() {
if (_state != PageTimerState.Init && _state != PageTimerState.End) {
return;
}
_state = PageTimerState.Start;
_lastTimeStamp = DateTime.now().millisecondsSinceEpoch;
_duration = const Duration();
}
pause() {
if (_state != PageTimerState.Start && _state != PageTimerState.Resume) {
return;
}
_state = PageTimerState.Pause;
_duration = Duration(milliseconds: _duration.inMilliseconds + _computeMilliseconds());
}
resume() {
if (_state != PageTimerState.Pause) {
return;
}
_state = PageTimerState.Resume;
_lastTimeStamp = DateTime.now().millisecondsSinceEpoch;
}
end() {
if (_state == PageTimerState.Pause) {
_state = PageTimerState.End;
return;
}
if (_state == PageTimerState.Start || _state == PageTimerState.Resume) {
_state = PageTimerState.End;
_duration = Duration(milliseconds: _duration.inMilliseconds + _computeMilliseconds());
}
}
}
... ...
import 'dart:collection';
import 'dart:core';
import 'package:flutter/widgets.dart';
import '../track/track.dart';
import 'page_info.dart';
class PageStack with WidgetsBindingObserver {
static final PageStack instance = PageStack._();
PageStack._() {
WidgetsBinding.instance?.addObserver(this);
}
final LinkedList<Page> _stack = LinkedList<Page>();
final _PageTask _task = _PageTask();
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
for (var page in _stack) {
page.pageInfo.timer.resume();
}
} else if (state == AppLifecycleState.paused) {
for (var page in _stack) {
page.pageInfo.timer.pause();
}
}
}
push(Route route, Element element, Route? previousRoute) {
Page page = Page(route, element);
_stack.add(page);
_task.addPush(page, page.previous);
}
pop(Route route, Route? previousRoute) {
if (_stack.isEmpty) {
return;
}
Page? page = _findPage(route);
if (page != null) {
_task.addPop(page, page.previous);
}
_removeAllAfter(page);
}
remove(Route route, Route? previousRoute) {
if (_stack.isEmpty) {
return;
}
Page? page = _findPage(route);
if (page != null) {
_stack.remove(page);
}
}
replace(Route newRoute, Element newElement, Route? oldRoute) {
Page newPage = Page(newRoute, newElement);
Page? oldPage;
if (oldRoute != null) {
oldPage = _findPage(oldRoute);
_removeAllAfter(oldPage);
}
_stack.add(newPage);
_task.addReplace(newPage, oldPage);
}
Page? _findPage(Route route) {
if (_stack.isEmpty) {
return null;
}
Page? page = _stack.last;
while (page != null) {
if (page.route == route) {
return page;
}
page = page.previous;
}
return null;
}
_removeAllAfter(Page? page) {
while (page != null) {
_stack.remove(page);
page = page.next;
}
}
Page? getCurrentPage() {
if (_stack.isEmpty) {
return null;
}
return _stack.last;
}
}
final class Page extends LinkedListEntry<Page> {
Page._({
required this.pageInfo,
required this.route,
required this.element,
});
factory Page(Route route, Element element) {
return Page._(
pageInfo: PageInfo.fromElement(element, route),
route: route,
element: element,
);
}
final PageInfo pageInfo;
final Route route;
final Element element;
@override
String toString() => 'pageInfo: $pageInfo, route: $route';
}
class _PageTask {
final List<_PageTaskData> _list = [];
bool _taskRunning = false;
addPush(Page page, Page? prevPage) {
_PageTaskData taskData = _PageTaskData(_PageTaskType.Push, page);
taskData.prevPage = prevPage;
_list.add(taskData);
_triggerTask();
}
addPop(Page page, Page? prevPage) {
_PageTaskData taskData = _PageTaskData(_PageTaskType.Pop, page);
taskData.prevPage = prevPage;
_list.add(taskData);
_triggerTask();
}
addReplace(Page page, Page? prevPage) {
_PageTaskData taskData = _PageTaskData(_PageTaskType.Replace, page);
taskData.prevPage = prevPage;
_list.add(taskData);
_triggerTask();
}
_triggerTask() {
if (_taskRunning) {
return;
}
_taskRunning = true;
Future.delayed(const Duration(milliseconds: 30)).then((_) => _executeTask());
}
_executeTask() {
if (_list.isEmpty) {
_taskRunning = false;
return;
}
List list = _list.sublist(0);
Page? enterPage, leavePage;
_list.clear();
for (_PageTaskData taskData in list as List<_PageTaskData>) {
if (taskData.type == _PageTaskType.Push) {
leavePage ??= taskData.prevPage;
enterPage = taskData.page;
} else if (taskData.type == _PageTaskType.Pop) {
leavePage ??= taskData.page;
if (enterPage == null || enterPage == taskData.page) {
enterPage = taskData.prevPage;
enterPage?.pageInfo.isBack = true;
}
} else if (taskData.type == _PageTaskType.Replace) {
leavePage ??= taskData.prevPage;
if (enterPage == null || enterPage == taskData.prevPage) {
enterPage = taskData.page;
}
}
}
if (enterPage != leavePage) {
if (enterPage != null && !enterPage.pageInfo.ignore) {
enterPage.pageInfo.timer.start();
Track.instance.pageView(enterPage.pageInfo);
}
if (leavePage != null && !leavePage.pageInfo.ignore) {
leavePage.pageInfo.timer.end();
Track.instance.pageLeave(leavePage.pageInfo);
}
}
_taskRunning = false;
}
}
class _PageTaskData {
_PageTaskData(this.type, this.page);
final _PageTaskType type;
final Page page;
Page? prevPage;
}
enum _PageTaskType {
Push,
Pop,
Replace,
}
... ...