first commit
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
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
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
- platform: android
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
- platform: ios
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
- platform: linux
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
- platform: macos
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
- platform: web
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
|
||||
# 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,16 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter (lib\\main.dart)",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "lib\\main.dart"
|
||||
},
|
||||
{
|
||||
"name": "Flutter (lib\\main.dart)",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "lib\\main.dart"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# shooter_app
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://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.
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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
|
||||
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,42 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.shooter_app"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
|
||||
ndkVersion = "27.0.12077973" // ✅ Keep only this line
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.shooter_app"
|
||||
minSdk = 26 // ✅ From earlier fix
|
||||
targetSdk = 33
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# TensorFlow Lite
|
||||
-keep class org.tensorflow.** { *; }
|
||||
-dontwarn org.tensorflow.**
|
||||
|
||||
# TFLite GPU Delegate
|
||||
-keep class org.tensorflow.lite.gpu.** { *; }
|
||||
-dontwarn org.tensorflow.lite.gpu.**
|
||||
|
||||
# TFLite Support Libraries
|
||||
-keep class org.tensorflow.lite.support.** { *; }
|
||||
-dontwarn org.tensorflow.lite.support.**
|
||||
|
||||
# Prevent R8 stripping
|
||||
-keep class com.google.** { *; }
|
||||
-dontwarn com.google.**
|
||||
|
||||
# Keep your YOLO model loader class if needed
|
||||
-keep class your.package.name.YOLODetect { *; }
|
||||
@@ -0,0 +1,7 @@
|
||||
<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>
|
||||
@@ -0,0 +1,61 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
|
||||
<application
|
||||
android:label="SDK Muta"
|
||||
android:name="${applicationName}"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:preserveLegacyExternalStorage="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
uses-cleartext-traffic="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
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>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.shooter_app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.test1
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
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>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is 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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
</network-security-config>
|
||||
@@ -0,0 +1,7 @@
|
||||
<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>
|
||||
@@ -0,0 +1,21 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath = run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.7.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 136 KiB |
@@ -0,0 +1,176 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 832 832" enable-background="new 0 0 832 832" xml:space="preserve">
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M217.385040,640.634888
|
||||
C167.371597,596.268005 135.726517,541.600220 122.004700,476.575897
|
||||
C120.362053,468.791779 119.680847,460.809143 118.293297,452.964630
|
||||
C117.746704,449.874481 119.037468,448.979736 121.666008,448.977570
|
||||
C143.829102,448.959259 165.992279,448.989624 188.155212,448.926971
|
||||
C191.644348,448.917114 192.750092,450.959106 193.207932,453.864044
|
||||
C197.968811,484.071350 208.669083,512.087463 225.487534,537.543823
|
||||
C260.541687,590.601685 309.220367,623.665039 371.859955,635.771423
|
||||
C373.328674,636.055237 374.787415,636.595581 376.258148,636.623108
|
||||
C379.651733,636.686707 380.333496,638.606079 380.326355,641.533630
|
||||
C380.273529,663.196899 380.204651,684.861206 380.380432,706.523193
|
||||
C380.419128,711.290527 378.009888,711.674622 374.391205,711.179443
|
||||
C327.438385,704.754150 284.132416,688.766541 244.939484,661.929932
|
||||
C235.439224,655.424683 226.393066,648.348389 217.385040,640.634888
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M380.315796,138.009048
|
||||
C380.315613,156.666809 380.196991,174.828186 380.405334,192.985779
|
||||
C380.451416,197.002670 378.977386,198.602676 375.229797,199.226151
|
||||
C335.021332,205.915405 299.223267,222.398804 268.394043,248.973557
|
||||
C227.854797,283.918365 202.418610,327.743378 193.373978,380.728607
|
||||
C192.710037,384.618042 191.495758,386.456116 187.107422,386.412689
|
||||
C165.950577,386.203247 144.790512,386.328552 123.631622,386.305786
|
||||
C121.848953,386.303894 119.952629,386.654358 118.105957,385.126953
|
||||
C119.237549,371.494934 121.871552,358.007690 125.292259,344.674927
|
||||
C139.522827,289.208862 167.430298,241.700974 209.076218,202.450302
|
||||
C250.204315,163.687637 298.698975,138.912613 354.109436,127.776833
|
||||
C361.426575,126.306313 368.881775,125.535194 376.239319,124.247482
|
||||
C379.124939,123.742447 380.322876,124.656189 380.293793,127.515274
|
||||
C380.259888,130.846863 380.304565,134.179245 380.315796,138.009048
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M585.821289,656.872925
|
||||
C548.639404,683.801758 507.704041,700.996277 462.908630,709.058960
|
||||
C458.167999,709.912231 453.382751,710.520142 448.614227,711.214783
|
||||
C445.480774,711.671204 443.593689,710.956299 443.612610,707.120056
|
||||
C443.721161,685.124817 443.705811,663.128479 443.617004,641.133057
|
||||
C443.604034,637.914429 444.969086,637.012329 447.979034,636.501038
|
||||
C484.623535,630.276062 517.888550,616.048584 547.155457,593.115906
|
||||
C592.754639,557.385681 620.489868,510.765808 630.524475,453.708771
|
||||
C631.162720,450.079681 632.684509,448.942596 636.058655,448.952362
|
||||
C657.387817,449.014221 678.717224,448.964172 700.046387,449.024506
|
||||
C705.170715,449.039001 705.935547,450.048218 705.135437,455.553528
|
||||
C701.971558,477.322327 697.174194,498.695526 689.552612,519.381042
|
||||
C673.613708,562.640137 648.921448,600.160706 615.603210,631.988220
|
||||
C606.323730,640.852417 596.531738,649.141724 585.821289,656.872925
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M543.106384,152.777008
|
||||
C623.864807,194.497498 675.672546,259.028137 698.780090,346.557220
|
||||
C701.664368,357.482635 703.416565,368.647156 705.113098,379.808838
|
||||
C705.972534,385.462982 705.255066,386.301849 699.637939,386.307220
|
||||
C678.483643,386.327515 657.328979,386.246948 636.175415,386.367004
|
||||
C632.515503,386.387756 631.252441,385.312256 630.465210,381.477020
|
||||
C611.099548,287.129395 554.752502,227.317871 461.961639,201.788712
|
||||
C457.494659,200.559738 452.832184,200.043533 448.263397,199.181366
|
||||
C445.693939,198.696503 443.598297,198.054764 443.615936,194.581528
|
||||
C443.730072,172.095444 443.680511,149.608551 443.709625,127.121956
|
||||
C443.710663,126.324203 443.973877,125.526787 444.240295,124.019760
|
||||
C478.926300,127.755081 511.672424,137.272491 543.106384,152.777008
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M701.000000,400.778809
|
||||
C723.485535,400.781036 745.472717,400.925323 767.455505,400.679413
|
||||
C772.544128,400.622498 774.256897,402.306488 774.059509,407.255646
|
||||
C773.780884,414.239410 773.761230,421.255371 774.102234,428.234528
|
||||
C774.338745,433.077240 772.593079,434.455170 767.814636,434.443237
|
||||
C714.183533,434.309387 660.552002,434.383759 606.920532,434.387543
|
||||
C570.777588,434.390106 534.634644,434.374756 498.491730,434.354248
|
||||
C491.790222,434.350464 491.779114,434.321625 491.763947,427.753906
|
||||
C491.748566,421.091644 491.746674,414.429291 491.760223,407.767029
|
||||
C491.774384,400.795593 491.785461,400.786346 498.632294,400.783600
|
||||
C544.268982,400.765198 589.905640,400.744354 635.542358,400.738556
|
||||
C657.194885,400.735809 678.847473,400.764465 701.000000,400.778809
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M394.777466,672.000000
|
||||
C394.776855,616.021362 394.775238,560.542725 394.776672,505.064087
|
||||
C394.776855,497.793488 394.784149,497.790741 401.856598,497.780914
|
||||
C407.687683,497.772827 413.518890,497.750397 419.349854,497.773285
|
||||
C429.261841,497.812164 428.328613,496.935669 428.334534,506.517517
|
||||
C428.361725,550.500610 428.347656,594.483643 428.344940,638.466736
|
||||
C428.342194,683.449402 428.338684,728.432129 428.326843,773.414795
|
||||
C428.325104,780.044495 428.302979,780.049255 421.686462,780.049927
|
||||
C414.689178,780.050598 407.684692,779.846741 400.697113,780.104919
|
||||
C396.084534,780.275330 394.677429,778.487915 394.696930,773.960876
|
||||
C394.842468,740.141113 394.776855,706.320435 394.777466,672.000000
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M394.780304,122.000160
|
||||
C394.780304,101.836891 394.773376,82.173622 394.786804,62.510368
|
||||
C394.791473,55.707291 394.814545,55.700260 401.803131,55.690872
|
||||
C408.635284,55.681694 415.483154,55.971924 422.294312,55.593998
|
||||
C427.205994,55.321468 428.454407,57.120087 428.440277,61.853291
|
||||
C428.289581,112.344139 428.346466,162.835602 428.344055,213.326889
|
||||
C428.342163,252.486801 428.341461,291.646729 428.329010,330.806641
|
||||
C428.326935,337.320618 428.300354,337.328125 421.669769,337.337067
|
||||
C414.171082,337.347198 406.670898,337.253967 399.174469,337.384735
|
||||
C395.948486,337.440979 394.707306,336.154388 394.750397,332.963135
|
||||
C394.849396,325.632660 394.734802,318.299561 394.736145,310.967529
|
||||
C394.747528,248.145081 394.765411,185.322617 394.780304,122.000160
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M109.999901,400.740662
|
||||
C181.795898,400.739990 253.091904,400.739319 324.387909,400.742310
|
||||
C331.320068,400.742584 331.332153,400.752899 331.343597,407.525208
|
||||
C331.355713,414.688080 331.146423,421.858337 331.413605,429.011169
|
||||
C331.569092,433.173920 330.074554,434.435059 325.972107,434.415558
|
||||
C298.820374,434.286438 271.667572,434.380585 244.515137,434.382446
|
||||
C182.214432,434.386749 119.913734,434.382141 57.613033,434.374512
|
||||
C49.791779,434.373535 49.788113,434.354919 49.774273,426.342194
|
||||
C49.762764,419.679016 49.974655,413.006439 49.680656,406.355988
|
||||
C49.482746,401.879120 51.266327,400.643585 55.529621,400.684052
|
||||
C73.518280,400.854736 91.509567,400.747375 109.999901,400.740662
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M355.006714,289.079895
|
||||
C314.542847,307.902771 288.356110,338.559448 276.091980,381.015442
|
||||
C274.888428,385.182037 273.095215,386.842010 268.864868,386.370209
|
||||
C266.231445,386.076508 263.534729,386.212646 260.874268,386.331818
|
||||
C257.080597,386.501678 256.319031,384.836212 257.247894,381.392822
|
||||
C273.855225,319.828796 312.712891,280.391144 374.326569,263.599457
|
||||
C380.012299,262.049927 380.234985,262.392334 380.309387,268.251495
|
||||
C380.459137,280.039276 380.432434,279.958344 369.353027,283.609314
|
||||
C364.617004,285.169952 360.018280,287.147278 355.006714,289.079895
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M530.844788,522.827209
|
||||
C507.250580,548.878845 478.829315,565.771240 443.768250,573.063721
|
||||
C443.768250,567.065430 443.631561,561.955872 443.824524,556.858826
|
||||
C443.924438,554.220093 446.501617,554.235901 448.316559,553.728210
|
||||
C477.592224,545.538330 501.883942,529.704346 521.112183,506.177460
|
||||
C533.607300,490.888947 542.482056,473.665497 547.554810,454.587372
|
||||
C548.635681,450.522400 550.318359,448.476959 554.665283,448.970612
|
||||
C558.553711,449.412201 562.612244,448.140808 566.989685,450.238983
|
||||
C561.468018,477.622711 549.265564,501.550690 530.844788,522.827209
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M321.429260,525.557129
|
||||
C337.614563,538.831970 355.341278,548.432312 375.303497,553.634033
|
||||
C378.937042,554.580811 380.806030,556.076660 380.342194,560.065674
|
||||
C379.868652,564.138123 380.930969,568.329590 379.559998,572.810181
|
||||
C371.784576,572.006714 364.506714,569.613953 357.468445,566.874451
|
||||
C305.258423,546.553284 271.903625,509.263031 257.610077,455.037445
|
||||
C256.168457,449.568359 256.639526,449.089569 262.330505,448.970734
|
||||
C273.943176,448.728271 273.961517,448.722382 277.490479,459.732086
|
||||
C285.826874,485.740204 300.358704,507.617676 321.429260,525.557129
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M478.263367,293.780090
|
||||
C468.371307,288.450684 458.355194,284.368042 447.821228,281.748016
|
||||
C444.510101,280.924408 443.492584,279.381256 443.613556,276.231689
|
||||
C443.777985,271.950439 443.654541,267.658112 443.654541,262.501068
|
||||
C470.685822,267.855408 493.812103,279.232391 514.055847,296.378204
|
||||
C541.776123,319.856445 559.376709,349.468872 567.201355,385.734680
|
||||
C561.265930,386.723236 555.849548,386.557587 550.500671,385.988373
|
||||
C548.103027,385.733246 548.268616,382.943359 547.763672,381.134033
|
||||
C540.945984,356.703400 528.606750,335.492645 510.620270,317.629517
|
||||
C501.116150,308.190613 490.407288,300.304352 478.263367,293.780090
|
||||
z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 219 KiB |
@@ -0,0 +1,305 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1200.000000pt" height="1200.000000pt" viewBox="0 0 1200.000000 1200.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,1200.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M5690 11129 c-1618 -101 -3089 -960 -3983 -2324 -1118 -1707 -1114
|
||||
-3907 10 -5615 772 -1173 1991 -1982 3363 -2234 1190 -218 2415 -9 3461 590
|
||||
1414 809 2363 2232 2559 3838 63 520 48 1033 -46 1546 -273 1484 -1193 2779
|
||||
-2513 3534 -860 493 -1864 727 -2851 665z m810 -44 c1443 -151 2716 -868 3584
|
||||
-2020 199 -263 344 -500 501 -820 288 -585 439 -1126 507 -1810 19 -193 16
|
||||
-720 -6 -920 -71 -662 -237 -1232 -526 -1805 -440 -870 -1112 -1589 -1960
|
||||
-2097 -163 -97 -547 -286 -725 -356 -1125 -441 -2339 -473 -3480 -92 -1534
|
||||
512 -2743 1728 -3243 3265 -115 354 -185 686 -228 1085 -25 224 -25 756 0 980
|
||||
54 500 159 934 333 1380 70 177 258 562 356 725 508 848 1227 1520 2097 1960
|
||||
636 321 1312 502 2030 544 136 8 621 -4 760 -19z"/>
|
||||
<path d="M5988 10916 c-21 -18 -38 -38 -38 -45 0 -14 27 -15 35 -1 16 27 25 3
|
||||
25 -70 0 -79 0 -80 25 -80 l25 0 0 115 c0 132 -6 139 -72 81z"/>
|
||||
<path d="M5675 10549 c-1290 -97 -2468 -731 -3271 -1760 -751 -962 -1082
|
||||
-2211 -908 -3429 65 -460 207 -921 414 -1345 227 -465 503 -849 879 -1226 459
|
||||
-458 962 -790 1551 -1022 399 -157 774 -248 1240 -299 151 -16 699 -16 850 0
|
||||
585 64 1074 202 1565 442 467 228 845 499 1221 874 196 196 295 309 439 502
|
||||
942 1255 1166 2923 589 4380 -342 866 -936 1605 -1704 2119 -672 451 -1413
|
||||
705 -2225 765 -160 11 -478 11 -640 -1z m646 -29 c609 -47 1173 -201 1699
|
||||
-465 1131 -567 1977 -1579 2331 -2790 317 -1083 210 -2266 -296 -3275 -541
|
||||
-1079 -1496 -1907 -2636 -2283 -914 -303 -1914 -303 -2828 0 -586 193 -1135
|
||||
512 -1608 934 -577 515 -1031 1209 -1276 1950 -302 913 -303 1914 -1 2826 228
|
||||
691 633 1334 1157 1839 766 739 1753 1183 2807 1263 157 12 498 12 651 1z"/>
|
||||
<path d="M5955 10355 c-14 -13 -25 -29 -25 -35 0 -16 47 -12 54 6 8 21 50 14
|
||||
54 -10 3 -11 -12 -34 -36 -60 -95 -99 -94 -116 3 -116 68 0 75 2 75 20 0 17
|
||||
-7 20 -40 20 -22 0 -40 3 -40 6 0 4 18 26 40 50 31 33 40 52 40 78 0 64 -78
|
||||
89 -125 41z"/>
|
||||
<path d="M5705 9980 c-566 -50 -1040 -186 -1527 -437 -1077 -556 -1847 -1580
|
||||
-2082 -2769 -229 -1158 67 -2350 814 -3274 419 -519 986 -939 1600 -1188 1339
|
||||
-542 2857 -318 3985 586 745 597 1258 1454 1429 2387 48 266 60 408 60 720 0
|
||||
328 -13 475 -70 766 -285 1461 -1377 2651 -2814 3065 -178 51 -455 106 -650
|
||||
129 -158 19 -599 28 -745 15z m750 -45 c919 -112 1725 -509 2370 -1167 628
|
||||
-639 1000 -1417 1112 -2323 24 -197 24 -683 0 -880 -116 -933 -508 -1732
|
||||
-1169 -2380 -648 -636 -1452 -1016 -2363 -1117 -158 -17 -642 -17 -800 0 -543
|
||||
60 -1058 223 -1520 481 -681 381 -1254 976 -1608 1671 -594 1167 -567 2562 72
|
||||
3705 381 681 976 1254 1671 1608 425 216 929 364 1385 406 66 6 136 13 155 15
|
||||
84 10 573 -4 695 -19z"/>
|
||||
<path d="M5946 9798 c-9 -12 -16 -26 -16 -30 0 -14 33 -9 51 8 21 19 49 12 49
|
||||
-11 0 -8 -9 -20 -20 -27 -26 -17 -25 -31 2 -43 27 -12 35 -40 18 -60 -16 -19
|
||||
-46 -19 -54 0 -7 19 -46 20 -46 1 0 -20 48 -56 75 -56 12 0 34 9 49 21 33 26
|
||||
36 81 6 102 -18 13 -19 16 -5 27 20 17 19 53 -3 73 -28 25 -87 22 -106 -5z"/>
|
||||
<path d="M5805 9439 c-1522 -84 -2826 -1194 -3159 -2689 -401 -1802 704 -3605
|
||||
2499 -4075 468 -123 989 -142 1479 -54 1091 196 2053 947 2511 1960 695 1536
|
||||
187 3329 -1210 4276 -430 291 -915 478 -1433 553 -128 18 -440 42 -517 38 -16
|
||||
0 -93 -4 -170 -9z m570 -44 c635 -70 1212 -305 1735 -708 134 -104 471 -441
|
||||
576 -577 411 -531 644 -1111 715 -1780 7 -63 12 -209 12 -325 0 -300 -28 -537
|
||||
-98 -821 -306 -1244 -1314 -2228 -2570 -2509 -522 -117 -1078 -106 -1600 30
|
||||
-482 126 -932 358 -1322 682 -118 98 -345 325 -443 443 -324 391 -564 864
|
||||
-685 1353 -71 290 -98 515 -98 822 0 307 27 532 98 822 121 489 361 962 685
|
||||
1353 98 118 325 345 443 443 579 481 1254 744 2032 791 102 6 383 -4 520 -19z"/>
|
||||
<path d="M6038 9262 c-24 -5 -118 -140 -118 -170 0 -22 3 -23 48 -21 42 3 47
|
||||
1 50 -19 4 -29 42 -31 42 -2 0 11 7 20 15 20 8 0 15 9 15 20 0 11 -7 20 -15
|
||||
20 -11 0 -15 18 -17 78 -3 67 -5 77 -20 74z m-18 -112 c0 -36 -2 -40 -25 -40
|
||||
-14 0 -25 5 -25 11 0 11 38 69 46 69 2 0 4 -18 4 -40z"/>
|
||||
<path d="M5840 8869 c-1028 -50 -1973 -686 -2425 -1632 -189 -395 -277 -787
|
||||
-277 -1232 0 -445 88 -837 277 -1232 504 -1054 1588 -1697 2749 -1632 1035 58
|
||||
1946 660 2415 1598 278 555 363 1218 236 1847 -158 785 -665 1488 -1368 1899
|
||||
-487 284 -1023 412 -1607 384z m330 -29 c419 -28 750 -118 1122 -307 435 -219
|
||||
819 -564 1090 -978 93 -141 207 -363 271 -525 224 -568 253 -1219 81 -1812
|
||||
-308 -1061 -1219 -1859 -2311 -2022 -262 -40 -574 -40 -837 0 -1003 150 -1864
|
||||
840 -2230 1786 -254 657 -255 1387 -1 2042 397 1029 1363 1741 2462 1815 171
|
||||
12 193 12 353 1z"/>
|
||||
<path d="M5945 8674 c-3 -26 -9 -52 -11 -59 -7 -18 24 -27 46 -12 39 23 79
|
||||
-33 50 -69 -16 -18 -46 -18 -54 1 -7 18 -46 20 -46 3 0 -21 14 -36 43 -47 59
|
||||
-23 107 13 107 80 0 44 -25 69 -69 69 -26 0 -32 4 -29 18 2 12 15 18 46 20 34
|
||||
3 42 7 42 23 0 17 -7 19 -59 19 l-59 0 -7 -46z"/>
|
||||
<path d="M5825 8350 c-309 -26 -571 -98 -850 -235 -681 -332 -1162 -974 -1289
|
||||
-1720 -82 -479 -9 -975 209 -1420 232 -476 604 -848 1080 -1081 570 -278 1202
|
||||
-316 1806 -109 57 20 172 69 254 110 474 231 849 606 1081 1080 165 338 237
|
||||
652 237 1030 0 378 -72 692 -237 1030 -341 697 -1004 1183 -1766 1295 -152 22
|
||||
-390 31 -525 20z m504 -45 c996 -144 1792 -917 1965 -1909 98 -562 -25 -1164
|
||||
-336 -1643 -372 -573 -956 -948 -1633 -1049 -165 -24 -475 -24 -640 0 -1033
|
||||
154 -1827 948 -1981 1981 -24 165 -24 475 0 640 75 506 305 963 662 1321 388
|
||||
388 887 621 1444 674 124 11 386 4 519 -15z"/>
|
||||
<path d="M5959 8161 c-26 -26 -29 -36 -29 -94 0 -83 23 -117 79 -117 48 0 71
|
||||
24 71 73 0 51 -31 81 -76 74 -38 -6 -37 -7 -24 27 10 26 45 36 52 15 2 -6 15
|
||||
-9 28 -7 l25 3 -27 28 c-35 35 -62 34 -99 -2z m69 -103 c30 -30 -1 -94 -33
|
||||
-68 -15 13 -21 61 -8 73 10 11 28 8 41 -5z"/>
|
||||
<path d="M5739 7785 c-677 -110 -1226 -572 -1438 -1210 -74 -223 -85 -298 -85
|
||||
-570 0 -272 11 -347 85 -570 178 -534 600 -956 1134 -1134 223 -74 298 -85
|
||||
570 -85 272 0 347 11 570 85 534 178 956 600 1134 1134 74 223 85 298 85 570
|
||||
0 212 -2 252 -22 345 -158 730 -691 1264 -1417 1420 -130 28 -483 37 -616 15z
|
||||
m341 -169 c0 -7 -16 -43 -35 -80 -19 -37 -35 -78 -35 -91 0 -37 -11 -55 -32
|
||||
-55 -19 0 -20 4 -14 46 3 26 19 71 35 100 l29 54 -49 0 c-42 0 -49 3 -49 20 0
|
||||
18 7 20 75 20 57 0 75 -3 75 -14z m175 -381 c433 -91 792 -405 936 -820 50
|
||||
-145 64 -235 64 -410 0 -175 -14 -265 -64 -410 -125 -359 -422 -655 -781 -778
|
||||
-140 -48 -232 -62 -405 -62 -173 0 -265 14 -405 62 -360 123 -660 423 -783
|
||||
783 -48 140 -62 232 -62 405 0 173 14 265 62 405 123 360 423 660 783 783 66
|
||||
23 175 48 265 61 64 9 313 -2 390 -19z m-1675 -1124 c0 -5 -16 -35 -35 -67
|
||||
-20 -33 -37 -77 -41 -106 -6 -43 -9 -48 -31 -48 -24 0 -25 2 -19 38 4 20 19
|
||||
63 35 95 l28 57 -49 0 c-41 0 -48 3 -48 20 0 18 7 20 80 20 44 0 80 -4 80 -9z
|
||||
m3010 -6 c0 -8 -13 -35 -30 -61 -16 -26 -34 -71 -40 -100 -9 -44 -15 -54 -32
|
||||
-54 -18 0 -20 5 -15 38 4 32 24 84 55 145 2 4 -19 7 -47 7 -44 0 -51 3 -51 20
|
||||
0 18 7 20 80 20 64 0 80 -3 80 -15z m-1510 -1505 c0 -11 -11 -37 -24 -58 -14
|
||||
-20 -30 -59 -37 -87 -6 -27 -13 -56 -15 -62 -6 -22 -44 -15 -44 7 0 31 37 130
|
||||
56 151 16 18 15 19 -34 19 -49 0 -52 2 -52 25 0 24 2 25 75 25 69 0 75 -2 75
|
||||
-20z"/>
|
||||
<path d="M5801 7194 c-498 -84 -904 -493 -986 -994 -118 -732 449 -1400 1190
|
||||
-1400 664 0 1205 541 1205 1205 0 669 -546 1210 -1215 1204 -60 -1 -148 -7
|
||||
-194 -15z m259 -134 c24 -24 25 -38 5 -67 -13 -18 -13 -22 0 -33 9 -7 15 -29
|
||||
15 -51 0 -44 -27 -69 -75 -69 -48 0 -75 25 -75 69 0 22 6 44 15 51 14 12 14
|
||||
15 0 30 -34 38 0 90 60 90 22 0 43 -8 55 -20z m124 -376 c235 -61 437 -263
|
||||
502 -504 24 -89 24 -263 0 -350 -54 -197 -190 -362 -372 -454 -104 -52 -182
|
||||
-69 -309 -69 -127 0 -205 17 -309 70 -140 69 -250 180 -320 319 -52 104 -69
|
||||
182 -69 309 0 127 17 205 69 309 105 209 296 349 529 387 67 11 204 2 279 -17z
|
||||
m-1076 -581 c16 -22 15 -58 -2 -72 -13 -11 -12 -15 5 -32 26 -26 24 -65 -7
|
||||
-96 -21 -21 -32 -24 -62 -19 -65 10 -94 74 -53 115 17 17 18 21 5 32 -23 19
|
||||
-14 71 16 87 30 15 80 8 98 -15z m1910 0 c16 -22 15 -58 -2 -72 -13 -11 -12
|
||||
-15 5 -32 24 -24 24 -60 0 -90 -14 -18 -29 -24 -61 -24 -32 0 -47 6 -61 24
|
||||
-24 30 -24 66 0 90 17 17 18 21 5 32 -23 19 -14 71 16 87 30 15 80 8 98 -15z
|
||||
m-958 -953 c24 -24 25 -38 5 -67 -13 -18 -13 -22 0 -33 9 -7 15 -29 15 -51 0
|
||||
-44 -27 -69 -75 -69 -48 0 -75 25 -75 69 0 22 6 44 15 51 14 12 14 15 0 30
|
||||
-19 21 -19 40 1 68 20 29 85 31 114 2z"/>
|
||||
<path d="M5982 7018 c2 -15 10 -23 23 -23 13 0 21 8 23 23 3 18 -1 22 -23 22
|
||||
-22 0 -26 -4 -23 -22z"/>
|
||||
<path d="M5980 6935 c-18 -21 -3 -55 25 -55 15 0 26 7 30 20 7 23 -10 50 -30
|
||||
50 -7 0 -18 -7 -25 -15z"/>
|
||||
<path d="M5893 6650 c-236 -43 -428 -211 -509 -445 -24 -70 -28 -95 -28 -200
|
||||
0 -105 4 -130 28 -200 70 -204 212 -347 415 -419 77 -27 98 -30 206 -30 108 0
|
||||
129 3 206 30 203 72 345 215 415 419 24 70 28 95 28 200 0 105 -4 130 -28 200
|
||||
-70 203 -214 349 -412 417 -92 32 -231 44 -321 28z m195 -528 c89 -64 76 -200
|
||||
-24 -247 -130 -61 -259 96 -175 213 44 63 136 78 199 34z"/>
|
||||
<path d="M5026 6073 c-4 -9 -1 -23 7 -30 18 -18 51 5 41 30 -8 22 -40 22 -48
|
||||
0z"/>
|
||||
<path d="M5027 5984 c-15 -16 -7 -53 14 -59 27 -9 42 6 37 36 -3 24 -36 38
|
||||
-51 23z"/>
|
||||
<path d="M6933 6074 c-3 -8 1 -22 9 -30 19 -19 52 3 42 29 -8 21 -43 22 -51 1z"/>
|
||||
<path d="M6934 5976 c-11 -29 0 -51 26 -51 21 0 25 5 25 30 0 24 -5 31 -23 33
|
||||
-13 2 -24 -3 -28 -12z"/>
|
||||
<path d="M5982 5108 c2 -15 10 -23 23 -23 13 0 21 8 23 23 3 18 -1 22 -23 22
|
||||
-22 0 -26 -4 -23 -22z"/>
|
||||
<path d="M5980 5025 c-18 -21 -3 -55 25 -55 15 0 26 7 30 20 7 23 -10 50 -30
|
||||
50 -7 0 -18 -7 -25 -15z"/>
|
||||
<path d="M3924 6120 c-37 -15 -54 -50 -54 -112 0 -71 15 -104 52 -117 55 -19
|
||||
98 15 98 78 0 45 -33 75 -76 68 -38 -6 -37 -7 -24 27 11 29 26 33 49 12 9 -8
|
||||
24 -11 34 -7 18 7 18 7 0 28 -22 26 -51 34 -79 23z m44 -122 c17 -17 15 -53
|
||||
-3 -68 -31 -26 -60 13 -44 58 9 24 29 28 47 10z"/>
|
||||
<path d="M8044 6120 c-38 -15 -54 -50 -54 -117 0 -50 4 -67 23 -89 48 -55 127
|
||||
-25 127 48 0 52 -30 82 -75 75 -29 -5 -32 -3 -27 19 5 29 39 42 55 21 7 -9 19
|
||||
-12 30 -8 18 7 18 7 0 28 -22 26 -51 34 -79 23z m44 -122 c17 -17 15 -67 -3
|
||||
-74 -22 -9 -45 14 -45 46 0 40 23 53 48 28z"/>
|
||||
<path d="M5951 4034 c-16 -20 -21 -41 -21 -85 0 -69 13 -103 47 -118 34 -16
|
||||
47 -14 77 10 20 16 26 29 26 60 0 54 -30 83 -77 76 -28 -5 -34 -3 -29 9 3 9 6
|
||||
19 6 24 0 17 34 20 51 4 12 -10 23 -13 33 -7 14 8 14 11 0 31 -23 33 -86 31
|
||||
-113 -4z m78 -98 c7 -8 11 -27 9 -43 -2 -22 -8 -28 -28 -28 -20 0 -26 6 -28
|
||||
28 -6 53 20 76 47 43z"/>
|
||||
<path d="M3357 6113 c-3 -5 -8 -32 -12 -61 l-7 -52 49 2 48 3 0 -34 c0 -43
|
||||
-29 -65 -49 -38 -14 18 -56 24 -56 7 0 -24 41 -50 78 -50 51 0 77 28 77 82 0
|
||||
47 -34 82 -72 75 -17 -3 -23 0 -23 14 0 16 8 19 45 19 38 0 45 3 45 20 0 18
|
||||
-7 20 -59 20 -33 0 -62 -3 -64 -7z"/>
|
||||
<path d="M8541 6070 c-16 -73 -16 -72 34 -64 41 6 46 4 54 -17 18 -49 -20 -87
|
||||
-53 -54 -9 9 -25 15 -37 13 l-21 -3 22 -27 c46 -59 140 -22 140 54 0 39 -43
|
||||
82 -76 75 -18 -3 -24 0 -24 14 0 16 8 19 45 19 38 0 45 3 45 20 0 18 -7 20
|
||||
-59 20 l-59 0 -11 -50z"/>
|
||||
<path d="M5943 3468 c-6 -35 -9 -65 -6 -68 2 -2 17 1 34 7 36 14 56 8 64 -18
|
||||
16 -49 -23 -81 -56 -48 -20 20 -49 20 -49 -1 0 -5 9 -19 20 -30 47 -47 130 -6
|
||||
130 65 0 44 -29 75 -71 75 -24 0 -30 3 -25 16 3 9 6 18 6 20 0 2 18 4 40 4 33
|
||||
0 40 3 40 20 0 18 -7 20 -59 20 l-58 0 -10 -62z"/>
|
||||
<path d="M2831 6048 c-28 -40 -51 -82 -51 -95 0 -21 4 -23 50 -23 43 0 50 -3
|
||||
50 -20 0 -13 7 -20 20 -20 13 0 20 7 20 20 0 11 7 20 15 20 8 0 15 9 15 20 0
|
||||
11 -7 20 -15 20 -12 0 -15 15 -15 75 0 67 -2 75 -19 75 -13 0 -37 -25 -70 -72z
|
||||
m46 -75 c-3 -2 -17 -3 -31 -1 l-26 3 27 40 28 40 3 -39 c2 -21 1 -40 -1 -43z"/>
|
||||
<path d="M9110 6045 c-27 -41 -50 -83 -50 -95 0 -17 6 -20 50 -20 43 0 50 -3
|
||||
50 -20 0 -13 7 -20 20 -20 13 0 20 7 20 20 0 11 7 20 15 20 8 0 15 9 15 20 0
|
||||
11 -7 20 -15 20 -12 0 -15 15 -15 75 0 68 -2 75 -20 75 -13 0 -36 -24 -70 -75z
|
||||
m50 -35 c0 -36 -2 -40 -25 -40 -14 0 -25 3 -25 6 0 9 41 74 46 74 2 0 4 -18 4
|
||||
-40z"/>
|
||||
<path d="M5970 2905 c-27 -41 -50 -83 -50 -95 0 -17 6 -20 50 -20 45 0 50 -2
|
||||
50 -23 0 -16 6 -23 20 -24 15 -1 20 4 20 23 0 15 6 24 15 24 8 0 15 9 15 20 0
|
||||
11 -7 20 -15 20 -12 0 -15 15 -15 75 0 68 -2 75 -20 75 -13 0 -36 -24 -70 -75z
|
||||
m50 -35 c0 -38 -2 -40 -30 -40 l-30 0 26 40 c15 22 28 40 30 40 2 0 4 -18 4
|
||||
-40z"/>
|
||||
<path d="M2273 6120 c-12 -5 -27 -18 -33 -30 -15 -28 13 -39 40 -15 21 19 50
|
||||
14 50 -10 0 -8 -9 -20 -20 -27 -26 -17 -25 -31 3 -43 27 -12 30 -53 5 -68 -13
|
||||
-9 -21 -7 -36 6 -20 18 -52 23 -52 8 0 -5 8 -19 19 -32 23 -29 81 -31 111 -4
|
||||
27 24 27 79 0 98 -18 13 -19 16 -5 27 18 15 20 50 3 73 -16 20 -57 29 -85 17z"/>
|
||||
<path d="M9680 6123 c-24 -9 -50 -35 -50 -50 0 -18 36 -17 44 2 8 21 42 19 51
|
||||
-2 4 -12 -1 -24 -14 -35 -24 -19 -27 -38 -6 -38 22 0 38 -36 25 -60 -13 -24
|
||||
-33 -26 -54 -5 -18 19 -46 20 -46 2 0 -17 28 -43 57 -51 26 -8 78 16 87 39 12
|
||||
32 6 64 -14 78 -18 13 -19 16 -5 27 43 36 -19 113 -75 93z"/>
|
||||
<path d="M5955 2406 c-26 -26 -26 -46 1 -46 8 0 17 7 20 15 8 20 43 19 51 -2
|
||||
4 -10 -2 -23 -15 -34 -28 -22 -28 -32 1 -44 27 -12 30 -53 4 -69 -15 -9 -22
|
||||
-7 -35 9 -18 22 -52 22 -52 1 0 -44 94 -62 130 -26 26 26 26 75 0 93 -18 13
|
||||
-19 16 -5 27 35 29 10 86 -42 96 -26 5 -37 1 -58 -20z"/>
|
||||
<path d="M1697 6102 c-21 -24 -22 -52 -1 -52 9 0 24 10 33 22 22 27 51 18 51
|
||||
-15 0 -13 -12 -34 -26 -48 -31 -28 -84 -98 -84 -111 0 -4 36 -8 80 -8 73 0 80
|
||||
2 80 20 0 17 -7 20 -46 20 l-47 0 47 50 c51 55 58 88 26 120 -26 26 -90 27
|
||||
-113 2z"/>
|
||||
<path d="M10212 6104 c-27 -18 -30 -54 -4 -54 9 0 23 9 30 20 15 25 38 26 51
|
||||
2 12 -22 -1 -44 -59 -107 -64 -69 -62 -75 30 -75 73 0 80 2 80 20 0 17 -7 20
|
||||
-46 20 l-47 0 47 50 c49 52 56 81 30 118 -18 27 -78 30 -112 6z"/>
|
||||
<path d="M5973 1860 c-19 -8 -43 -38 -43 -54 0 -14 48 -5 54 10 9 23 50 14 54
|
||||
-12 2 -13 -16 -42 -53 -83 -75 -84 -73 -91 20 -91 68 0 75 2 75 20 0 17 -7 20
|
||||
-40 20 -22 0 -40 3 -40 6 0 4 18 26 40 50 31 33 40 52 40 78 0 54 -48 79 -107
|
||||
56z"/>
|
||||
<path d="M1158 6086 c-21 -18 -38 -38 -38 -45 0 -14 27 -15 35 -1 16 27 25 3
|
||||
25 -70 0 -79 0 -80 25 -80 l25 0 0 115 c0 132 -6 139 -72 81z"/>
|
||||
<path d="M10818 6086 c-21 -18 -38 -38 -38 -45 0 -14 27 -15 35 -1 16 27 25 3
|
||||
25 -70 0 -79 0 -80 25 -80 l25 0 0 115 c0 132 -6 139 -72 81z"/>
|
||||
<path d="M5988 1256 c-21 -18 -38 -38 -38 -45 0 -14 27 -15 35 -1 16 27 25 3
|
||||
25 -70 0 -79 0 -80 25 -80 l25 0 0 115 c0 132 -6 139 -72 81z"/>
|
||||
<path d="M10760 1064 c0 -3 5 -14 10 -25 14 -25 52 -24 75 1 22 25 11 39 -17
|
||||
21 -14 -9 -23 -10 -31 -2 -12 12 -37 15 -37 5z"/>
|
||||
<path d="M8445 993 c-33 -9 -65 -45 -65 -74 0 -28 17 -51 51 -69 33 -18 22
|
||||
-32 -23 -28 -42 3 -43 2 -46 -30 l-3 -32 50 0 c64 0 98 13 112 44 17 37 4 71
|
||||
-37 95 -42 25 -36 46 11 37 29 -6 33 -4 39 20 4 14 5 28 3 31 -8 7 -71 12 -92
|
||||
6z"/>
|
||||
<path d="M9395 993 c-56 -15 -85 -87 -47 -118 9 -7 28 -23 42 -35 l25 -20 -49
|
||||
2 c-46 2 -50 0 -57 -24 -9 -35 -5 -38 52 -38 62 0 97 14 110 44 17 37 4 71
|
||||
-37 95 -42 25 -36 46 11 37 29 -6 33 -4 39 20 4 14 5 28 3 31 -8 7 -71 12 -92
|
||||
6z"/>
|
||||
<path d="M9815 992 c-35 -12 -52 -26 -74 -62 -46 -77 -9 -170 69 -170 85 0
|
||||
130 46 130 133 0 61 -11 82 -51 96 -35 12 -44 12 -74 3z m53 -94 c4 -49 -12
|
||||
-78 -43 -78 -30 0 -41 33 -25 75 22 58 63 60 68 3z"/>
|
||||
<path d="M10770 987 c-69 -24 -75 -98 -10 -132 42 -22 37 -35 -14 -35 -36 0
|
||||
-45 -4 -49 -22 -9 -33 -1 -38 59 -38 48 0 60 4 85 29 16 16 29 37 29 47 0 9
|
||||
-18 34 -40 54 -22 20 -40 40 -40 45 0 4 16 5 35 2 32 -5 37 -3 46 20 5 14 8
|
||||
28 6 30 -12 11 -76 11 -107 0z"/>
|
||||
<path d="M11395 993 c-31 -7 -73 -52 -86 -90 -25 -76 14 -143 83 -143 82 0
|
||||
128 52 128 142 -1 49 -15 73 -54 88 -28 10 -38 11 -71 3z m53 -95 c4 -48 -12
|
||||
-78 -43 -78 -29 0 -38 24 -25 70 18 62 63 68 68 8z"/>
|
||||
<path d="M8567 968 c-8 -27 -1 -38 24 -38 21 0 20 -6 -1 -128 l-8 -43 37 3
|
||||
c35 3 36 4 43 53 15 109 17 115 42 115 23 0 36 17 36 46 0 11 -18 14 -84 14
|
||||
-77 0 -84 -2 -89 -22z"/>
|
||||
<path d="M8765 968 c-2 -13 -9 -52 -15 -88 -6 -36 -13 -77 -16 -92 -5 -26 -3
|
||||
-28 29 -28 33 0 36 2 42 40 8 50 19 51 31 3 8 -35 12 -38 47 -41 42 -4 43 0
|
||||
21 53 -15 37 -15 38 10 64 14 15 26 39 26 52 0 43 -28 59 -103 59 -61 0 -67
|
||||
-2 -72 -22z m101 -39 c10 -16 -23 -46 -36 -33 -6 6 -8 18 -4 27 7 19 29 22 40
|
||||
6z"/>
|
||||
<path d="M8974 953 c-3 -21 -12 -69 -20 -106 -8 -38 -14 -73 -14 -78 0 -5 35
|
||||
-9 79 -9 73 0 79 2 85 23 9 36 8 37 -40 37 -54 0 -57 23 -3 28 36 4 49 15 49
|
||||
45 0 13 -8 17 -35 17 -19 0 -35 5 -35 10 0 6 18 10 39 10 38 0 51 11 51 46 0
|
||||
11 -17 14 -75 14 l-74 0 -7 -37z"/>
|
||||
<path d="M9165 978 c-2 -7 -9 -47 -15 -88 -6 -41 -13 -87 -16 -102 l-6 -28 75
|
||||
0 c59 0 76 3 81 16 14 35 7 44 -35 44 -41 0 -41 0 -35 32 3 18 9 56 13 85 l6
|
||||
53 -32 0 c-17 0 -33 -6 -36 -12z"/>
|
||||
<path d="M9530 987 c0 -2 -9 -52 -20 -112 -11 -60 -20 -110 -20 -112 0 -2 15
|
||||
-3 34 -3 32 0 35 2 41 41 8 48 26 47 38 -4 9 -35 11 -37 49 -37 28 0 39 4 36
|
||||
13 -3 6 -13 36 -22 65 l-18 53 36 44 c20 24 36 46 36 49 0 3 -17 6 -37 6 -35
|
||||
0 -41 -4 -66 -48 l-28 -47 4 48 5 47 -34 0 c-19 0 -34 -1 -34 -3z"/>
|
||||
<path d="M10056 973 c-2 -10 -10 -52 -16 -93 -6 -41 -13 -85 -16 -97 -4 -22
|
||||
-1 -23 58 -23 112 0 175 56 164 145 -7 61 -41 85 -121 85 -52 0 -65 -3 -69
|
||||
-17z m102 -45 c15 -15 16 -63 2 -89 -6 -11 -21 -19 -36 -19 -23 0 -25 3 -20
|
||||
30 3 17 9 44 12 60 6 31 22 38 42 18z"/>
|
||||
<path d="M10277 897 c-24 -134 -24 -139 16 -135 29 3 32 6 35 41 4 48 23 44
|
||||
35 -6 8 -35 11 -37 47 -37 l38 0 -16 49 c-15 49 -15 50 7 68 29 23 32 80 6 99
|
||||
-11 9 -45 14 -85 14 l-67 0 -16 -93z m113 23 c0 -11 -2 -20 -4 -20 -2 0 -11
|
||||
-3 -20 -6 -11 -4 -16 0 -16 13 0 23 8 33 27 33 7 0 13 -9 13 -20z"/>
|
||||
<path d="M10491 927 c-16 -92 -14 -124 9 -147 38 -38 146 -20 165 28 6 17 35
|
||||
160 35 175 0 4 -15 7 -33 7 l-34 0 -13 -72 c-17 -96 -18 -98 -47 -98 -26 0
|
||||
-26 1 -20 60 4 33 10 72 13 85 6 23 3 25 -29 25 l-35 0 -11 -63z"/>
|
||||
<path d="M10906 968 c-9 -35 -8 -38 20 -38 19 0 24 -4 21 -17 -3 -10 -8 -36
|
||||
-11 -58 -4 -22 -9 -52 -12 -67 -5 -26 -3 -28 30 -28 39 0 38 -2 54 115 6 45
|
||||
10 50 35 53 19 2 27 9 27 22 0 10 4 21 9 24 9 6 16 -30 27 -141 l7 -73 44 0
|
||||
44 0 54 113 53 112 -36 3 c-40 4 -45 -1 -68 -73 -25 -75 -32 -74 -40 4 l-7 71
|
||||
-122 0 c-119 0 -123 -1 -129 -22z"/>
|
||||
<path d="M8786 603 c-3 -16 -10 -57 -16 -93 -6 -36 -13 -75 -15 -87 -5 -20 -1
|
||||
-23 29 -23 36 0 46 12 46 56 0 41 28 28 35 -16 7 -39 8 -40 46 -40 21 0 39 2
|
||||
39 3 0 2 -9 30 -20 62 -22 63 -22 61 29 123 l34 43 -44 -3 c-40 -3 -46 -7 -67
|
||||
-45 -23 -43 -48 -59 -38 -25 21 70 21 72 -17 72 -31 0 -36 -4 -41 -27z"/>
|
||||
<path d="M9031 604 c-55 -46 -64 -142 -18 -184 32 -30 107 -27 146 6 55 46 64
|
||||
133 19 182 -31 33 -106 31 -147 -4z m99 -44 c19 -35 -13 -110 -46 -110 -34 0
|
||||
-38 76 -6 113 20 22 39 22 52 -3z"/>
|
||||
<path d="M9240 603 c0 -15 5 -66 11 -115 l12 -88 43 0 44 0 44 95 c65 140 65
|
||||
137 21 133 -36 -3 -38 -5 -62 -70 -14 -37 -29 -65 -34 -62 -5 3 -9 34 -9 70
|
||||
l0 64 -35 0 c-31 0 -35 -3 -35 -27z"/>
|
||||
<path d="M9456 533 c-10 -54 -20 -106 -23 -115 -3 -15 2 -18 31 -18 40 0 36
|
||||
-9 61 129 19 107 20 101 -18 101 l-33 0 -18 -97z"/>
|
||||
<path d="M9557 537 c-24 -130 -23 -137 12 -137 24 0 29 5 35 31 3 17 6 42 6
|
||||
55 0 39 18 27 30 -18 6 -24 13 -49 15 -55 7 -20 73 -16 79 5 7 25 36 182 36
|
||||
198 0 20 -56 18 -64 -2 -3 -9 -6 -34 -6 -57 -1 -38 -2 -40 -15 -24 -7 11 -17
|
||||
36 -20 58 -7 38 -8 39 -49 39 l-43 0 -16 -93z"/>
|
||||
<path d="M9807 527 c-26 -58 -47 -109 -47 -116 0 -6 15 -11 34 -11 26 0 36 5
|
||||
41 20 4 13 15 20 31 20 17 0 24 -5 24 -20 0 -17 7 -20 39 -20 l39 0 -5 43 c-2
|
||||
23 -7 74 -11 112 l-7 70 -46 3 -46 3 -46 -104z m79 -35 c-13 -12 -18 9 -10 45
|
||||
l8 38 4 -39 c2 -21 1 -41 -2 -44z"/>
|
||||
<path d="M10024 616 c-3 -7 -13 -59 -23 -115 l-18 -101 32 0 c35 0 45 13 45
|
||||
56 0 38 18 28 30 -16 10 -37 13 -40 45 -40 40 0 39 -1 23 51 -10 35 -9 42 9
|
||||
62 35 38 37 64 9 92 -21 21 -34 25 -86 25 -42 0 -62 -4 -66 -14z m93 -52 c3
|
||||
-9 0 -20 -8 -25 -21 -13 -29 -11 -29 9 0 33 26 44 37 16z"/>
|
||||
<path d="M10306 618 c-2 -7 -11 -49 -21 -93 -9 -44 -19 -90 -22 -102 -5 -21
|
||||
-1 -23 30 -23 l36 0 6 55 c4 30 11 55 16 55 5 0 9 -25 9 -55 0 -54 1 -55 29
|
||||
-55 25 0 31 6 46 48 24 64 39 78 31 27 -4 -22 -9 -48 -12 -57 -4 -15 1 -18 29
|
||||
-18 l34 0 17 104 c9 58 16 110 16 116 0 7 -17 10 -47 8 l-48 -3 -21 -62 c-11
|
||||
-35 -24 -60 -27 -56 -4 3 -7 33 -7 65 l0 58 -45 0 c-28 0 -47 -5 -49 -12z"/>
|
||||
<path d="M10585 618 c-9 -31 -25 -122 -25 -148 0 -46 31 -70 90 -70 81 0 107
|
||||
38 125 178 l7 52 -34 0 -34 0 -12 -77 c-13 -82 -29 -110 -58 -99 -17 7 -17 30
|
||||
2 139 6 36 6 37 -25 37 -17 0 -33 -6 -36 -12z"/>
|
||||
<path d="M10806 614 c-11 -29 -6 -44 15 -44 23 0 23 -2 3 -127 l-7 -43 35 0
|
||||
35 0 14 83 c12 73 16 82 37 85 19 3 42 35 42 58 0 2 -38 4 -84 4 -67 0 -85 -3
|
||||
-90 -16z"/>
|
||||
<path d="M11020 610 c-6 -11 -26 -57 -46 -102 -19 -46 -38 -89 -41 -95 -4 -9
|
||||
6 -13 32 -13 28 0 38 5 42 20 4 14 14 20 34 20 22 0 29 -5 29 -20 0 -16 7 -20
|
||||
36 -20 l37 0 -7 78 c-14 155 -13 152 -62 152 -33 0 -46 -5 -54 -20z m50 -85
|
||||
c0 -22 -16 -42 -26 -33 -2 3 -1 19 2 36 7 36 24 33 24 -3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,34 @@
|
||||
**/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
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,616 @@
|
||||
// !$*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 */; };
|
||||
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 */; };
|
||||
/* 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 */
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
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 */,
|
||||
);
|
||||
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 = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
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 = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
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 = 1510;
|
||||
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 */
|
||||
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";
|
||||
};
|
||||
/* 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;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
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;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
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 = 12.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)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.shooterApp;
|
||||
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;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.shooterApp.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;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.shooterApp.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;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.shooterApp.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;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
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;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
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 = 12.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;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
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;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
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 = 12.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)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.shooterApp;
|
||||
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)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.shooterApp;
|
||||
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 */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
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"
|
||||
enableGPUValidationMode = "1"
|
||||
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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,13 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 807 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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>Shooter App</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>shooter_app</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>
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -0,0 +1,12 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart'; // Import for SystemChrome
|
||||
import 'screens/home_screen.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Set system UI overlay style globally for the app
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.grey[900], // Set status bar color (top bar)
|
||||
systemNavigationBarColor: Colors.grey[900], // Set navigation bar color
|
||||
systemNavigationBarIconBrightness: Brightness.light, // Set navigation icon color to light
|
||||
),
|
||||
);
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'App with Calendar',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const HomeScreen(), // Change LoginScreen to HomeScreen
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,809 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart'; // For DateFormat
|
||||
import 'package:path_provider/path_provider.dart'; // For getApplicationDocumentsDirectory
|
||||
import 'target_view.dart'; // Import the TargetViewScreen
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class BookScreen extends StatefulWidget {
|
||||
const BookScreen({super.key});
|
||||
|
||||
@override
|
||||
State<BookScreen> createState() => _BookScreenState();
|
||||
}
|
||||
|
||||
class _BookScreenState extends State<BookScreen> with WidgetsBindingObserver {
|
||||
Map<String, List<File>> _groupedFiles = {}; // Files grouped by folder
|
||||
Map<String, DateTime> _folderCreationDates = {}; // Folder creation dates
|
||||
Map<String, int> _folderScores = {}; // Folder scores (sum of scores of all target files inside)
|
||||
String? _expandedFolder;
|
||||
Color _bulletColor = Colors.blue; // default fallback
|
||||
Map<String, bool> _folderTypes = {}; // true = tournament, false = training
|
||||
String _folderFilter = 'all';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this); // Register to listen for lifecycle events
|
||||
_loadBulletColor();
|
||||
_loadTargetDataFiles(); // Initial load when the screen is first created
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this); // Unregister from lifecycle events when disposed
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
// Reload data when the screen is resumed or comes into view
|
||||
_loadTargetDataFiles();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadBulletColor() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selected = prefs.getString('bulletColor') ?? 'Blue';
|
||||
setState(() {
|
||||
_bulletColor = _getColorFromName(selected);
|
||||
});
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'sky':
|
||||
return Colors.orange;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
default:
|
||||
return Colors.blue; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Load the target data files from the directory
|
||||
void _loadTargetDataFiles() async {
|
||||
try {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final dataDirectory = Directory('${directory.path}/saved_images');
|
||||
|
||||
if (!await dataDirectory.exists()) {
|
||||
await dataDirectory.create(recursive: true);
|
||||
}
|
||||
|
||||
List<FileSystemEntity> folders = dataDirectory.listSync().whereType<Directory>().toList();
|
||||
|
||||
Map<String, List<File>> groupedFiles = {};
|
||||
Map<String, DateTime> folderDates = {};
|
||||
Map<String, int> folderScores = {};
|
||||
Map<String, bool> folderTypes = {}; // ✅
|
||||
|
||||
for (var folder in folders) {
|
||||
String folderName = folder.path.split('/').last;
|
||||
DateTime folderDate = folder.statSync().modified;
|
||||
|
||||
// ✅ Read isTournament flag
|
||||
bool isTournament = false;
|
||||
final metadataFile = File('${folder.path}/metadata.json');
|
||||
if (await metadataFile.exists()) {
|
||||
try {
|
||||
final metadata = jsonDecode(await metadataFile.readAsString());
|
||||
isTournament = metadata['isTournament'] == true;
|
||||
} catch (e) {
|
||||
debugPrint("Error reading metadata for $folderName: $e");
|
||||
}
|
||||
}
|
||||
folderTypes[folderName] = isTournament;
|
||||
|
||||
List<File> targetFiles = Directory(folder.path)
|
||||
.listSync()
|
||||
.whereType<File>()
|
||||
.where((file) =>
|
||||
file.path.endsWith(".json") &&
|
||||
!file.path.endsWith("metadata.json"))
|
||||
.toList();
|
||||
|
||||
int totalScore = 0;
|
||||
for (var file in targetFiles) {
|
||||
final jsonString = await file.readAsString();
|
||||
final data = jsonDecode(jsonString);
|
||||
final score = _getScoreFromJson(data);
|
||||
totalScore += score;
|
||||
}
|
||||
|
||||
groupedFiles[folderName] = targetFiles;
|
||||
folderDates[folderName] = folderDate;
|
||||
folderScores[folderName] = totalScore;
|
||||
}
|
||||
|
||||
final sortedFolderNames = folderDates.keys.toList()
|
||||
..sort((a, b) => folderDates[b]!.compareTo(folderDates[a]!));
|
||||
|
||||
final sortedGroupedFiles = {
|
||||
for (var folderName in sortedFolderNames) folderName: groupedFiles[folderName]!
|
||||
};
|
||||
final sortedFolderScores = {
|
||||
for (var folderName in sortedFolderNames) folderName: folderScores[folderName]!
|
||||
};
|
||||
final sortedFolderDates = {
|
||||
for (var folderName in sortedFolderNames) folderName: folderDates[folderName]!
|
||||
};
|
||||
final sortedFolderTypes = {
|
||||
for (var folderName in sortedFolderNames) folderName: folderTypes[folderName]!
|
||||
};
|
||||
|
||||
setState(() {
|
||||
_groupedFiles = sortedGroupedFiles;
|
||||
_folderCreationDates = sortedFolderDates;
|
||||
_folderScores = sortedFolderScores;
|
||||
_folderTypes = sortedFolderTypes; // ✅ FIXED
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint("Error loading target data files: $e");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Function to safely get the score from the JSON data
|
||||
int _getScoreFromJson(Map<String, dynamic> data) {
|
||||
if (data.containsKey('totalScore') && data['totalScore'] is int) {
|
||||
return data['totalScore'];
|
||||
}
|
||||
return 0; // Default to 0 if the score is not present or invalid
|
||||
}
|
||||
|
||||
// Delete a file
|
||||
Future<void> _deleteFile(File file) async {
|
||||
bool? confirmDeletion = await _showConfirmationDialog(
|
||||
context,
|
||||
'Are you sure you want to delete this file?',
|
||||
);
|
||||
|
||||
if (confirmDeletion == true) {
|
||||
try {
|
||||
await file.delete();
|
||||
_loadTargetDataFiles(); // Refresh after deleting the file
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Error deleting file: $e")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open file preview in the TargetViewScreen
|
||||
void _openFilePreview(File targetFile) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TargetViewScreen(
|
||||
jsonFiles: [targetFile], // Pass the selected file in a list
|
||||
refreshFolders: _loadTargetDataFiles, // Pass the refreshFolders callback
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Open folder's combined target view in the TargetViewScreen
|
||||
void _openFolderTargetView(String folderName) async {
|
||||
final targetFiles = _groupedFiles[folderName] ?? [];
|
||||
|
||||
List<Map<String, dynamic>> allBullets = [];
|
||||
String latestDate = 'Unknown';
|
||||
|
||||
for (var file in targetFiles) {
|
||||
final jsonString = await file.readAsString();
|
||||
final data = jsonDecode(jsonString);
|
||||
|
||||
final bullets = (data['bullets'] as List)
|
||||
.map((b) => {'x': b['x'], 'y': b['y']})
|
||||
.toList();
|
||||
|
||||
allBullets.addAll(bullets);
|
||||
|
||||
if (data['date'] != null) {
|
||||
latestDate = data['date']; // You could sort if you want the most recent
|
||||
}
|
||||
}
|
||||
|
||||
// Create combined JSON structure
|
||||
final combinedJson = jsonEncode({
|
||||
'date': latestDate,
|
||||
'bullets': allBullets,
|
||||
});
|
||||
|
||||
// Save this JSON temporarily to disk
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final combinedFile = File('${tempDir.path}/combined_${folderName}.json');
|
||||
await combinedFile.writeAsString(combinedJson);
|
||||
|
||||
// Navigate to view screen with the combined file
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TargetViewScreen(
|
||||
jsonFiles: [combinedFile], // treat it like a normal file
|
||||
refreshFolders: _loadTargetDataFiles,
|
||||
isCombinedView: true, // 👈 This line
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _filteredFolderNames() {
|
||||
if (_folderFilter == 'all') return _groupedFiles.keys.toList();
|
||||
|
||||
final isTournamentFilter = _folderFilter == 'tournament';
|
||||
|
||||
return _groupedFiles.keys
|
||||
.where((folderName) => _folderTypes[folderName] == isTournamentFilter)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Rename folder
|
||||
Future<void> _renameFolder(String oldFolderName) async {
|
||||
final newNameController = TextEditingController();
|
||||
bool isRenameEnabled = false;
|
||||
bool isTournament = _folderTypes[oldFolderName] == true;
|
||||
|
||||
final result = await showDialog<Map<String, dynamic>>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Folder Settings',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
TextField(
|
||||
controller: newNameController,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[800],
|
||||
hintText: 'Enter new folder name',
|
||||
hintStyle: const TextStyle(color: Colors.white54),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _bulletColor),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
isRenameEnabled = value.trim().isNotEmpty;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isTournament = !isTournament;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: isTournament
|
||||
? _bulletColor.withOpacity(0.2)
|
||||
: Colors.grey[850],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isTournament
|
||||
? _bulletColor
|
||||
: Colors.grey[700]!,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 14),
|
||||
child: Center(
|
||||
child: Text(
|
||||
isTournament
|
||||
? 'Tournament Mode 🏆'
|
||||
: 'Mark as Tournament 🏆',
|
||||
style: TextStyle(
|
||||
color: isTournament
|
||||
? _bulletColor
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop({
|
||||
'name': newNameController.text.trim(),
|
||||
'isTournament': isTournament,
|
||||
}),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _bulletColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text('Apply'),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// ✅ Handle result (rename + update tag)
|
||||
if (result != null) {
|
||||
final newName = result['name'];
|
||||
final isTournament = result['isTournament'] ?? false;
|
||||
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final oldFolderPath =
|
||||
Directory('${directory.path}/saved_images/$oldFolderName');
|
||||
|
||||
final targetFolderName =
|
||||
(newName != null && newName.isNotEmpty) ? newName : oldFolderName;
|
||||
|
||||
final newFolderPath =
|
||||
Directory('${directory.path}/saved_images/$targetFolderName');
|
||||
|
||||
if (targetFolderName != oldFolderName) {
|
||||
if (await newFolderPath.exists()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('A folder named "$targetFolderName" already exists.')),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
await oldFolderPath.rename(newFolderPath.path);
|
||||
}
|
||||
}
|
||||
|
||||
final metadataFile = File('${newFolderPath.path}/metadata.json');
|
||||
if (isTournament) {
|
||||
await metadataFile.writeAsString(jsonEncode({'isTournament': true}));
|
||||
} else {
|
||||
if (await metadataFile.exists()) {
|
||||
await metadataFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
_loadTargetDataFiles(); // refresh UI
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Delete a folder
|
||||
Future<void> _deleteFolder(String folderName) async {
|
||||
bool? confirmDeletion = await _showConfirmationDialog(
|
||||
context,
|
||||
'Are you sure you want to delete the folder "$folderName" and all of its contents?',
|
||||
);
|
||||
|
||||
if (confirmDeletion == true) {
|
||||
try {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final folderPath = Directory('${directory.path}/saved_images/$folderName');
|
||||
|
||||
if (await folderPath.exists()) {
|
||||
await folderPath.delete(recursive: true);
|
||||
}
|
||||
|
||||
_loadTargetDataFiles(); // Refresh file/folder list
|
||||
|
||||
if (_expandedFolder == folderName) {
|
||||
setState(() {
|
||||
_expandedFolder = null;
|
||||
});
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Folder "$folderName" deleted successfully.'),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Error deleting folder: $e")),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool?> _showConfirmationDialog(BuildContext context, String message) async {
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Confirm Deletion',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
message,
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// "Yes" Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.redAccent,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Yes, delete"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// "No" Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Build the folder row with folder name, trash, and rename options
|
||||
// Build the folder row with folder name, trash, rename options, and view all targets icon
|
||||
Widget _buildFolderRow(String folderName) {
|
||||
final numberOfTargets = _groupedFiles[folderName]!.length;
|
||||
final totalScore = _folderScores[folderName];
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300], // Lighter background for folder
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
title: Row(
|
||||
children: [
|
||||
Text(folderName, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
if (_folderTypes[folderName] == true)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 6),
|
||||
child: Text('🏆', style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), // Add padding between title and info
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.insert_drive_file, color: Colors.black45),
|
||||
const SizedBox(width: 4), // Small space between the icon and the text
|
||||
Text(
|
||||
"$numberOfTargets / 🎯 $totalScore",
|
||||
style: const TextStyle(fontSize: 16, color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.my_location, color: Colors.grey),
|
||||
onPressed: () => _openFolderTargetView(folderName),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit, color: _bulletColor),
|
||||
onPressed: () => _renameFolder(folderName),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () => _deleteFolder(folderName),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
// If the folder is already expanded, collapse it, otherwise expand it
|
||||
_expandedFolder = _expandedFolder == folderName ? null : folderName;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_expandedFolder == folderName)
|
||||
// When expanded, display the file row inside the folder
|
||||
..._groupedFiles[folderName]!
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => _buildFileRow(entry.value, folderName, entry.key))
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Build the row for files in the folder with everything in a single row
|
||||
Widget _buildFileRow(File file, String folderName, int index) {
|
||||
final fileName = "${index + 1} Target"; // Use number starting from 1
|
||||
final folderDate = _folderCreationDates[folderName];
|
||||
final formattedDate = folderDate != null
|
||||
? DateFormat('yyyy-MM-dd').format(folderDate)
|
||||
: 'Unknown Date'; // or any default fallback string
|
||||
|
||||
final score = _getScoreFromJson(jsonDecode(file.readAsStringSync())); // Read the score for the file
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), // Reduced vertical padding for compact height
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), // Tight margin
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200], // Light grey background for files
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.grey[300]!, width: 1), // Light border around the file
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _openFilePreview(file), // Open the file preview when the row is tapped
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, // Distribute space across the row
|
||||
children: [
|
||||
// File name
|
||||
Text(fileName, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
|
||||
// Date
|
||||
Text(formattedDate, style: const TextStyle(fontSize: 12, color: Colors.black54)),
|
||||
|
||||
// Target emoji and score
|
||||
Text("🎯 $score", style: const TextStyle(fontSize: 14, color: Colors.black54)),
|
||||
|
||||
// Delete button
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Color.fromARGB(255, 252, 102, 92)),
|
||||
onPressed: () => _deleteFile(file), // Trigger file deletion
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
// 🔽 Filter dropdown at the top
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => _showFolderFilterPopup(context),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300], // Same as folder background
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
_folderFilter == 'all'
|
||||
? 'Showing: All folders'
|
||||
: _folderFilter == 'tournament'
|
||||
? 'Showing: 🏆 Tournament folders'
|
||||
: 'Showing: 🎯 Training folders',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const Icon(Icons.filter_list),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
|
||||
// 📁 Folder list or empty state
|
||||
Expanded(
|
||||
child: _groupedFiles.isEmpty
|
||||
? const Center(child: Text("No saved target data found", style: TextStyle(fontSize: 18)))
|
||||
: ListView.builder(
|
||||
itemCount: _filteredFolderNames().length,
|
||||
itemBuilder: (context, index) {
|
||||
String folderName = _filteredFolderNames()[index];
|
||||
return _buildFolderRow(folderName);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showFolderFilterPopup(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Filter Folders',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildFilterOption('all', '📂 All folders'),
|
||||
const SizedBox(height: 12),
|
||||
_buildFilterOption('tournament', '🏆 Tournament'),
|
||||
const SizedBox(height: 12),
|
||||
_buildFilterOption('training', '🎯 Training'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildFilterOption(String value, String label) {
|
||||
final isSelected = _folderFilter == value;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_folderFilter = value;
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? _bulletColor.withOpacity(0.3) : Colors.grey[800],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isSelected ? _bulletColor : Colors.transparent,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class CalendarScreen extends StatefulWidget {
|
||||
const CalendarScreen({super.key});
|
||||
|
||||
@override
|
||||
State<CalendarScreen> createState() => _CalendarScreenState();
|
||||
}
|
||||
|
||||
class _CalendarScreenState extends State<CalendarScreen> {
|
||||
late final WebViewController _controller;
|
||||
bool _isLoading = true;
|
||||
bool _hasError = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onPageStarted: (_) => setState(() {
|
||||
_isLoading = true;
|
||||
_hasError = false;
|
||||
}),
|
||||
onPageFinished: (_) => setState(() => _isLoading = false),
|
||||
onWebResourceError: (error) => setState(() {
|
||||
_hasError = true;
|
||||
_isLoading = false;
|
||||
}),
|
||||
),
|
||||
)
|
||||
..loadRequest(Uri.parse(
|
||||
'https://calendar.google.com/calendar/u/0/embed?src=b9c267252d5be63d5cdc880be499885a88421eefd0e34e1392890daba0d8471e@group.calendar.google.com&ctz=Europe/Belgrade'));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
WebViewWidget(controller: _controller),
|
||||
if (_isLoading)
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
if (_hasError)
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
"Failed to load Google Calendar.",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () => _controller.reload(),
|
||||
child: const Text("Retry"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'yolo_detector.dart';
|
||||
import 'preview_screen.dart';
|
||||
|
||||
class CameraScreen extends StatefulWidget {
|
||||
const CameraScreen({super.key});
|
||||
|
||||
@override
|
||||
State<CameraScreen> createState() => _CameraScreenState();
|
||||
}
|
||||
|
||||
class _CameraScreenState extends State<CameraScreen> {
|
||||
CameraController? _controller;
|
||||
Future<void>? _initializeControllerFuture;
|
||||
late YOLODetector _yoloDetector;
|
||||
Color _bulletColor = Colors.blue; // default fallback
|
||||
|
||||
// The variable to hold the number of bullets entered by the user (default set to 5)
|
||||
int _numberOfBullets = 5;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBulletCount();
|
||||
_initCamera();
|
||||
_loadBulletColor(); // 👈
|
||||
YOLODetector.create().then((detector) => _yoloDetector = detector);
|
||||
}
|
||||
|
||||
Future<void> _loadBulletColor() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selected = prefs.getString('bulletColor') ?? 'Blue';
|
||||
setState(() {
|
||||
_bulletColor = _getColorFromName(selected);
|
||||
});
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'sky':
|
||||
return Colors.lightBlueAccent;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
default:
|
||||
return Colors.blue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _loadBulletCount() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_numberOfBullets = prefs.getInt('bulletCount') ?? 5; // Load stored bullet count, default to 5
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveBulletCount(int count) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('bulletCount', count); // Save the bullet count
|
||||
}
|
||||
|
||||
Future<void> _initCamera() async {
|
||||
final cameras = await availableCameras();
|
||||
final camera = cameras.first;
|
||||
|
||||
_controller = CameraController(
|
||||
camera,
|
||||
ResolutionPreset.high,
|
||||
enableAudio: false,
|
||||
);
|
||||
|
||||
_initializeControllerFuture = _controller!.initialize();
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
_yoloDetector.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _takePicture() async {
|
||||
try {
|
||||
await _initializeControllerFuture;
|
||||
final file = await _controller!.takePicture();
|
||||
final imageFile = File(file.path);
|
||||
final imageBytes = await imageFile.readAsBytes();
|
||||
final decodedImage = img.decodeImage(imageBytes)!;
|
||||
|
||||
// Crop center square from full image
|
||||
final width = decodedImage.width;
|
||||
final height = decodedImage.height;
|
||||
final size = width < height ? width : height;
|
||||
final offsetX = (width - size) ~/ 2;
|
||||
final offsetY = (height - size) ~/ 2;
|
||||
|
||||
final cropped = img.copyCrop(
|
||||
decodedImage,
|
||||
x: offsetX,
|
||||
y: offsetY,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
|
||||
final croppedFile = File('${file.path}_square.jpg')
|
||||
..writeAsBytesSync(img.encodeJpg(cropped));
|
||||
|
||||
final detections = await _yoloDetector.detect(croppedFile);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PreviewScreen(
|
||||
imagePath: croppedFile.path,
|
||||
detections: detections,
|
||||
bulletCount: _numberOfBullets, // Pass the bullet count
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error taking picture: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Method to show the dialog for selecting the number of bullets
|
||||
void _showBulletDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0), // Increased padding for better spacing
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
'Select the number of bullets',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: List.generate(5, (index) {
|
||||
final bulletNum = index + 1;
|
||||
final isSelected = _numberOfBullets == bulletNum;
|
||||
return ElevatedButton(
|
||||
onPressed: () {
|
||||
_selectBulletCount(bulletNum);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
isSelected ? _bulletColor : Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'$bulletNum',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Update the bullet count and save it
|
||||
void _selectBulletCount(int number) {
|
||||
setState(() {
|
||||
_numberOfBullets = number; // Update the selected number
|
||||
});
|
||||
_saveBulletCount(number); // Save the selected number
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_controller == null || _initializeControllerFuture == null) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.grey[900],
|
||||
title: const Text(
|
||||
"Align the target inside the square",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
// Display the selected bullet count
|
||||
IconButton(
|
||||
icon: Text(
|
||||
'$_numberOfBullets',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: _showBulletDialog, // Show dialog on tap
|
||||
),
|
||||
const SizedBox(width: 16), // Space after the number
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
color: Colors.grey[900],
|
||||
shape: const CircularNotchedRectangle(),
|
||||
notchMargin: 8.0,
|
||||
child: SizedBox(height: 60),
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
backgroundColor: Colors.white,
|
||||
onPressed: _takePicture,
|
||||
child: const Icon(Icons.camera_alt, color: Colors.black),
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _initializeControllerFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black, // covers any empty space
|
||||
child: FittedBox(
|
||||
fit: BoxFit.cover,
|
||||
child: SizedBox(
|
||||
width: _controller!.value.previewSize!.height,
|
||||
height: _controller!.value.previewSize!.width,
|
||||
child: CameraPreview(_controller!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
width: screenSize.width * 0.9,
|
||||
height: screenSize.width * 0.9,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _bulletColor.withOpacity(0.9),
|
||||
width: 3,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'book_screen.dart';
|
||||
import 'calendar_screen.dart';
|
||||
import 'settings_screen.dart';
|
||||
import 'target_screen.dart';
|
||||
import '../widgets/bottom_nav.dart';
|
||||
//import 'login_screen.dart';
|
||||
import 'camera_screen.dart';
|
||||
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
_HomeScreenState createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
int _selectedIndex = 1; // Default to Shooting Screen
|
||||
|
||||
static final List<Widget> _pages = <Widget>[
|
||||
BookScreen(),
|
||||
TargetScreen(),
|
||||
Placeholder(), // Placeholder for Scan - optional
|
||||
CalendarScreen(),
|
||||
SettingsScreen(),
|
||||
];
|
||||
|
||||
static final List<String> _pageTitles = [
|
||||
'Book',
|
||||
'Shooting',
|
||||
'Scan',
|
||||
'Calendar',
|
||||
'Settings'
|
||||
];
|
||||
|
||||
void _onItemTapped(int index) {
|
||||
if (index == 2) {
|
||||
_openCameraScan();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
//void _logout() {
|
||||
// Navigator.of(context).pushAndRemoveUntil(
|
||||
// MaterialPageRoute(builder: (context) => const LoginScreen()),
|
||||
// (Route<dynamic> route) => false,
|
||||
// );
|
||||
//}
|
||||
|
||||
// When opening the camera screen, ensure the shooting screen is always selected after returning
|
||||
void _openCameraScan() async {
|
||||
// Navigate to CameraScreen
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => const CameraScreen()),
|
||||
);
|
||||
|
||||
// After returning, ensure that Shooting screen (index 1) is selected
|
||||
setState(() {
|
||||
_selectedIndex = 1;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
_pageTitles[_selectedIndex],
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: Colors.grey[900],
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
automaticallyImplyLeading: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout, color: Colors.red),
|
||||
onPressed: null, // disables the button
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _pages[_selectedIndex],
|
||||
bottomNavigationBar: BottomNavBar(
|
||||
selectedIndex: _selectedIndex,
|
||||
onItemTapped: _onItemTapped,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../widgets/input_field.dart';
|
||||
import 'home_screen.dart';
|
||||
import 'register_screen.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final usernameController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
bool rememberMe = false;
|
||||
|
||||
final GoogleSignIn _googleSignIn = GoogleSignIn(
|
||||
clientId: 'YOUR_WEB_CLIENT_ID_HERE',
|
||||
scopes: ['email'],
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkAutoLogin();
|
||||
}
|
||||
|
||||
Future<void> _checkAutoLogin() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final isRemembered = prefs.getBool('rememberMe') ?? false;
|
||||
final googleUser = prefs.getString('googleUser');
|
||||
|
||||
if (isRemembered) {
|
||||
final savedUsername = prefs.getString('loginUsername') ?? prefs.getString('username');
|
||||
final savedPassword = prefs.getString('password');
|
||||
if (savedUsername != null && savedPassword != null) {
|
||||
usernameController.text = savedUsername;
|
||||
passwordController.text = savedPassword;
|
||||
setState(() => rememberMe = true);
|
||||
}
|
||||
} else if (googleUser != null) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const HomeScreen()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveLoginDetails(String username, String password) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('loginUsername', username); // Ensures login uses latest username
|
||||
await prefs.setString('password', password);
|
||||
|
||||
if (rememberMe) {
|
||||
await prefs.setBool('rememberMe', true);
|
||||
} else {
|
||||
await prefs.remove('rememberMe');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void login() async {
|
||||
String username = usernameController.text.trim();
|
||||
String password = passwordController.text.trim();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String? savedUsername = prefs.getString('loginUsername') ?? prefs.getString('username'); // Get the latest username
|
||||
String? savedPassword = prefs.getString('password'); // Get the saved password
|
||||
|
||||
if (savedUsername == username && savedPassword == password) {
|
||||
await _saveLoginDetails(username, password);
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const HomeScreen()),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Invalid username or password')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> googleLogin() async {
|
||||
try {
|
||||
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
|
||||
if (googleUser != null) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('googleUser', googleUser.displayName ?? 'Google User');
|
||||
await prefs.setString('googleEmail', googleUser.email);
|
||||
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const HomeScreen()),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Google Sign-In Failed: $error')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/logo.png',
|
||||
width: 1000,
|
||||
height: 150,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
InputField(
|
||||
hintText: 'Username',
|
||||
icon: Icons.person,
|
||||
controller: usernameController,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
InputField(
|
||||
hintText: 'Password',
|
||||
icon: Icons.lock,
|
||||
obscureText: true,
|
||||
controller: passwordController,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Remember Me Checkbox - Centered
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: rememberMe,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
rememberMe = value ?? false;
|
||||
});
|
||||
},
|
||||
activeColor: Colors.grey[800],
|
||||
),
|
||||
const Text(
|
||||
'Remember Me',
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
/// Login Button
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
onPressed: login,
|
||||
child: const Text('Login', style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Horizontal Line with "or"
|
||||
Row(
|
||||
children: const [
|
||||
Expanded(
|
||||
child: Divider(color: Colors.grey, thickness: 1),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text("or", style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
),
|
||||
Expanded(
|
||||
child: Divider(color: Colors.grey, thickness: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Google Sign-In Button (Styled same as Login)
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800], // Match Login Button
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
onPressed: googleLogin,
|
||||
icon: const Icon(Icons.g_mobiledata, size: 30), // Google Icon
|
||||
label: const Text('Sign in with Google', style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
/// Register Text (Styled in Blue)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const RegisterScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
"Don't have an account? Register",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey, // Better visibility
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,972 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'yolo_detector.dart';
|
||||
import 'dart:convert'; // For JSON encoding
|
||||
import 'dart:math';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class PreviewScreen extends StatefulWidget {
|
||||
final String imagePath;
|
||||
final List<DetectionResult> detections;
|
||||
final int bulletCount; // Add bulletCount parameter
|
||||
|
||||
const PreviewScreen({super.key, required this.imagePath, required this.detections, required this.bulletCount});
|
||||
|
||||
@override
|
||||
State<PreviewScreen> createState() => _PreviewScreenState();
|
||||
}
|
||||
|
||||
enum PreviewMode { cameraWithDetections, mappedTarget }
|
||||
|
||||
class _PreviewScreenState extends State<PreviewScreen> {
|
||||
bool _isLoading = true;
|
||||
PreviewMode _mode = PreviewMode.mappedTarget;
|
||||
|
||||
List<Offset> _mappedBullets = [];
|
||||
late List<DetectionResult> _realImageDetections;
|
||||
|
||||
img.Image? _realImageDecoded;
|
||||
img.Image? _targetImageDecoded;
|
||||
Future<img.Image?>? _targetImageFuture;
|
||||
Offset? _targetImageCenterForScoring;
|
||||
|
||||
Uint8List? _cachedTargetImage;
|
||||
Uint8List? _cachedRealImage;
|
||||
|
||||
final String targetAssetPath = 'assets/target.png';
|
||||
|
||||
final Offset fixedRingTargetCenter = const Offset(602.51, 600.43);
|
||||
final Size fixedRingTargetSize = const Size(355.94, 359.27);
|
||||
|
||||
int? _highlightedBulletIndex;
|
||||
|
||||
final List<int> _manualBulletScores = [];
|
||||
bool _hasMissingBullets = false;
|
||||
|
||||
Color _bulletColor = Colors.blue; // default fallback
|
||||
double get _bulletOpacity => 0.8;
|
||||
Color get _bulletColorWithOpacity => _bulletColor.withOpacity(_bulletOpacity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_realImageDetections = widget.detections;
|
||||
_targetImageFuture = _loadAndDecodeTargetImage();
|
||||
_loadBulletColor(); // 👈 Add this
|
||||
_runDetectionAndMapping();
|
||||
}
|
||||
|
||||
Future<void> _loadBulletColor() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
|
||||
|
||||
setState(() {
|
||||
_bulletColor = _getColorFromName(selectedColor);
|
||||
});
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'sky':
|
||||
return Colors.orange;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
default:
|
||||
return Colors.blue; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleMissingBullets() async {
|
||||
final currentDetected = _mappedBullets.length;
|
||||
final result = await _showMismatchOptionsDialog(currentDetected);
|
||||
if (result == 'retry') {
|
||||
Navigator.pop(context); // Go back to camera screen
|
||||
} else if (result == 'manual') {
|
||||
_manualScoreEntry();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Offset _generatePositionForScore(int score, Offset center) {
|
||||
const double bulletRadius = 25;
|
||||
final List<double> rings = [14, 70, 125, 180, 235, 290, 345, 400, 455, 510];
|
||||
|
||||
final rng = score.clamp(0, 10);
|
||||
final double distance = rng == 0 ? rings.last + bulletRadius + 5 : rings[10 - rng];
|
||||
|
||||
final angle = (360 / widget.bulletCount) * (_manualBulletScores.length + 1);
|
||||
final radians = angle * 3.14159 / 180;
|
||||
|
||||
return Offset(
|
||||
center.dx + distance * cos(radians),
|
||||
center.dy + distance * sin(radians),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<img.Image?> _loadAndDecodeTargetImage() async {
|
||||
final byteData = await rootBundle.load(targetAssetPath);
|
||||
final bytes = byteData.buffer.asUint8List();
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final targetFile = File('${tempDir.path}/target.png');
|
||||
await targetFile.writeAsBytes(bytes);
|
||||
final decoded = img.decodeImage(bytes);
|
||||
if (decoded != null) {
|
||||
_targetImageCenterForScoring = Offset(decoded.width / 2, decoded.height / 2 - 1);
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
Future<void> _runDetectionAndMapping() async {
|
||||
try {
|
||||
// 1. Read the real image file
|
||||
final File realImageFile = File(widget.imagePath);
|
||||
final realBytes = await realImageFile.readAsBytes();
|
||||
|
||||
// 2. Decode the real image (ensure it's valid)
|
||||
_realImageDecoded = img.decodeImage(realBytes);
|
||||
if (_realImageDecoded == null) {
|
||||
debugPrint("❌ Failed to decode image from path: ${widget.imagePath}");
|
||||
setState(() => _isLoading = false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Cache decoded image as PNG for rendering
|
||||
_cachedRealImage = Uint8List.fromList(img.encodePng(_realImageDecoded!));
|
||||
|
||||
// 4. Find the detected ring center and size
|
||||
final ringCamData = _getRingCenterSize(_realImageDetections);
|
||||
if (ringCamData == null) {
|
||||
debugPrint("⚠️ Ring (classId 2) not detected in real image.");
|
||||
setState(() => _isLoading = false);
|
||||
return;
|
||||
}
|
||||
|
||||
final Offset ringCamCenter = ringCamData['center'];
|
||||
final Size sizeCam = ringCamData['size'];
|
||||
|
||||
// 5. Extract all bullet centers (classId 0)
|
||||
final List<Offset> bulletsCam = _realImageDetections
|
||||
.where((r) => r.classId == 0)
|
||||
.map((r) => Offset((r.x1 + r.x2) / 2, (r.y1 + r.y2) / 2))
|
||||
.toList();
|
||||
|
||||
// 6. Map bullets to target and score each one
|
||||
final List<Map<String, dynamic>> scoredBullets = bulletsCam.map((bullet) {
|
||||
final mappedBullet = _mapToTargetNormalized(
|
||||
bullet,
|
||||
ringCamCenter,
|
||||
fixedRingTargetCenter,
|
||||
sizeCam,
|
||||
fixedRingTargetSize,
|
||||
);
|
||||
final score = _scoreForBullet(
|
||||
mappedBullet,
|
||||
_targetImageCenterForScoring ?? fixedRingTargetCenter,
|
||||
);
|
||||
return {'bullet': mappedBullet, 'score': score};
|
||||
}).toList();
|
||||
|
||||
// 7. Trim to bullet limit by removing highest scoring ones
|
||||
scoredBullets.sort((a, b) => b['score'].compareTo(a['score']));
|
||||
while (scoredBullets.length > widget.bulletCount) {
|
||||
scoredBullets.removeAt(0);
|
||||
}
|
||||
|
||||
// 8. Collect final mapped bullets
|
||||
final List<Offset> mappedBullets = scoredBullets
|
||||
.map((e) => e['bullet'] as Offset)
|
||||
.toList();
|
||||
|
||||
// 9. Load target background image
|
||||
_targetImageDecoded = await _targetImageFuture;
|
||||
if (_targetImageDecoded != null) {
|
||||
_cachedTargetImage = Uint8List.fromList(img.encodePng(_targetImageDecoded!));
|
||||
}
|
||||
|
||||
// 10. Update UI
|
||||
final missing = widget.bulletCount - mappedBullets.length;
|
||||
setState(() {
|
||||
_mappedBullets = mappedBullets;
|
||||
_isLoading = false;
|
||||
_hasMissingBullets = missing > 0;
|
||||
});
|
||||
|
||||
} catch (e, stack) {
|
||||
debugPrint("❌ Error during detection mapping: $e");
|
||||
debugPrint(stack.toString());
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<String?> _showMismatchOptionsDialog(int detectedCount) async {
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final missing = widget.bulletCount - detectedCount;
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
title: const Text(
|
||||
'Bullet Count Mismatch',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
content: Text(
|
||||
'Detected $detectedCount bullet(s), expected ${widget.bulletCount}.\n'
|
||||
'You can retry detection or manually add the ${missing.abs()} missing shot(s).',
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 12),
|
||||
actionsPadding: const EdgeInsets.only(bottom: 16, left: 16, right: 16),
|
||||
actions: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, 'retry'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.redAccent,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Icon(Icons.restart_alt, color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, 'manual'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _bulletColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Icon(Icons.edit, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _manualScoreEntry() {
|
||||
final missingCount = widget.bulletCount - _mappedBullets.length;
|
||||
final controllers = List.generate(missingCount, (_) => TextEditingController());
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Enter Missing Bullet Scores',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: List.generate(missingCount, (index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: TextField(
|
||||
controller: controllers[index],
|
||||
keyboardType: TextInputType.number,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Bullet Score ${_mappedBullets.length + index + 1}',
|
||||
labelStyle: const TextStyle(color: Colors.white54),
|
||||
filled: true,
|
||||
fillColor: Colors.grey[800],
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _bulletColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
final scores = controllers.map((c) => int.tryParse(c.text.trim()) ?? 0).toList();
|
||||
final center = _targetImageCenterForScoring ?? fixedRingTargetCenter;
|
||||
|
||||
setState(() {
|
||||
for (final score in scores) {
|
||||
final offset = _generatePositionForScore(score, center);
|
||||
_mappedBullets.add(offset);
|
||||
_manualBulletScores.add(score);
|
||||
}
|
||||
});
|
||||
|
||||
Navigator.pop(context);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.greenAccent[700],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text('Add Bullets'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveTargetData() async {
|
||||
try {
|
||||
// Show a dialog to let the user select between a new folder or an existing one
|
||||
final result = await _showSaveOptionsDialog();
|
||||
|
||||
if (result == null) return;
|
||||
|
||||
String? selectedFolder;
|
||||
bool isTournament = false;
|
||||
|
||||
try {
|
||||
final decoded = jsonDecode(result);
|
||||
selectedFolder = decoded['folder'];
|
||||
isTournament = decoded['isTournament'] == true;
|
||||
} catch (_) {
|
||||
selectedFolder = result; // Treat plain string as folder name (existing folder)
|
||||
}
|
||||
|
||||
if (selectedFolder == null) return;
|
||||
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final saveDirectory = Directory('${directory.path}/saved_images/$selectedFolder');
|
||||
|
||||
// Create folder if it doesn't exist
|
||||
if (!await saveDirectory.exists()) {
|
||||
await saveDirectory.create(recursive: true);
|
||||
}
|
||||
|
||||
// Save tournament metadata if applicable
|
||||
if (isTournament) {
|
||||
final metadataFile = File('${saveDirectory.path}/metadata.json');
|
||||
await metadataFile.writeAsString(jsonEncode({'isTournament': true}));
|
||||
}
|
||||
|
||||
// Use timestamp for a unique file name
|
||||
final now = DateTime.now();
|
||||
final dateString = "${now.year}-${now.month}-${now.day}";
|
||||
final timeString = "${now.hour}-${now.minute}-${now.second}";
|
||||
final fileName = "target_${dateString}_$timeString.json";
|
||||
final filePath = '${saveDirectory.path}/$fileName';
|
||||
|
||||
// Prepare target data to save
|
||||
final targetData = {
|
||||
'date': dateString,
|
||||
'totalScore': _totalScore,
|
||||
'bullets': _mappedBullets.map((b) => {'x': b.dx, 'y': b.dy}).toList(),
|
||||
};
|
||||
|
||||
// Write file
|
||||
final jsonFile = File(filePath);
|
||||
await jsonFile.writeAsString(jsonEncode(targetData));
|
||||
|
||||
// Close preview and signal refresh
|
||||
Navigator.pop(context, true);
|
||||
} catch (e) {
|
||||
print("Error saving target data: $e");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<String?> _showSaveOptionsDialog() async {
|
||||
TextEditingController _controller = TextEditingController();
|
||||
bool isNewFolderEnabled = false;
|
||||
bool isTournament = false; // 👈 new flag
|
||||
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final saveDirectory = Directory('${directory.path}/saved_images');
|
||||
|
||||
final List<String> existingFolders = [];
|
||||
final folders = saveDirectory.listSync();
|
||||
for (var folder in folders) {
|
||||
if (folder is Directory) {
|
||||
existingFolders.add(folder.path.split('/').last);
|
||||
}
|
||||
}
|
||||
|
||||
String? lastFolderName = existingFolders.isNotEmpty ? existingFolders.last : null;
|
||||
|
||||
return await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
'Save Target Data',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
|
||||
const Text(
|
||||
"Select where you'd like to save the data:",
|
||||
style: TextStyle(color: Colors.white70),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
if (lastFolderName != null) ...[
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, lastFolderName);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _bulletColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text("Save to '$lastFolderName'"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// New folder name input
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[800],
|
||||
hintText: "Enter a new folder name",
|
||||
hintStyle: const TextStyle(color: Colors.white54),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _bulletColor),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
isNewFolderEnabled = value.trim().isNotEmpty;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Tournament toggle
|
||||
if (isNewFolderEnabled) ...[
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isTournament = !isTournament;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: isTournament ? _bulletColor.withOpacity(0.2) : Colors.grey[850],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isTournament ? _bulletColor : Colors.grey[700]!,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Center(
|
||||
child: Text(
|
||||
isTournament ? 'Tournament Mode 🏆' : 'Mark as Tournament 🏆',
|
||||
style: TextStyle(
|
||||
color: isTournament ? _bulletColor : Colors.white70,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Create new folder
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: isNewFolderEnabled
|
||||
? () => Navigator.pop(
|
||||
context,
|
||||
jsonEncode({
|
||||
'folder': _controller.text.trim(),
|
||||
'isTournament': isTournament,
|
||||
}),
|
||||
)
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
backgroundColor:
|
||||
isNewFolderEnabled ? _bulletColor : Colors.grey[700],
|
||||
foregroundColor: Colors.white,
|
||||
disabledBackgroundColor: Colors.grey[700],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Create New Folder"),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic>? _getRingCenterSize(List<DetectionResult> results) {
|
||||
try {
|
||||
final ring = results.firstWhere((r) => r.classId == 2);
|
||||
final cx = (ring.x1 + ring.x2) / 2;
|
||||
final cy = (ring.y1 + ring.y2) / 2;
|
||||
final width = ring.x2 - ring.x1;
|
||||
final height = ring.y2 - ring.y1;
|
||||
return {
|
||||
'center': Offset(cx, cy),
|
||||
'size': Size(width, height),
|
||||
};
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Offset _mapToTargetNormalized(
|
||||
Offset bulletCam,
|
||||
Offset ringCam,
|
||||
Offset ringTarget,
|
||||
Size sizeCam,
|
||||
Size sizeTarget) {
|
||||
final normX = (bulletCam.dx - ringCam.dx) / sizeCam.width;
|
||||
final normY = (bulletCam.dy - ringCam.dy) / sizeCam.height;
|
||||
final mappedX = normX * sizeTarget.width + ringTarget.dx;
|
||||
final mappedY = normY * sizeTarget.height + ringTarget.dy;
|
||||
return Offset(mappedX, mappedY);
|
||||
}
|
||||
|
||||
int _scoreForBullet(Offset bullet, Offset centerOverride) {
|
||||
const double bulletRadius = 25;
|
||||
final double distanceToEdge = (bullet - centerOverride).distance - bulletRadius;
|
||||
|
||||
final List<double> rings = [14, 70, 125, 180, 235, 290, 345, 400, 455, 510];
|
||||
|
||||
for (int i = 0; i < rings.length; i++) {
|
||||
if (distanceToEdge <= rings[i]) {
|
||||
return 10 - i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get _totalScore {
|
||||
final autoScore = _mappedBullets
|
||||
.take(widget.bulletCount - _manualBulletScores.length)
|
||||
.map((b) => _scoreForBullet(b, _targetImageCenterForScoring ?? fixedRingTargetCenter))
|
||||
.fold(0, (sum, score) => sum + score);
|
||||
|
||||
final manualScore = _manualBulletScores.fold(0, (a, b) => a + b);
|
||||
|
||||
return autoScore + manualScore;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.grey[900],
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
title: const Text(
|
||||
'Detection Preview',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_mode == PreviewMode.mappedTarget ? Icons.camera_alt : Icons.center_focus_strong,
|
||||
),
|
||||
color: Colors.white,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_mode = _mode == PreviewMode.mappedTarget
|
||||
? PreviewMode.cameraWithDetections
|
||||
: PreviewMode.mappedTarget;
|
||||
});
|
||||
},
|
||||
tooltip: _mode == PreviewMode.mappedTarget
|
||||
? "Show Camera Detections"
|
||||
: "Show Mapped Target",
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
color: Colors.white,
|
||||
onPressed: _saveTargetData,
|
||||
tooltip: "Save Target Data",
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final screenWidth = constraints.maxWidth;
|
||||
final screenHeight = constraints.maxHeight;
|
||||
|
||||
final displayImage = _mode == PreviewMode.mappedTarget
|
||||
? _cachedTargetImage
|
||||
: _cachedRealImage;
|
||||
|
||||
final imgData = _mode == PreviewMode.mappedTarget
|
||||
? _targetImageDecoded
|
||||
: _realImageDecoded;
|
||||
|
||||
if (displayImage == null || imgData == null) {
|
||||
return const Center(child: Text("Image not available"));
|
||||
}
|
||||
|
||||
final imgWidth = imgData.width.toDouble();
|
||||
final imgHeight = imgData.height.toDouble();
|
||||
final aspectRatio = imgWidth / imgHeight;
|
||||
final screenAspect = screenWidth / screenHeight;
|
||||
|
||||
double displayWidth, displayHeight, offsetX, offsetY;
|
||||
|
||||
if (aspectRatio > screenAspect) {
|
||||
displayWidth = screenWidth;
|
||||
displayHeight = screenWidth / aspectRatio;
|
||||
offsetX = 0;
|
||||
offsetY = (screenHeight - displayHeight) / 2;
|
||||
} else {
|
||||
displayHeight = screenHeight;
|
||||
displayWidth = screenHeight * aspectRatio;
|
||||
offsetY = 0;
|
||||
offsetX = (screenWidth - displayWidth) / 2;
|
||||
}
|
||||
|
||||
final scaleX = displayWidth / imgWidth;
|
||||
final scaleY = displayHeight / imgHeight;
|
||||
|
||||
final imageWidget = Image.memory(displayImage, fit: BoxFit.fill);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: offsetX,
|
||||
top: offsetY,
|
||||
width: displayWidth,
|
||||
height: displayHeight,
|
||||
child: imageWidget,
|
||||
),
|
||||
if (_mode == PreviewMode.cameraWithDetections)
|
||||
..._realImageDetections.map((d) {
|
||||
final left = d.x1 * scaleX + offsetX;
|
||||
final top = d.y1 * scaleY + offsetY;
|
||||
final width = (d.x2 - d.x1) * scaleX;
|
||||
final height = (d.y2 - d.y1) * scaleY;
|
||||
return Positioned(
|
||||
left: left,
|
||||
top: top,
|
||||
width: width,
|
||||
height: height,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.green, width: 2),
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
color: Colors.green.withOpacity(0.5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
child: Text(
|
||||
"ID:${d.classId} ${(d.confidence * 100).toStringAsFixed(1)}%",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
if (_mode == PreviewMode.mappedTarget)
|
||||
..._mappedBullets.asMap().entries.take(widget.bulletCount).map((entry) {
|
||||
final index = entry.key;
|
||||
final b = entry.value;
|
||||
final dx = b.dx * scaleX + offsetX;
|
||||
final dy = b.dy * scaleY + offsetY;
|
||||
final double bulletSize = ((scaleX + scaleY) / 2) * 55;
|
||||
|
||||
final isHighlighted = index == _highlightedBulletIndex;
|
||||
final isManual = index >= (_mappedBullets.length - _manualBulletScores.length);
|
||||
|
||||
return Positioned(
|
||||
left: dx - bulletSize / 2,
|
||||
top: dy - bulletSize / 2,
|
||||
child: Container(
|
||||
width: bulletSize,
|
||||
height: bulletSize,
|
||||
decoration: BoxDecoration(
|
||||
color: isManual
|
||||
? Colors.red.withOpacity(0.9)
|
||||
: _bulletColorWithOpacity,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isHighlighted ? Colors.red : Colors.black,
|
||||
width: isHighlighted ? 3 : 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (_mappedBullets.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
children: List.generate(
|
||||
_mappedBullets.length,
|
||||
(index) {
|
||||
final bullet = _mappedBullets[index];
|
||||
final isManual = index >= (_mappedBullets.length - _manualBulletScores.length);
|
||||
final score = isManual
|
||||
? _manualBulletScores[index - (_mappedBullets.length - _manualBulletScores.length)]
|
||||
: _scoreForBullet(
|
||||
bullet,
|
||||
_targetImageCenterForScoring ?? fixedRingTargetCenter,
|
||||
);
|
||||
final isSelected = _highlightedBulletIndex == index;
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _highlightedBulletIndex = index),
|
||||
onTapUp: (_) => setState(() => _highlightedBulletIndex = null),
|
||||
onTapCancel: () => setState(() => _highlightedBulletIndex = null),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 6),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${index + 1}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Colors.red[400]
|
||||
: (isManual
|
||||
? Colors.red[600]
|
||||
: _bulletColor),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'$score',
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
if (_hasMissingBullets)
|
||||
Positioned(
|
||||
top: 12,
|
||||
right: 12,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _handleMissingBullets,
|
||||
icon: const Icon(Icons.warning_amber_rounded, color: Colors.white, size: 20),
|
||||
label: const Text("Fix Bullets", style: TextStyle(color: Colors.white)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red[600],
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
color: Colors.grey[900],
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"🎯 Total Score: $_totalScore",
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../widgets/input_field.dart';
|
||||
import 'login_screen.dart';
|
||||
|
||||
class RegisterScreen extends StatefulWidget {
|
||||
const RegisterScreen({super.key});
|
||||
|
||||
@override
|
||||
State<RegisterScreen> createState() => _RegisterScreenState();
|
||||
}
|
||||
|
||||
class _RegisterScreenState extends State<RegisterScreen> {
|
||||
final usernameController = TextEditingController();
|
||||
final emailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final confirmPasswordController = TextEditingController();
|
||||
|
||||
Future<void> register() async {
|
||||
String username = usernameController.text.trim();
|
||||
String email = emailController.text.trim();
|
||||
String password = passwordController.text.trim();
|
||||
String confirmPassword = confirmPasswordController.text.trim();
|
||||
|
||||
if (username.isEmpty || email.isEmpty || password.isEmpty || confirmPassword.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('All fields are required')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(email)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Invalid email format')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password != confirmPassword) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Passwords do not match')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Check if the username or email is already taken
|
||||
String? existingUser = prefs.getString(username);
|
||||
String? existingEmail = prefs.getString(email);
|
||||
if (existingUser != null || existingEmail != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Username or Email already exists. Choose another.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save user credentials in SharedPreferences
|
||||
await prefs.setString(username, password);
|
||||
await prefs.setString(email, username); // Store email linked to the username
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Registration Successful! Please Login')),
|
||||
);
|
||||
|
||||
// Redirect to Login Screen
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const LoginScreen()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
/// Logo
|
||||
Image.asset(
|
||||
'assets/logo.png',
|
||||
width: 1000,
|
||||
height: 150,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
/// Username Field
|
||||
InputField(
|
||||
hintText: 'Username',
|
||||
icon: Icons.person,
|
||||
controller: usernameController,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Email Field
|
||||
InputField(
|
||||
hintText: 'Email',
|
||||
icon: Icons.email,
|
||||
controller: emailController,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Password Field
|
||||
InputField(
|
||||
hintText: 'Password',
|
||||
icon: Icons.lock,
|
||||
obscureText: true,
|
||||
controller: passwordController,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Confirm Password Field
|
||||
InputField(
|
||||
hintText: 'Confirm Password',
|
||||
icon: Icons.lock,
|
||||
obscureText: true,
|
||||
controller: confirmPasswordController,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
/// Register Button
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800], // Match Login Button
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
onPressed: register,
|
||||
child: const Text('Register', style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Horizontal Line with "or" (optional, in case Google Sign-Up is added)
|
||||
Row(
|
||||
children: const [
|
||||
Expanded(
|
||||
child: Divider(color: Colors.grey, thickness: 1),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text("or", style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
),
|
||||
Expanded(
|
||||
child: Divider(color: Colors.grey, thickness: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// Login Navigation (Styled in Blue)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const LoginScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
"Already have an account? Login",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey, // Styled for better visibility
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
List<String> _cameraIPs = List.generate(6, (index) => 'Not Set');
|
||||
|
||||
final List<String> _defaultIPs = [
|
||||
'192.168.0.134:9081',
|
||||
'192.168.0.134:9082',
|
||||
'192.168.0.134:9083',
|
||||
'192.168.0.134:9084',
|
||||
'192.168.0.134:9085',
|
||||
'192.168.0.134:9086',
|
||||
];
|
||||
|
||||
|
||||
String _selectedBulletColor = 'Blue';
|
||||
final List<String> _bulletColors = [
|
||||
'Blue',
|
||||
'Green',
|
||||
'Yellow',
|
||||
'Purple',
|
||||
'Cyan',
|
||||
'Teal',
|
||||
'Indigo',
|
||||
'Pink',
|
||||
'Lime',
|
||||
'Mint',
|
||||
'Sky',
|
||||
'Violet',
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadCameraIPs();
|
||||
_loadBulletColor();
|
||||
_loadBulletColor();
|
||||
}
|
||||
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'sky':
|
||||
return Colors.orange;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
default:
|
||||
return Colors.blue; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadBulletColor() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String savedColor = prefs.getString('bulletColor') ?? 'Blue';
|
||||
|
||||
// Fallback in case old color is no longer valid
|
||||
if (!_bulletColors.contains(savedColor)) {
|
||||
savedColor = 'Blue';
|
||||
await prefs.setString('bulletColor', savedColor);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_selectedBulletColor = savedColor;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveBulletColor(String color) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('bulletColor', color);
|
||||
setState(() => _selectedBulletColor = color);
|
||||
}
|
||||
|
||||
|
||||
Future<void> _loadCameraIPs() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
List<String> tempIPs = [];
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
String key = 'cameraIP_${i + 1}';
|
||||
String? savedIP = prefs.getString(key);
|
||||
|
||||
// If not saved, use default and write it to prefs
|
||||
if (savedIP == null) {
|
||||
await prefs.setString(key, _defaultIPs[i]);
|
||||
tempIPs.add(_defaultIPs[i]);
|
||||
} else {
|
||||
tempIPs.add(savedIP);
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_cameraIPs = tempIPs;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveCameraIP(int index, String ip) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('cameraIP_${index + 1}', ip);
|
||||
_loadCameraIPs();
|
||||
}
|
||||
|
||||
Future<void> _clearCameraIP(int index) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove('cameraIP_${index + 1}');
|
||||
_loadCameraIPs();
|
||||
}
|
||||
|
||||
void _editCameraIP(int index) {
|
||||
TextEditingController controller = TextEditingController(
|
||||
text: _cameraIPs[index],
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Camera ${index + 1}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[800],
|
||||
hintText: "Enter Camera IP",
|
||||
hintStyle: const TextStyle(color: Colors.white54),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _getColorFromName(_selectedBulletColor)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Save Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
String newIP = controller.text.trim();
|
||||
if (newIP.isNotEmpty) {
|
||||
await _saveCameraIP(index, newIP);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _getColorFromName(_selectedBulletColor),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Save"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Reset Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await _clearCameraIP(index);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[700],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Reset to Default"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Cancel Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
_buildBulletColorSelector(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildCameraList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBulletColorSelector() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Select Bullet Color",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: _showBulletColorPopup,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: _getColorFromName(_selectedBulletColor),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black26),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_selectedBulletColor,
|
||||
style: const TextStyle(color: Colors.black87, fontSize: 14),
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right, color: Colors.black54),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showBulletColorPopup() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
"Select Bullet Color",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: _bulletColors.map((colorName) {
|
||||
final color = _getColorFromName(colorName);
|
||||
final isSelected = colorName == _selectedBulletColor;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await _saveBulletColor(colorName);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.white : Colors.white24,
|
||||
width: isSelected ? 3 : 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text("Cancel", style: TextStyle(color: Colors.white70)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCameraList() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Camera Settings",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...List.generate(6, (index) {
|
||||
return GestureDetector(
|
||||
onTap: () => _editCameraIP(index),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Camera ${index + 1}",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_cameraIPs[index],
|
||||
style: const TextStyle(color: Colors.black54),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Icon(Icons.chevron_right, color: Colors.black45),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:screenshot/screenshot.dart';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mjpeg/flutter_mjpeg.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'yolo_detector.dart';
|
||||
import 'preview_screen.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
enum ImageFilterType { none, greyscale, invert}
|
||||
|
||||
class CameraStreamScreen extends StatefulWidget {
|
||||
final int cameraIndex;
|
||||
|
||||
const CameraStreamScreen({super.key, required this.cameraIndex});
|
||||
|
||||
@override
|
||||
State<CameraStreamScreen> createState() => _CameraStreamScreenState();
|
||||
}
|
||||
|
||||
class _CameraStreamScreenState extends State<CameraStreamScreen> with SingleTickerProviderStateMixin {
|
||||
String? _streamUrl;
|
||||
bool _isError = false;
|
||||
bool _isCapturing = false;
|
||||
final ScreenshotController _screenshotController = ScreenshotController();
|
||||
final TransformationController _transformationController = TransformationController();
|
||||
ImageFilterType _selectedFilter = ImageFilterType.none;
|
||||
|
||||
|
||||
late YOLODetector _yoloDetector;
|
||||
int _numberOfBullets = 5;
|
||||
|
||||
late AnimationController _zoomAnimController;
|
||||
Animation<Matrix4>? _zoomAnimation;
|
||||
Matrix4? _originalMatrix;
|
||||
|
||||
Color _bulletColor = Colors.blue; // default/fallback bullet color
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadStreamUrl();
|
||||
_loadBulletCount();
|
||||
_loadSelectedFilter();
|
||||
_loadBulletColor(); // 👈 add this to actually load the saved bullet color
|
||||
|
||||
|
||||
YOLODetector.create().then((detector) => _yoloDetector = detector);
|
||||
|
||||
_setSystemBarStyle(); // 👈 set colors
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // 👈 show bars
|
||||
|
||||
_keepScreenAwake();
|
||||
|
||||
_zoomAnimController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadBulletColor() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selected = prefs.getString('bulletColor') ?? 'Blue';
|
||||
setState(() {
|
||||
_bulletColor = _getColorFromName(selected);
|
||||
});
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'sky':
|
||||
return Colors.orange;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
default:
|
||||
return Colors.blue; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
void _setSystemBarStyle() {
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.grey[900], // ✅ match your AppBar
|
||||
statusBarIconBrightness: Brightness.light, // white icons
|
||||
systemNavigationBarColor: Colors.grey[900],
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadSelectedFilter() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final index = prefs.getInt('selectedFilter');
|
||||
if (index != null && index >= 0 && index < ImageFilterType.values.length) {
|
||||
setState(() {
|
||||
_selectedFilter = ImageFilterType.values[index];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _keepScreenAwake() async {
|
||||
try {
|
||||
await WakelockPlus.enable();
|
||||
} catch (e) {
|
||||
debugPrint("⚠️ Could not keep screen awake: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadStreamUrl() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String? url = prefs.getString('cameraIP_${widget.cameraIndex}');
|
||||
|
||||
setState(() {
|
||||
_streamUrl = (url != null && url.trim().isNotEmpty)
|
||||
? (url.startsWith(RegExp(r"https?://")) ? url : "http://$url")
|
||||
: null;
|
||||
_isError = _streamUrl == null;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadBulletCount() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_numberOfBullets = prefs.getInt('bulletCount') ?? 5;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _captureAndPreviewImage() async {
|
||||
if (_streamUrl == null || _isCapturing) return;
|
||||
|
||||
setState(() => _isCapturing = true);
|
||||
_originalMatrix = _transformationController.value;
|
||||
|
||||
try {
|
||||
final targetMatrix = Matrix4.identity()..scale(1.0);
|
||||
final isZoomedIn = !_matrixEquals(_originalMatrix!, targetMatrix);
|
||||
|
||||
if (isZoomedIn) {
|
||||
_zoomAnimController.reset();
|
||||
_zoomAnimation = Matrix4Tween(
|
||||
begin: _originalMatrix,
|
||||
end: targetMatrix,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _zoomAnimController,
|
||||
curve: Curves.easeOut,
|
||||
));
|
||||
|
||||
void updateZoom() {
|
||||
_transformationController.value = _zoomAnimation!.value;
|
||||
}
|
||||
|
||||
_zoomAnimController.addListener(updateZoom);
|
||||
await _zoomAnimController.forward();
|
||||
_zoomAnimController.removeListener(updateZoom);
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
final imageBytes = await _screenshotController.capture();
|
||||
if (imageBytes == null || imageBytes.isEmpty) return;
|
||||
|
||||
final decoded = img.decodeImage(imageBytes);
|
||||
|
||||
if (decoded == null) return;
|
||||
final width = decoded.width;
|
||||
final height = decoded.height;
|
||||
final size = width < height ? width : height;
|
||||
final offsetX = (width - size) ~/ 2;
|
||||
final offsetY = (height - size) ~/ 2;
|
||||
final cropped = img.copyCrop(decoded, x: offsetX, y: offsetY, width: size, height: size);
|
||||
|
||||
// Debugging: log the selected filter
|
||||
debugPrint("📸 Selected filter: $_selectedFilter");
|
||||
|
||||
// Create the output directory and path
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = '${dir.path}/ip_capture_temp.jpg';
|
||||
img.Image processed = cropped;
|
||||
|
||||
// Apply the selected filter
|
||||
switch (_selectedFilter) {
|
||||
case ImageFilterType.greyscale:
|
||||
processed = img.grayscale(cropped);
|
||||
break;
|
||||
|
||||
case ImageFilterType.invert:
|
||||
processed = invertImage(cropped); // ✅ use your custom invert function
|
||||
break;
|
||||
case ImageFilterType.none:
|
||||
break;
|
||||
}
|
||||
|
||||
// Save the processed image to a file
|
||||
final file = File(path)..writeAsBytesSync(img.encodeJpg(processed));
|
||||
|
||||
// Detect objects in the image using YOLO
|
||||
final detections = await _yoloDetector.detect(file);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// Navigate to the preview screen
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PreviewScreen(
|
||||
imagePath: file.path,
|
||||
detections: detections,
|
||||
bulletCount: _numberOfBullets,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint("❌ Capture error: $e");
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Capture failed: $e")),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_zoomAnimController.reset();
|
||||
_zoomAnimation = Matrix4Tween(
|
||||
begin: _transformationController.value,
|
||||
end: _originalMatrix!,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _zoomAnimController,
|
||||
curve: Curves.easeIn,
|
||||
));
|
||||
|
||||
void restoreZoom() {
|
||||
_transformationController.value = _zoomAnimation!.value;
|
||||
}
|
||||
|
||||
_zoomAnimController.addListener(restoreZoom);
|
||||
await _zoomAnimController.forward();
|
||||
_zoomAnimController.removeListener(restoreZoom);
|
||||
|
||||
setState(() => _isCapturing = false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
img.Image invertImage(img.Image source) {
|
||||
final result = img.Image.from(source);
|
||||
for (int y = 0; y < result.height; y++) {
|
||||
for (int x = 0; x < result.width; x++) {
|
||||
final pixel = result.getPixel(x, y);
|
||||
result.setPixelRgb(x, y, 255 - pixel.r, 255 - pixel.g, 255 - pixel.b);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _matrixEquals(Matrix4 a, Matrix4 b) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if ((a.storage[i] - b.storage[i]).abs() > 0.001) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _showBulletDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0), // Increased padding for better spacing
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
'Select the number of bullets',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: List.generate(5, (index) {
|
||||
final bulletNum = index + 1;
|
||||
final isSelected = _numberOfBullets == bulletNum;
|
||||
return ElevatedButton(
|
||||
onPressed: () {
|
||||
_selectBulletCount(bulletNum);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
isSelected ? _bulletColor : Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'$bulletNum',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showFilterDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Choose a Filter',
|
||||
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
alignment: WrapAlignment.center,
|
||||
children: ImageFilterType.values.map((f) {
|
||||
final isSelected = _selectedFilter == f;
|
||||
final label = f.toString().split('.').last;
|
||||
return ElevatedButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_selectedFilter = f;
|
||||
});
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('selectedFilter', f.index); // Save filter index
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isSelected ? _bulletColor : Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text(label[0].toUpperCase() + label.substring(1)),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _selectBulletCount(int number) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_numberOfBullets = number;
|
||||
});
|
||||
await prefs.setInt('bulletCount', number);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_zoomAnimController.dispose();
|
||||
_yoloDetector.close();
|
||||
_restoreScreenSettings();
|
||||
|
||||
_setSystemBarStyle(); // restore grey[900] on back
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
Future<void> _restoreScreenSettings() async {
|
||||
try {
|
||||
await WakelockPlus.disable();
|
||||
} catch (e) {
|
||||
debugPrint("⚠️ Could not restore screen settings: $e");
|
||||
}
|
||||
}
|
||||
|
||||
ColorFilter _buildColorFilter() {
|
||||
switch (_selectedFilter) {
|
||||
case ImageFilterType.greyscale:
|
||||
return const ColorFilter.matrix([
|
||||
0.2126, 0.7152, 0.0722, 0, 0,
|
||||
0.2126, 0.7152, 0.0722, 0, 0,
|
||||
0.2126, 0.7152, 0.0722, 0, 0,
|
||||
0, 0, 0, 1, 0,
|
||||
]);
|
||||
case ImageFilterType.invert:
|
||||
return const ColorFilter.matrix([
|
||||
-1, 0, 0, 0, 255,
|
||||
0, -1, 0, 0, 255,
|
||||
0, 0, -1, 0, 255,
|
||||
0, 0, 0, 1, 0,
|
||||
]);
|
||||
default:
|
||||
return const ColorFilter.mode(Colors.transparent, BlendMode.dst);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Target ${widget.cameraIndex}"),
|
||||
backgroundColor: Colors.grey[900],
|
||||
foregroundColor: Colors.white,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Text(
|
||||
'$_numberOfBullets',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: _showBulletDialog,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.filter_alt, color: Colors.white),
|
||||
onPressed: _showFilterDialog,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
children: [
|
||||
Screenshot(
|
||||
controller: _screenshotController,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Center(
|
||||
child: InteractiveViewer(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
constrained: false,
|
||||
boundaryMargin: EdgeInsets.zero,
|
||||
minScale: 1.0,
|
||||
maxScale: 5.0,
|
||||
transformationController: _transformationController,
|
||||
child: SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
child: (_isError || _streamUrl == null)
|
||||
? _buildErrorContent()
|
||||
: RepaintBoundary(
|
||||
child: ColorFiltered(
|
||||
colorFilter: _buildColorFilter(),
|
||||
child: Mjpeg(
|
||||
stream: _streamUrl!,
|
||||
isLive: true,
|
||||
fit: BoxFit.contain,
|
||||
error: (context, error, stackTrace) => _buildErrorContent(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
if (_isCapturing)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.4),
|
||||
child: const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(color: Colors.white),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
"Processing image...",
|
||||
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||
floatingActionButton: _isError
|
||||
? null
|
||||
: FloatingActionButton(
|
||||
backgroundColor: Colors.white,
|
||||
onPressed: _captureAndPreviewImage,
|
||||
child: const Icon(Icons.camera_alt, color: Colors.black),
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
color: Colors.grey[900],
|
||||
shape: const CircularNotchedRectangle(),
|
||||
notchMargin: 8.0,
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorContent() {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.red, size: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
"Unable to load camera stream",
|
||||
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: _loadStreamUrl,
|
||||
child: const Text("Retry"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'stream_screen.dart'; // Import the camera stream screen
|
||||
|
||||
class TargetScreen extends StatefulWidget {
|
||||
const TargetScreen({super.key});
|
||||
|
||||
@override
|
||||
State<TargetScreen> createState() => _TargetScreenState();
|
||||
}
|
||||
|
||||
class _TargetScreenState extends State<TargetScreen> {
|
||||
List<bool> _cameraButtonsEnabled = List.generate(6, (_) => false); // Initially, all buttons are disabled
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadCameraData();
|
||||
}
|
||||
|
||||
/// Load the camera IPs from SharedPreferences and update button status
|
||||
Future<void> _loadCameraData() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final List<String> defaultIPs = [
|
||||
'192.168.0.134:9081',
|
||||
'192.168.0.134:9082',
|
||||
'192.168.0.134:9083',
|
||||
'192.168.0.134:9084',
|
||||
'192.168.0.134:9085',
|
||||
'192.168.0.134:9086',
|
||||
];
|
||||
|
||||
List<bool> cameraDataStatus = List.generate(6, (index) {
|
||||
String key = 'cameraIP_${index + 1}';
|
||||
|
||||
if (!prefs.containsKey(key)) {
|
||||
prefs.setString(key, defaultIPs[index]);
|
||||
return true; // Default set
|
||||
}
|
||||
|
||||
String? ip = prefs.getString(key);
|
||||
return ip != null && ip.isNotEmpty && ip != 'Not Set';
|
||||
});
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_cameraButtonsEnabled = cameraDataStatus;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
itemCount: 6,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16,
|
||||
childAspectRatio: 1, // square-ish buttons
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final isEnabled = _cameraButtonsEnabled[index];
|
||||
return InkWell(
|
||||
onTap: isEnabled
|
||||
? () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CameraStreamScreen(cameraIndex: index + 1),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isEnabled ? Colors.grey[900] : Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.videocam_rounded,
|
||||
color: isEnabled ? Colors.white : Colors.grey[500],
|
||||
size: 36,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"Target ${index + 1}",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isEnabled ? Colors.white : Colors.grey[600],
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'dart:convert';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class TargetViewScreen extends StatefulWidget {
|
||||
final List<File> jsonFiles;
|
||||
final Function refreshFolders;
|
||||
final bool isCombinedView;
|
||||
|
||||
const TargetViewScreen({
|
||||
super.key,
|
||||
required this.jsonFiles,
|
||||
required this.refreshFolders,
|
||||
this.isCombinedView = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TargetViewScreen> createState() => _TargetViewScreenState();
|
||||
}
|
||||
|
||||
class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
int _currentIndex = 0;
|
||||
List<Offset> _bullets = [];
|
||||
int _totalScore = 0;
|
||||
Offset? _targetCenter;
|
||||
Uint8List? _targetImageBytes;
|
||||
img.Image? _targetImageDecoded;
|
||||
String _date = '';
|
||||
int? _highlightedBulletIndex;
|
||||
int? _pressedScoreGroup;
|
||||
late String _targetFilePath;
|
||||
|
||||
final String targetAssetPath = 'assets/target.png';
|
||||
final double bulletSize = 19;
|
||||
|
||||
Color _bulletColor = Colors.blue; // default/fallback bullet color
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBulletColor(); // 👈 this will load bullet color
|
||||
_loadJsonAndImage();
|
||||
}
|
||||
|
||||
Future<void> _loadBulletColor() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
|
||||
|
||||
setState(() {
|
||||
_bulletColor = _getColorFromName(selectedColor);
|
||||
});
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'sky':
|
||||
return Colors.orange;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
default:
|
||||
return Colors.blue; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _loadJsonAndImage() async {
|
||||
try {
|
||||
final byteData = await rootBundle.load(targetAssetPath);
|
||||
final bytes = byteData.buffer.asUint8List();
|
||||
_targetImageDecoded = img.decodeImage(bytes);
|
||||
|
||||
if (_targetImageDecoded == null) {
|
||||
print("Failed to decode image.");
|
||||
return;
|
||||
}
|
||||
|
||||
_targetImageBytes = Uint8List.fromList(img.encodePng(_targetImageDecoded!));
|
||||
_targetCenter = Offset(
|
||||
_targetImageDecoded!.width / 2,
|
||||
_targetImageDecoded!.height / 2 - 1,
|
||||
);
|
||||
|
||||
final currentFile = widget.jsonFiles[_currentIndex];
|
||||
final jsonString = await currentFile.readAsString();
|
||||
final data = jsonDecode(jsonString);
|
||||
|
||||
_date = data['date'] ?? 'Unknown Date';
|
||||
final bulletsJson = data['bullets'] as List;
|
||||
_bullets = bulletsJson.map((b) => Offset(b['x'], b['y'])).toList();
|
||||
|
||||
_targetFilePath = currentFile.path;
|
||||
|
||||
_totalScore = _bullets
|
||||
.map((b) => _scoreForBullet(b, _targetCenter!))
|
||||
.fold(0, (sum, score) => sum + score);
|
||||
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
print("Error loading JSON or image: $e");
|
||||
}
|
||||
}
|
||||
|
||||
int _scoreForBullet(Offset bullet, Offset center) {
|
||||
const bulletRadius = 25;
|
||||
final distance = (bullet - center).distance - bulletRadius;
|
||||
final rings = [14, 70, 125, 180, 235, 290, 345, 400, 455, 510];
|
||||
for (int i = 0; i < rings.length; i++) {
|
||||
if (distance <= rings[i]) return 10 - i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<void> _deleteTarget() async {
|
||||
try {
|
||||
final file = File(_targetFilePath);
|
||||
await file.delete();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Target deleted')),
|
||||
);
|
||||
widget.refreshFolders();
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
print("Error deleting target: $e");
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Failed to delete target')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text('📅 $_date', style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: Colors.grey[900],
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
color: Colors.red,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Delete Target',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"Are you sure you want to delete this target?",
|
||||
style: TextStyle(color: Colors.white70, fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// "Yes" Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
_deleteTarget(); // call your delete function
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.redAccent,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Yes, delete"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// "Cancel" Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _targetImageDecoded == null
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final imgWidth = _targetImageDecoded!.width.toDouble();
|
||||
final imgHeight = _targetImageDecoded!.height.toDouble();
|
||||
final screenWidth = constraints.maxWidth;
|
||||
final screenHeight = constraints.maxHeight;
|
||||
|
||||
final aspectRatio = imgWidth / imgHeight;
|
||||
final screenAspect = screenWidth / screenHeight;
|
||||
|
||||
double displayWidth, displayHeight, offsetX, offsetY;
|
||||
|
||||
if (aspectRatio > screenAspect) {
|
||||
displayWidth = screenWidth;
|
||||
displayHeight = screenWidth / aspectRatio;
|
||||
offsetX = 0;
|
||||
offsetY = (screenHeight - displayHeight) / 2;
|
||||
} else {
|
||||
displayHeight = screenHeight;
|
||||
displayWidth = screenHeight * aspectRatio;
|
||||
offsetY = 0;
|
||||
offsetX = (screenWidth - displayWidth) / 2;
|
||||
}
|
||||
|
||||
final scaleX = displayWidth / imgWidth;
|
||||
final scaleY = displayHeight / imgHeight;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: offsetX,
|
||||
top: offsetY,
|
||||
width: displayWidth,
|
||||
height: displayHeight,
|
||||
child: Image.memory(_targetImageBytes!, fit: BoxFit.fill),
|
||||
),
|
||||
..._bullets.asMap().entries.map((entry) {
|
||||
final i = entry.key;
|
||||
final bullet = entry.value;
|
||||
final dx = bullet.dx * scaleX + offsetX;
|
||||
final dy = bullet.dy * scaleY + offsetY;
|
||||
final score = _scoreForBullet(bullet, _targetCenter!);
|
||||
final isHighlighted = widget.isCombinedView
|
||||
? (_pressedScoreGroup == score)
|
||||
: (i == _highlightedBulletIndex);
|
||||
|
||||
return Positioned(
|
||||
left: dx - bulletSize / 2,
|
||||
top: dy - bulletSize / 2,
|
||||
child: Container(
|
||||
width: bulletSize,
|
||||
height: bulletSize,
|
||||
decoration: BoxDecoration(
|
||||
color: _bulletColor.withOpacity(0.8),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isHighlighted ? Colors.red : Colors.black,
|
||||
width: isHighlighted ? 3 : 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
children: widget.isCombinedView
|
||||
? _buildGroupedScoreButtons()
|
||||
: _buildIndividualScoreButtons(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
color: Colors.grey[900],
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"🎯 Total Score: $_totalScore",
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildGroupedScoreButtons() {
|
||||
final Map<int, int> scoreGroups = {};
|
||||
for (final bullet in _bullets) {
|
||||
final score = _scoreForBullet(bullet, _targetCenter!);
|
||||
scoreGroups[score] = (scoreGroups[score] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return scoreGroups.entries.map((entry) {
|
||||
final score = entry.key;
|
||||
final count = entry.value;
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _pressedScoreGroup = score),
|
||||
onTapUp: (_) => setState(() => _pressedScoreGroup = null),
|
||||
onTapCancel: () => setState(() => _pressedScoreGroup = null),
|
||||
child: Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
offset: const Offset(0, 2),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'${count}x',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _pressedScoreGroup == score
|
||||
? Colors.red[400]
|
||||
: _bulletColor.withOpacity(0.85),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'$score',
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
|
||||
List<Widget> _buildIndividualScoreButtons() {
|
||||
return List.generate(_bullets.length, (index) {
|
||||
final bullet = _bullets[index];
|
||||
final score = _scoreForBullet(bullet, _targetCenter!);
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _highlightedBulletIndex = index),
|
||||
onTapUp: (_) => setState(() => _highlightedBulletIndex = null),
|
||||
onTapCancel: () => setState(() => _highlightedBulletIndex = null),
|
||||
child: Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
offset: const Offset(0, 2),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${index + 1}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _highlightedBulletIndex == index
|
||||
? Colors.red[400]
|
||||
: _bulletColor.withOpacity(0.85),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'$score',
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// ======= yolo_detector.dart (updated with imageToInputTensor helper) =======
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:tflite_flutter/tflite_flutter.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
class DetectionResult {
|
||||
final double x1, y1, x2, y2;
|
||||
final double confidence;
|
||||
final int classId;
|
||||
|
||||
DetectionResult(this.x1, this.y1, this.x2, this.y2, this.confidence, this.classId);
|
||||
}
|
||||
|
||||
class CroppedImageResult {
|
||||
final img.Image image;
|
||||
final int offsetX;
|
||||
final int offsetY;
|
||||
final int cropWidth;
|
||||
final int cropHeight;
|
||||
|
||||
CroppedImageResult(this.image, this.offsetX, this.offsetY, this.cropWidth, this.cropHeight);
|
||||
}
|
||||
|
||||
class YOLODetector {
|
||||
late Interpreter _interpreter;
|
||||
late int inputWidth, inputHeight;
|
||||
final double confidenceThreshold = 0.4;
|
||||
|
||||
YOLODetector._create();
|
||||
|
||||
static Future<YOLODetector> create() async {
|
||||
final detector = YOLODetector._create();
|
||||
await detector._loadModel();
|
||||
return detector;
|
||||
}
|
||||
|
||||
Future<void> _loadModel() async {
|
||||
final options = InterpreterOptions()..threads = 2;
|
||||
_interpreter = await Interpreter.fromAsset('assets/best_float32_nms.tflite', options: options);
|
||||
|
||||
final inputTensor = _interpreter.getInputTensor(0);
|
||||
final inputShape = inputTensor.shape;
|
||||
inputHeight = inputShape[1];
|
||||
inputWidth = inputShape[2];
|
||||
|
||||
print("📐 Model input shape: $inputShape");
|
||||
print("📦 Model input type: ${inputTensor.type}");
|
||||
}
|
||||
|
||||
Future<List<DetectionResult>> detect(File imageFile) async {
|
||||
final bytes = await imageFile.readAsBytes();
|
||||
final decodedImage = img.decodeImage(bytes);
|
||||
if (decodedImage == null) throw Exception("Could not decode image.");
|
||||
return detectFromImage(decodedImage);
|
||||
}
|
||||
|
||||
Future<List<DetectionResult>> detectFromImage(img.Image originalImage) async {
|
||||
final croppedResult = cropToModelAspectRatio(originalImage, inputWidth, inputHeight);
|
||||
final fixedImage = img.bakeOrientation(croppedResult.image);
|
||||
final inputTensor = imageToInputTensor(fixedImage, inputWidth, inputHeight);
|
||||
|
||||
final inputBuffer = reshapeFloat(inputTensor.toList(), [1, inputHeight, inputWidth, 3]);
|
||||
|
||||
final outputShape = _interpreter.getOutputTensor(0).shape;
|
||||
final numDetections = outputShape[1];
|
||||
final outputBuffer = List.generate(1, (_) =>
|
||||
List.generate(numDetections, (_) => List.filled(6, 0.0)));
|
||||
|
||||
print("🚀 Running inference...");
|
||||
_interpreter.run(inputBuffer, outputBuffer);
|
||||
|
||||
final rawDetections = outputBuffer[0];
|
||||
final results = <DetectionResult>[];
|
||||
|
||||
for (var detection in rawDetections) {
|
||||
final x1 = detection[0];
|
||||
final y1 = detection[1];
|
||||
final x2 = detection[2];
|
||||
final y2 = detection[3];
|
||||
final score = detection[4];
|
||||
final classId = detection[5].toInt();
|
||||
|
||||
if (score < confidenceThreshold) continue;
|
||||
|
||||
results.add(DetectionResult(
|
||||
x1 * croppedResult.cropWidth + croppedResult.offsetX,
|
||||
y1 * croppedResult.cropHeight + croppedResult.offsetY,
|
||||
x2 * croppedResult.cropWidth + croppedResult.offsetX,
|
||||
y2 * croppedResult.cropHeight + croppedResult.offsetY,
|
||||
score,
|
||||
classId,
|
||||
));
|
||||
}
|
||||
|
||||
print("🎯 Detections found: ${results.length}");
|
||||
return results;
|
||||
}
|
||||
|
||||
void close() {
|
||||
_interpreter.close();
|
||||
}
|
||||
|
||||
List<List<List<List<double>>>> reshapeFloat(List<double> flat, List<int> dims) {
|
||||
final d0 = dims[0], d1 = dims[1], d2 = dims[2], d3 = dims[3];
|
||||
final reshaped = List.generate(d0, (_) =>
|
||||
List.generate(d1, (_) =>
|
||||
List.generate(d2, (_) => List.filled(d3, 0.0))));
|
||||
int index = 0;
|
||||
for (int i = 0; i < d0; i++) {
|
||||
for (int j = 0; j < d1; j++) {
|
||||
for (int k = 0; k < d2; k++) {
|
||||
for (int l = 0; l < d3; l++) {
|
||||
reshaped[i][j][k][l] = flat[index++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reshaped;
|
||||
}
|
||||
|
||||
CroppedImageResult cropToModelAspectRatio(img.Image image, int targetWidth, int targetHeight) {
|
||||
final targetAspectRatio = targetWidth / targetHeight;
|
||||
final originalAspectRatio = image.width / image.height;
|
||||
|
||||
int cropWidth = image.width;
|
||||
int cropHeight = image.height;
|
||||
|
||||
if (originalAspectRatio > targetAspectRatio) {
|
||||
cropWidth = (image.height * targetAspectRatio).toInt();
|
||||
} else {
|
||||
cropHeight = (image.width / targetAspectRatio).toInt();
|
||||
}
|
||||
|
||||
final offsetX = ((image.width - cropWidth) / 2).toInt();
|
||||
final offsetY = ((image.height - cropHeight) / 2).toInt();
|
||||
|
||||
final croppedImage = img.copyCrop(image, x: offsetX, y: offsetY, width: cropWidth, height: cropHeight);
|
||||
|
||||
return CroppedImageResult(croppedImage, offsetX, offsetY, cropWidth, cropHeight);
|
||||
}
|
||||
|
||||
Float32List imageToInputTensor(img.Image inputImage, int width, int height) {
|
||||
final resized = img.copyResize(inputImage, width: width, height: height);
|
||||
final Float32List input = Float32List(width * height * 3);
|
||||
int index = 0;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
final pixel = resized.getPixel(x, y);
|
||||
final r = pixel.r / 255.0;
|
||||
final g = pixel.g / 255.0;
|
||||
final b = pixel.b / 255.0;
|
||||
input[index++] = r;
|
||||
input[index++] = g;
|
||||
input[index++] = b;
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SharedPrefsService {
|
||||
static Future<void> saveCameraIPs(List<String> cameraIPs) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
prefs.setString('camera_$i', cameraIPs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<String>> loadCameraIPs() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return List.generate(6, (index) => prefs.getString('camera_$index') ?? '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomNavBar extends StatelessWidget {
|
||||
final int selectedIndex;
|
||||
final Function(int) onItemTapped;
|
||||
|
||||
const BottomNavBar({
|
||||
super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onItemTapped,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: SizedBox(
|
||||
height: 80,
|
||||
child: BottomNavigationBar(
|
||||
backgroundColor: Colors.grey[900],
|
||||
currentIndex: selectedIndex,
|
||||
onTap: onItemTapped,
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: true,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedFontSize: 12,
|
||||
unselectedFontSize: 12,
|
||||
selectedItemColor: Colors.white,
|
||||
unselectedItemColor: Colors.white70,
|
||||
items: const [
|
||||
BottomNavigationBarItem(icon: Icon(Icons.book), label: "Book"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.my_location), label: "Shooting"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.camera_alt), label: "Camera"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.calendar_today), label: "Calendar"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.settings), label: "Settings"),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class InputField extends StatelessWidget {
|
||||
final String hintText;
|
||||
final IconData icon;
|
||||
final bool obscureText;
|
||||
final TextEditingController controller;
|
||||
|
||||
const InputField({
|
||||
super.key,
|
||||
required this.hintText,
|
||||
required this.icon,
|
||||
this.obscureText = false,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
style: const TextStyle(color: Colors.grey), // Text inside input field is gray
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(icon, color: Colors.grey), // Icons inside input field are gray
|
||||
hintText: hintText,
|
||||
hintStyle: const TextStyle(color: Colors.grey), // Placeholder text is gray
|
||||
filled: true,
|
||||
fillColor: Colors.grey[300], // Light gray background for inputs
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none, // No border
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,930 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.5"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
camera:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: camera
|
||||
sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.6"
|
||||
camera_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_android
|
||||
sha256: "997f19dbdb1bb0e40bdb87265c7e550abea657fe3c4ba3720f81e97b6d4b64dd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.10+1"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_avfoundation
|
||||
sha256: ba48b65a3a97004276ede882e6b838d9667642ff462c95a8bb57ca8a82b6bd25
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.18+11"
|
||||
camera_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_platform_interface
|
||||
sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
camera_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_web
|
||||
sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+2"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4+2"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+4"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_exif_rotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_exif_rotation
|
||||
sha256: f581601523bbb7fbec3a0498fe930bf6c48f627d4548d859b2772230edfbe658
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.2"
|
||||
flutter_hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_hooks
|
||||
sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.21.2"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.3"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_mjpeg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_mjpeg
|
||||
sha256: "46ea3f1a49838deb8e2b01a70b897313c7f985affecfaf3b3bae6f4e15405b6b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.27"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.17"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
google_identity_services_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_identity_services_web
|
||||
sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
google_sign_in:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_sign_in
|
||||
sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
google_sign_in_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_sign_in_android
|
||||
sha256: "4e52c64366bdb3fe758f683b088ee514cc7a95e69c52b5ee9fc5919e1683d21b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.0"
|
||||
google_sign_in_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_sign_in_ios
|
||||
sha256: "29cd125f58f50ceb40e8253d3c0209e321eee3e5df16cd6d262495f7cad6a2bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.8.1"
|
||||
google_sign_in_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_sign_in_platform_interface
|
||||
sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
google_sign_in_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_sign_in_web
|
||||
sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.4+4"
|
||||
http:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+22"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+2"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.1"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
package_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.16"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
photo_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: photo_view
|
||||
sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
screenshot:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: screenshot
|
||||
sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.8"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
syncfusion_flutter_calendar:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: syncfusion_flutter_calendar
|
||||
sha256: "87761c6e73997d8d39181741f9e6244daf29b521aafaccf46b1bf31e86767a3f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "28.2.12"
|
||||
syncfusion_flutter_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_flutter_core
|
||||
sha256: f1d2b52697543e13bdefdc62d15868124a265987577f53224a7dbe176c8448f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "28.2.12"
|
||||
syncfusion_flutter_datepicker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_flutter_datepicker
|
||||
sha256: cfc91ebacee63b2c5220e541736f8df211d4f0bfbf34265778862ae20faf094f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "28.2.12"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
tflite_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tflite_flutter
|
||||
sha256: ffb8651fdb116ab0131d6dc47ff73883e0f634ad1ab12bb2852eef1bbeab4a6a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.4"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.18"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.13"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.16"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
visibility_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: visibility_detector
|
||||
sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0+2"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
wakelock_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wakelock_plus
|
||||
sha256: b90fbcc8d7bdf3b883ea9706d9d76b9978cb1dfa4351fcc8014d6ec31a493354
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.11"
|
||||
wakelock_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_plus_platform_interface
|
||||
sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
webview_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.0"
|
||||
webview_flutter_android:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: e09150b28a07933839adef0e4a088bb43e8c8d9e6b93025b01882d4067a58ab0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.4"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
webview_flutter_wkwebview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: c49a98510080378b1525132f407a92c3dcd3b7145bef04fb8137724aadcf1cf0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.18.4"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.12.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
@@ -0,0 +1,58 @@
|
||||
name: test1
|
||||
description: A new Flutter project.
|
||||
publish_to: 'none' # This prevents publishing to pub.dev
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.8
|
||||
google_sign_in: ^6.1.5
|
||||
shared_preferences: ^2.2.2
|
||||
photo_view: ^0.15.0
|
||||
screenshot: ^3.0.0
|
||||
flutter_mjpeg: ^2.0.0
|
||||
webview_flutter: ^4.4.1
|
||||
webview_flutter_android: ^4.3.3
|
||||
webview_flutter_wkwebview: ^3.7.4
|
||||
intl: any
|
||||
flutter_exif_rotation: any
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
flutter_svg: ^2.0.9
|
||||
collection: ^1.17.2
|
||||
|
||||
tflite_flutter: ^0.10.4
|
||||
image: ^4.5.4
|
||||
camera: ^0.10.0
|
||||
path_provider: ^2.0.15
|
||||
image_picker: ^1.0.4
|
||||
wakelock_plus: ^1.2.11
|
||||
|
||||
dependency_overrides:
|
||||
syncfusion_flutter_calendar: ^28.2.9
|
||||
http: ^0.13.6
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
flutter_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "assets/Icon.png"
|
||||
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/logo.png
|
||||
- assets/logo.svg
|
||||
- assets/target.png
|
||||
- assets/google_logo.png
|
||||
- assets/best_float32_nms.tflite
|
||||
@@ -0,0 +1,30 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility 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:test1/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||