first commit

This commit is contained in:
2025-04-05 11:47:40 +02:00
commit 1fb8c28c2e
99 changed files with 7656 additions and 0 deletions
+45
View File
@@ -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
+42
View File
@@ -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'
+16
View File
@@ -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"
}
]
}
+16
View File
@@ -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.
+28
View File
@@ -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
+14
View File
@@ -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
+42
View File
@@ -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 = "../.."
}
+18
View File
@@ -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>
+61
View File
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

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>
+21
View File
@@ -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)
}
+3
View File
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
+5
View File
@@ -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
+25
View File
@@ -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")
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

+176
View File
@@ -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

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

+305
View File
@@ -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

+34
View File
@@ -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
+26
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
#include "Generated.xcconfig"
+1
View File
@@ -0,0 +1 @@
#include "Generated.xcconfig"
+616
View File
@@ -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>
+7
View File
@@ -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>
+13
View File
@@ -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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

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>
+26
View File
@@ -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>
+49
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
+12
View File
@@ -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.
}
}
+35
View File
@@ -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
);
}
}
+809
View File
@@ -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),
),
),
),
);
}
}
+67
View File
@@ -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"),
),
],
),
),
],
),
);
}
}
+323
View File
@@ -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());
}
},
),
);
}
}
+93
View File
@@ -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,
),
);
}
}
+232
View File
@@ -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
),
),
),
],
),
),
),
);
}
}
+972
View File
@@ -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,
),
),
),
),
),
);
}
}
+180
View File
@@ -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
),
),
),
],
),
),
),
);
}
}
+445
View File
@@ -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),
],
),
),
);
}),
],
);
}
}
+579
View File
@@ -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"),
),
],
);
}
}
+118
View File
@@ -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,
),
),
],
),
),
);
},
),
),
],
),
),
);
}
}
+515
View File
@@ -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,
),
),
),
),
],
),
),
),
),
);
});
}
}
+162
View File
@@ -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;
}
}
+15
View File
@@ -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') ?? '');
}
}
View File
+41
View File
@@ -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"),
],
),
),
);
}
}
+36
View File
@@ -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
),
),
);
}
}
+930
View File
@@ -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"
+58
View File
@@ -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
+30
View File
@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility 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);
});
}