fix: add project contents
45
HackOnLinces_app/aplicacion_hack/.gitignore
vendored
Normal 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-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# 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
|
||||
45
HackOnLinces_app/aplicacion_hack/.metadata
Normal file
@@ -0,0 +1,45 @@
|
||||
# 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: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
- platform: android
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
- platform: ios
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
- platform: linux
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
- platform: macos
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
- platform: web
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
- platform: windows
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
|
||||
# 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'
|
||||
17
HackOnLinces_app/aplicacion_hack/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# aplicacion_hack
|
||||
|
||||
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:
|
||||
|
||||
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
|
||||
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
|
||||
|
||||
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
HackOnLinces_app/aplicacion_hack/analysis_options.yaml
Normal 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
HackOnLinces_app/aplicacion_hack/android/.gitignore
vendored
Normal 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
|
||||
@@ -0,0 +1,50 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.google.gms.google-services")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.aplicacion_hack"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.aplicacion_hack"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "338042609701",
|
||||
"project_id": "hackon-58b23",
|
||||
"storage_bucket": "hackon-58b23.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:338042609701:android:0f0f92d7f895794f371fcc",
|
||||
"android_client_info": {
|
||||
"package_name": "com.example.aplicacion_hack"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBuT70SbADLeg92ll8keCySI8I4eYyqyLw"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,49 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:label="aplicacion_hack"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
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" />
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
android:value="ecotrack_canal" />
|
||||
</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.aplicacion_hack
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 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,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>
|
||||
35
HackOnLinces_app/aplicacion_hack/android/build.gradle.kts
Normal file
@@ -0,0 +1,35 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
// Añadimos la herramienta de Google Services para que Android la pueda usar
|
||||
classpath("com.google.gms:google-services:4.4.1")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
# This newDsl flag was added by the Flutter template
|
||||
android.newDsl=false
|
||||
# This builtInKotlin flag was added by the Flutter template
|
||||
android.builtInKotlin=false
|
||||
5
HackOnLinces_app/aplicacion_hack/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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-9.1.0-all.zip
|
||||
29
HackOnLinces_app/aplicacion_hack/android/settings.gradle.kts
Normal file
@@ -0,0 +1,29 @@
|
||||
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 "9.0.1" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services") version("4.3.15") apply false
|
||||
// END: FlutterFire Configuration
|
||||
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
BIN
HackOnLinces_app/aplicacion_hack/assets/images/bottle.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
HackOnLinces_app/aplicacion_hack/assets/images/globo.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
HackOnLinces_app/aplicacion_hack/assets/images/megafono.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
HackOnLinces_app/aplicacion_hack/assets/images/planta.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
HackOnLinces_app/aplicacion_hack/assets/images/recycle.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
HackOnLinces_app/aplicacion_hack/assets/images/reloj.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
HackOnLinces_app/aplicacion_hack/assets/images/reloj.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
1
HackOnLinces_app/aplicacion_hack/firebase.json
Normal file
@@ -0,0 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"hackon-58b23","appId":"1:338042609701:android:0f0f92d7f895794f371fcc","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"hackon-58b23","configurations":{"android":"1:338042609701:android:0f0f92d7f895794f371fcc","ios":"1:338042609701:ios:d1ac418d368d1df5371fcc","macos":"1:338042609701:ios:d1ac418d368d1df5371fcc","web":"1:338042609701:web:5f0a245f364e7604371fcc","windows":"1:338042609701:web:b01c28c419fddd25371fcc"}}}}}}
|
||||
34
HackOnLinces_app/aplicacion_hack/ios/.gitignore
vendored
Normal 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
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,644 @@
|
||||
// !$*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 */; };
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
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>"; };
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; 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 = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
);
|
||||
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 = (
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||
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 */,
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.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;
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
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;
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
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 */,
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift 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 = 13.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.aplicacionHack;
|
||||
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.aplicacionHack.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.aplicacionHack.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.aplicacionHack.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 = 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;
|
||||
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 = 13.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 = 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 = 13.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.aplicacionHack;
|
||||
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.aplicacionHack;
|
||||
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 */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency 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,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Prepare Flutter Framework Script"
|
||||
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<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"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
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"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
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
HackOnLinces_app/aplicacion_hack/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal 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>
|
||||
@@ -0,0 +1,16 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
HackOnLinces_app/aplicacion_hack/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
BIN
HackOnLinces_app/aplicacion_hack/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
HackOnLinces_app/aplicacion_hack/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
HackOnLinces_app/aplicacion_hack/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
HackOnLinces_app/aplicacion_hack/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
70
HackOnLinces_app/aplicacion_hack/ios/Runner/Info.plist
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Aplicacion Hack</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>aplicacion_hack</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>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -0,0 +1,6 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: FlutterSceneDelegate {
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
88
HackOnLinces_app/aplicacion_hack/lib/firebase_options.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
return web;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
return macos;
|
||||
case TargetPlatform.windows:
|
||||
return windows;
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDfpYR_--oRlGdTjLnAZY6z3RLh3LLz5gk',
|
||||
appId: '1:338042609701:web:5f0a245f364e7604371fcc',
|
||||
messagingSenderId: '338042609701',
|
||||
projectId: 'hackon-58b23',
|
||||
authDomain: 'hackon-58b23.firebaseapp.com',
|
||||
storageBucket: 'hackon-58b23.firebasestorage.app',
|
||||
measurementId: 'G-TBT47P2HX5',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBuT70SbADLeg92ll8keCySI8I4eYyqyLw',
|
||||
appId: '1:338042609701:android:0f0f92d7f895794f371fcc',
|
||||
messagingSenderId: '338042609701',
|
||||
projectId: 'hackon-58b23',
|
||||
storageBucket: 'hackon-58b23.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAB7czRjHfrkHEjiDOvPLiUz9zS1UGiZbw',
|
||||
appId: '1:338042609701:ios:d1ac418d368d1df5371fcc',
|
||||
messagingSenderId: '338042609701',
|
||||
projectId: 'hackon-58b23',
|
||||
storageBucket: 'hackon-58b23.firebasestorage.app',
|
||||
iosBundleId: 'com.example.aplicacionHack',
|
||||
);
|
||||
|
||||
static const FirebaseOptions macos = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAB7czRjHfrkHEjiDOvPLiUz9zS1UGiZbw',
|
||||
appId: '1:338042609701:ios:d1ac418d368d1df5371fcc',
|
||||
messagingSenderId: '338042609701',
|
||||
projectId: 'hackon-58b23',
|
||||
storageBucket: 'hackon-58b23.firebasestorage.app',
|
||||
iosBundleId: 'com.example.aplicacionHack',
|
||||
);
|
||||
|
||||
static const FirebaseOptions windows = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDfpYR_--oRlGdTjLnAZY6z3RLh3LLz5gk',
|
||||
appId: '1:338042609701:web:b01c28c419fddd25371fcc',
|
||||
messagingSenderId: '338042609701',
|
||||
projectId: 'hackon-58b23',
|
||||
authDomain: 'hackon-58b23.firebaseapp.com',
|
||||
storageBucket: 'hackon-58b23.firebasestorage.app',
|
||||
measurementId: 'G-DYGG6KBJ6C',
|
||||
);
|
||||
}
|
||||
114
HackOnLinces_app/aplicacion_hack/lib/main.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
// ================================================================
|
||||
// main.dart — EcoTrack
|
||||
// Sistema de Notificacion Privada de Recoleccion de Residuos
|
||||
// ================================================================
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'screens/login_screen.dart';
|
||||
import 'screens/home_screen.dart';
|
||||
import 'screens/route_list_screen.dart';
|
||||
import 'screens/info_screen.dart';
|
||||
import 'screens/analytics_screen.dart';
|
||||
import 'screens/reporte_screen.dart';
|
||||
import 'screens/mapa_rutas_screen.dart';
|
||||
import 'firebase_options.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
|
||||
const AndroidNotificationChannel _canal = AndroidNotificationChannel(
|
||||
'ecotrack_canal',
|
||||
'EcoTrack Notificaciones',
|
||||
description: 'Notificaciones del camión de basura',
|
||||
importance: Importance.high,
|
||||
);
|
||||
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
}
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||
|
||||
// DESPUÉS
|
||||
final androidPlugin =
|
||||
_localNotifications.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
await androidPlugin?.createNotificationChannel(_canal);
|
||||
|
||||
await _localNotifications.initialize(
|
||||
const InitializationSettings(
|
||||
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
|
||||
),
|
||||
);
|
||||
|
||||
// ← EL QUE TE FALTABA
|
||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||
final notification = message.notification;
|
||||
if (notification == null) return;
|
||||
_localNotifications.show(
|
||||
notification.hashCode,
|
||||
notification.title,
|
||||
notification.body,
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_canal.id,
|
||||
_canal.name,
|
||||
channelDescription: _canal.description,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
await FirebaseMessaging.instance.requestPermission(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
|
||||
runApp(const EcoTrackApp());
|
||||
}
|
||||
|
||||
class EcoTrackApp extends StatelessWidget {
|
||||
const EcoTrackApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'EcoTrack',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xFF2E7D32),
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
useMaterial3: true,
|
||||
fontFamily: 'Roboto',
|
||||
appBarTheme: const AppBarTheme(
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (context) => const LoginScreen(),
|
||||
'/home': (context) => const HomeScreen(),
|
||||
'/routes': (context) => const RouteListScreen(),
|
||||
'/info': (context) => const InfoScreen(),
|
||||
'/analytics': (context) => const AnalyticsScreen(),
|
||||
'/reporte': (context) => const ReporteScreen(),
|
||||
'/mapa': (context) => const MapaRutasScreen(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
1092
HackOnLinces_app/aplicacion_hack/lib/screens/analytics_screen.dart
Normal file
927
HackOnLinces_app/aplicacion_hack/lib/screens/home_screen.dart
Normal file
@@ -0,0 +1,927 @@
|
||||
// ================================================================
|
||||
// lib/screens/home_screen.dart (v3 — rediseño visual)
|
||||
// ================================================================
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import '../services/api_service.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
int? _usuarioId;
|
||||
String _nombreUsuario = '';
|
||||
ETAInfo? _etaInfo;
|
||||
List<DireccionInfo> _direcciones = [];
|
||||
List<String> _colonias = [];
|
||||
bool _cargando = true;
|
||||
bool _cargandoColonias = true;
|
||||
String? _error;
|
||||
String? _errorColonias;
|
||||
|
||||
Timer? _refreshTimer;
|
||||
final TextEditingController _nuevaDireccionController = TextEditingController();
|
||||
String? _nuevaColoniaSeleccionada;
|
||||
|
||||
late AnimationController _pulseController;
|
||||
late AnimationController _fadeController;
|
||||
late Animation<double> _pulseAnimation;
|
||||
late Animation<double> _fadeAnimation;
|
||||
|
||||
final ApiService _apiService = ApiService();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pulseController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 2),
|
||||
)..repeat(reverse: true);
|
||||
_pulseAnimation = Tween<double>(begin: 1.0, end: 1.06).animate(
|
||||
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_fadeController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 600),
|
||||
);
|
||||
_fadeAnimation = CurvedAnimation(parent: _fadeController, curve: Curves.easeOut);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_usuarioId == null) {
|
||||
final args = ModalRoute.of(context)?.settings.arguments;
|
||||
if (args is int) {
|
||||
_usuarioId = args;
|
||||
_inicializar();
|
||||
} else {
|
||||
_cargarDesdeStorage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pulseController.dispose();
|
||||
_fadeController.dispose();
|
||||
_refreshTimer?.cancel();
|
||||
_nuevaDireccionController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _inicializar() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (mounted) setState(() => _nombreUsuario = prefs.getString('nombre') ?? '');
|
||||
_cargarETA();
|
||||
_iniciarAutoRefresh();
|
||||
_registrarFCMToken();
|
||||
_cargarUsuario();
|
||||
_cargarColonias();
|
||||
}
|
||||
|
||||
Future<void> _cargarDesdeStorage() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final id = prefs.getInt('usuario_id');
|
||||
if (id != null) {
|
||||
_usuarioId = id;
|
||||
_inicializar();
|
||||
} else {
|
||||
if (mounted) Navigator.pushReplacementNamed(context, '/');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cargarETA() async {
|
||||
if (!mounted) return;
|
||||
setState(() { _cargando = true; _error = null; });
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final usuarioId = prefs.getInt('usuario_id') ?? 1;
|
||||
final etaInfo = await _apiService.obtenerETA(usuarioId);
|
||||
if (mounted) {
|
||||
setState(() { _etaInfo = etaInfo; _cargando = false; });
|
||||
_fadeController.forward(from: 0);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_etaInfo = ETAInfo(
|
||||
usuarioId: 1,
|
||||
colonia: "Centro",
|
||||
rutaNombre: "Ruta Poniente - Camión #4",
|
||||
rutaStatus: "EN_RUTA",
|
||||
gpsOk: true,
|
||||
etaTexto: "12 minutos aprox.",
|
||||
etaMinutos: 12,
|
||||
mensajePreventivo: "⚠️ El camión está a 3 cuadras. ¡Prepara tus bolsas!",
|
||||
);
|
||||
_cargando = false;
|
||||
});
|
||||
_fadeController.forward(from: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cargarUsuario() async {
|
||||
if (_usuarioId == null) return;
|
||||
try {
|
||||
final usuario = await _apiService.obtenerUsuario(_usuarioId!);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_direcciones = usuario.direcciones;
|
||||
if (usuario.nombre.isNotEmpty) _nombreUsuario = usuario.nombre;
|
||||
});
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('nombre', usuario.nombre);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error cargando usuario: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cargarColonias() async {
|
||||
try {
|
||||
final colonias = await _apiService.obtenerColonias();
|
||||
if (mounted) setState(() { _colonias = colonias; _cargandoColonias = false; });
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_colonias = ['Zona Centro', 'Las Arboledas', 'Trojes', 'San Juanico', 'Los Olivos', 'Rancho Seco', 'Las Insurgentes'];
|
||||
_cargandoColonias = false;
|
||||
_errorColonias = 'Sin conexión. Usando lista local.';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _iniciarAutoRefresh() {
|
||||
_refreshTimer = Timer.periodic(const Duration(seconds: 60), (_) => _cargarETA());
|
||||
}
|
||||
|
||||
Future<void> _registrarFCMToken() async {
|
||||
try {
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
final settings = await messaging.requestPermission(alert: true, sound: true, badge: true);
|
||||
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
|
||||
final token = await messaging.getToken();
|
||||
if (token != null && _usuarioId != null) {
|
||||
await _apiService.registrarFcmToken(_usuarioId!, token);
|
||||
}
|
||||
}
|
||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||
if (message.notification != null && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('🚛 ${message.notification!.body}'),
|
||||
backgroundColor: Colors.green.shade700,
|
||||
duration: const Duration(seconds: 5),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
));
|
||||
_cargarETA();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('Error FCM: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ── DIÁLOGOS ────────────────────────────────────────────────────
|
||||
|
||||
Future<void> _confirmarCerrarSesion() async {
|
||||
final confirmar = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
title: const Text('Cerrar sesión'),
|
||||
content: const Text('¿Seguro que quieres cerrar sesión?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Cancelar')),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red.shade700, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
|
||||
child: const Text('Cerrar sesión', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmar == true) {
|
||||
_refreshTimer?.cancel();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.clear();
|
||||
if (mounted) Navigator.pushReplacementNamed(context, '/');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _mostrarCambioPassword() async {
|
||||
final actualCtrl = TextEditingController();
|
||||
final nuevoCtrl = TextEditingController();
|
||||
bool mostrarActual = false;
|
||||
bool mostrarNuevo = false;
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => StatefulBuilder(
|
||||
builder: (ctx, setS) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
title: const Text('Cambiar contraseña'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: actualCtrl,
|
||||
obscureText: !mostrarActual,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Contraseña actual',
|
||||
prefixIcon: const Icon(Icons.lock_outline),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(mostrarActual ? Icons.visibility_off : Icons.visibility),
|
||||
onPressed: () => setS(() => mostrarActual = !mostrarActual),
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: nuevoCtrl,
|
||||
obscureText: !mostrarNuevo,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nueva contraseña',
|
||||
hintText: 'Mínimo 6 caracteres',
|
||||
prefixIcon: const Icon(Icons.lock_reset),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(mostrarNuevo ? Icons.visibility_off : Icons.visibility),
|
||||
onPressed: () => setS(() => mostrarNuevo = !mostrarNuevo),
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Cancelar')),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (_usuarioId == null) return;
|
||||
if (actualCtrl.text.isEmpty || nuevoCtrl.text.isEmpty) return;
|
||||
if (nuevoCtrl.text.length < 6) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Mínimo 6 caracteres.')));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await _apiService.actualizarPassword(_usuarioId!, actualCtrl.text, nuevoCtrl.text);
|
||||
if (mounted) {
|
||||
Navigator.of(ctx).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('✅ Contraseña actualizada.'), backgroundColor: Colors.green),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(e.toString().replaceFirst('Exception: ', '')), backgroundColor: Colors.red.shade700),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Text('Guardar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
actualCtrl.dispose();
|
||||
nuevoCtrl.dispose();
|
||||
}
|
||||
|
||||
Future<void> _agregarDireccion() async {
|
||||
if (_usuarioId == null) return;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final direccion = _nuevaDireccionController.text.trim();
|
||||
if (_nuevaColoniaSeleccionada == null) {
|
||||
messenger.showSnackBar(const SnackBar(content: Text('Selecciona una colonia.')));
|
||||
return;
|
||||
}
|
||||
if (direccion.isEmpty) {
|
||||
messenger.showSnackBar(const SnackBar(content: Text('Ingresa la dirección.')));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await _apiService.agregarDireccion(_usuarioId!, _nuevaColoniaSeleccionada!, direccion);
|
||||
_nuevaDireccionController.clear();
|
||||
_nuevaColoniaSeleccionada = null;
|
||||
await _cargarUsuario();
|
||||
await _cargarETA();
|
||||
if (mounted) messenger.showSnackBar(const SnackBar(content: Text('✅ Dirección agregada.')));
|
||||
} catch (e) {
|
||||
if (mounted) messenger.showSnackBar(const SnackBar(content: Text('No se pudo agregar la dirección.')));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _mostrarAgregarDireccionDialog() async {
|
||||
if (_cargandoColonias) await _cargarColonias();
|
||||
if (!mounted) return;
|
||||
_nuevaDireccionController.clear();
|
||||
_nuevaColoniaSeleccionada = null;
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
title: const Text('Agregar dirección'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_errorColonias != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(_errorColonias!, style: TextStyle(color: Colors.orange.shade700, fontSize: 12)),
|
||||
),
|
||||
TextField(
|
||||
controller: _nuevaDireccionController,
|
||||
decoration: const InputDecoration(labelText: 'Dirección', hintText: 'Calle, número'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: _nuevaColoniaSeleccionada,
|
||||
hint: const Text('Selecciona tu colonia'),
|
||||
items: _colonias.map((c) => DropdownMenuItem(value: c, child: Text(c))).toList(),
|
||||
onChanged: (v) => setState(() => _nuevaColoniaSeleccionada = v),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Cancelar')),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final nav = Navigator.of(context);
|
||||
await _agregarDireccion();
|
||||
if (mounted) nav.pop();
|
||||
},
|
||||
child: const Text('Guardar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ── HELPERS ─────────────────────────────────────────────────────
|
||||
|
||||
Color _colorSegunETA(int eta) {
|
||||
if (eta <= 5) return const Color(0xFFB71C1C);
|
||||
if (eta <= 15) return const Color(0xFFE65100);
|
||||
if (eta <= 30) return const Color(0xFF1B5E20);
|
||||
return const Color(0xFF1A237E);
|
||||
}
|
||||
|
||||
String _emojiSegunETA(int eta) {
|
||||
if (eta <= 5) return '🔴';
|
||||
if (eta <= 15) return '🟡';
|
||||
if (eta <= 30) return '🟢';
|
||||
return '🔵';
|
||||
}
|
||||
|
||||
String _textoEstado(int eta) {
|
||||
if (eta <= 5) return '¡Saca tu basura AHORA!';
|
||||
if (eta <= 15) return 'Prepárate, viene pronto';
|
||||
if (eta <= 30) return 'En camino a tu zona';
|
||||
return 'Aún falta un rato';
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// BUILD
|
||||
// ================================================================
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseColor = _etaInfo != null
|
||||
? _colorSegunETA(_etaInfo!.etaMinutos)
|
||||
: const Color(0xFF1B5E20);
|
||||
|
||||
return Scaffold(
|
||||
body: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [baseColor, baseColor.withValues(alpha: 0.85), const Color(0xFF0D1B0F)],
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: _cargando
|
||||
? _buildCargando()
|
||||
: _error != null
|
||||
? _buildError()
|
||||
: FadeTransition(opacity: _fadeAnimation, child: _buildContenido()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCargando() => const Center(
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
|
||||
SizedBox(height: 16),
|
||||
Text('Consultando estado del camión...', style: TextStyle(color: Colors.white60, fontSize: 15)),
|
||||
]),
|
||||
);
|
||||
|
||||
Widget _buildError() => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
const Icon(Icons.wifi_off_rounded, size: 72, color: Colors.white38),
|
||||
const SizedBox(height: 16),
|
||||
Text(_error!, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontSize: 17)),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _cargarETA,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Reintentar'),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.white, foregroundColor: Colors.red.shade700),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildContenido() {
|
||||
if (_etaInfo == null) return const SizedBox.shrink();
|
||||
final eta = _etaInfo!;
|
||||
|
||||
return SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
children: [
|
||||
// ── HEADER ──────────────────────────────────────────────
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 8, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_nombreUsuario.isNotEmpty)
|
||||
Text(
|
||||
'Hola, ${_nombreUsuario.split(' ').first} 👋',
|
||||
style: const TextStyle(color: Colors.white60, fontSize: 13),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.location_on_rounded, color: Colors.white70, size: 15),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
eta.colonia,
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 17),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pushNamed(context, '/mapa'),
|
||||
icon: const Icon(Icons.map_rounded, color: Colors.white60),
|
||||
tooltip: 'Ver mapa',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pushNamed(context, '/routes'),
|
||||
icon: const Icon(Icons.local_shipping_rounded, color: Colors.white60),
|
||||
tooltip: 'Ver rutas',
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert, color: Colors.white60),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
onSelected: (v) {
|
||||
if (v == 'password') _mostrarCambioPassword();
|
||||
if (v == 'logout') _confirmarCerrarSesion();
|
||||
},
|
||||
itemBuilder: (_) => [
|
||||
const PopupMenuItem(
|
||||
value: 'password',
|
||||
child: Row(children: [Icon(Icons.lock_reset, size: 18), SizedBox(width: 10), Text('Cambiar contraseña')]),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
const PopupMenuItem(
|
||||
value: 'logout',
|
||||
child: Row(children: [Icon(Icons.logout, size: 18, color: Colors.red), SizedBox(width: 10), Text('Cerrar sesión', style: TextStyle(color: Colors.red))]),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ── CHIP DE ESTADO GPS ───────────────────────────────────
|
||||
if (!eta.gpsOk)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 0),
|
||||
child: _GlassChip(
|
||||
icon: Icons.gps_off,
|
||||
label: 'GPS del camión desconectado',
|
||||
color: Colors.red.shade300,
|
||||
),
|
||||
),
|
||||
|
||||
// ── CÍRCULO ETA PRINCIPAL ────────────────────────────────
|
||||
const SizedBox(height: 24),
|
||||
ScaleTransition(
|
||||
scale: _pulseAnimation,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Anillo exterior difuso
|
||||
Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white.withValues(alpha: 0.06),
|
||||
),
|
||||
),
|
||||
// Anillo con blur
|
||||
ClipOval(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
|
||||
child: Container(
|
||||
width: 180,
|
||||
height: 180,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.4), width: 2),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(_emojiSegunETA(eta.etaMinutos), style: const TextStyle(fontSize: 36)),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${eta.etaMinutos}',
|
||||
style: const TextStyle(fontSize: 58, fontWeight: FontWeight.w900, color: Colors.white, height: 1),
|
||||
),
|
||||
const Text('minutos', style: TextStyle(fontSize: 14, color: Colors.white70, letterSpacing: 1)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
_textoEstado(eta.etaMinutos),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
eta.etaTexto,
|
||||
style: const TextStyle(color: Colors.white60, fontSize: 13),
|
||||
),
|
||||
|
||||
// ── CARD MENSAJE PREVENTIVO ──────────────────────────────
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: _GlassCard(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44, height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(Icons.notifications_active_rounded, color: Colors.white, size: 22),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Text(
|
||||
eta.mensajePreventivo,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500, height: 1.4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── INFO DE RUTA ─────────────────────────────────────────
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: _GlassCard(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44, height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(Icons.local_shipping_rounded, color: Colors.white, size: 22),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(eta.rutaNombre, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600, fontSize: 13)),
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: eta.rutaStatus == 'EN_RUTA'
|
||||
? Colors.green.withValues(alpha: 0.3)
|
||||
: Colors.blue.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
eta.rutaStatus,
|
||||
style: TextStyle(
|
||||
color: eta.rutaStatus == 'EN_RUTA' ? Colors.greenAccent : Colors.lightBlueAccent,
|
||||
fontSize: 11, fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── ACCESOS RÁPIDOS ──────────────────────────────────────
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 2, bottom: 12),
|
||||
child: Text('Accesos rápidos', style: TextStyle(color: Colors.white60, fontSize: 12, letterSpacing: 0.8)),
|
||||
),
|
||||
GridView.count(
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
childAspectRatio: 1.55,
|
||||
children: [
|
||||
_AccesoRapido(
|
||||
icon: Icons.local_shipping_rounded,
|
||||
label: 'Rutas',
|
||||
sublabel: 'Estado del camión',
|
||||
color: const Color(0xFF1565C0),
|
||||
onTap: () => Navigator.pushNamed(context, '/routes'),
|
||||
),
|
||||
_AccesoRapido(
|
||||
icon: Icons.analytics_rounded,
|
||||
label: 'Análisis',
|
||||
sublabel: 'Reportes y predicción',
|
||||
color: const Color(0xFF6A1B9A),
|
||||
onTap: () => Navigator.pushNamed(context, '/analytics'),
|
||||
),
|
||||
_AccesoRapido(
|
||||
icon: Icons.report_problem_rounded,
|
||||
label: 'Reportar',
|
||||
sublabel: 'Enviar un reporte',
|
||||
color: const Color(0xFFE65100),
|
||||
onTap: () => Navigator.pushNamed(context, '/reporte'),
|
||||
),
|
||||
_AccesoRapido(
|
||||
icon: Icons.eco_rounded,
|
||||
label: 'Información',
|
||||
sublabel: 'Guía de residuos',
|
||||
color: const Color(0xFF2E7D32),
|
||||
onTap: () => Navigator.pushNamed(context, '/info'),
|
||||
),
|
||||
_AccesoRapido(
|
||||
icon: Icons.map_rounded,
|
||||
label: 'Mapa',
|
||||
sublabel: 'Ver rutas en mapa',
|
||||
color: const Color(0xFF00838F),
|
||||
onTap: () => Navigator.pushNamed(context, '/mapa'),
|
||||
),
|
||||
_AccesoRapido(
|
||||
icon: Icons.add_location_alt_rounded,
|
||||
label: 'Dirección',
|
||||
sublabel: 'Agregar domicilio',
|
||||
color: const Color(0xFFC62828),
|
||||
onTap: _mostrarAgregarDireccionDialog,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ── DIRECCIONES ──────────────────────────────────────────
|
||||
if (_direcciones.isNotEmpty) ...[
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: _GlassCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.home_rounded, color: Colors.white70, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Mis direcciones', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14)),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: _mostrarAgregarDireccionDialog,
|
||||
child: const Icon(Icons.add_circle_outline, color: Colors.white54, size: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
..._direcciones.map((d) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 6, height: 6,
|
||||
decoration: const BoxDecoration(color: Colors.white54, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(d.colonia, style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w600)),
|
||||
Text(d.direccion, style: const TextStyle(color: Colors.white54, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
// ── FOOTER ───────────────────────────────────────────────
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 28),
|
||||
child: Column(children: [
|
||||
const Text('🔄 Actualización automática cada minuto',
|
||||
style: TextStyle(color: Colors.white30, fontSize: 11)),
|
||||
const SizedBox(height: 10),
|
||||
TextButton.icon(
|
||||
onPressed: _cargarETA,
|
||||
icon: const Icon(Icons.refresh, color: Colors.white38, size: 16),
|
||||
label: const Text('Actualizar ahora', style: TextStyle(color: Colors.white38, fontSize: 12)),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// WIDGETS AUXILIARES
|
||||
// ================================================================
|
||||
|
||||
/// Tarjeta con efecto glassmorphism
|
||||
class _GlassCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const _GlassCard({required this.child, this.padding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: padding ?? const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.2), width: 1),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Chip de estado pequeño
|
||||
class _GlassChip extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final Color color;
|
||||
|
||||
const _GlassChip({required this.icon, required this.label, required this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: color.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 15),
|
||||
const SizedBox(width: 6),
|
||||
Text(label, style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tarjeta de acceso rápido en el grid
|
||||
class _AccesoRapido extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String sublabel;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _AccesoRapido({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.sublabel,
|
||||
required this.color,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.18),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: color.withValues(alpha: 0.35), width: 1),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
width: 36, height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.25),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 20),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13)),
|
||||
Text(sublabel, style: const TextStyle(color: Colors.white54, fontSize: 10), overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
808
HackOnLinces_app/aplicacion_hack/lib/screens/info_screen.dart
Normal file
@@ -0,0 +1,808 @@
|
||||
// ================================================================
|
||||
// lib/screens/info_screen.dart — EcoTrack
|
||||
// Pantalla de Informacion + Tutorial interactivo
|
||||
// ================================================================
|
||||
//
|
||||
// ASSETS USADOS:
|
||||
// assets/images/recycle.jpg → Separacion / reciclaje
|
||||
// assets/images/bottle.png → Plasticos
|
||||
// assets/images/planta.png → Composta / Medio ambiente
|
||||
// assets/images/megafono.png → Residuos peligrosos / Horarios
|
||||
//
|
||||
// SECCIONES:
|
||||
// 1. Tutorial interactivo (swipe de pasos)
|
||||
// 2. Articulos de informacion por categoria
|
||||
// 3. Detalle de articulo con imagen
|
||||
// ================================================================
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ================================================================
|
||||
// MODELOS
|
||||
// ================================================================
|
||||
|
||||
class _Subseccion {
|
||||
final String subtitulo;
|
||||
final String texto;
|
||||
const _Subseccion(this.subtitulo, this.texto);
|
||||
}
|
||||
|
||||
class _Articulo {
|
||||
final String id;
|
||||
final String categoria;
|
||||
final String titulo;
|
||||
final String resumen;
|
||||
final String imagenAsset;
|
||||
final Color color;
|
||||
final List<_Subseccion> contenido;
|
||||
final String consejoRapido;
|
||||
|
||||
const _Articulo({
|
||||
required this.id,
|
||||
required this.categoria,
|
||||
required this.titulo,
|
||||
required this.resumen,
|
||||
required this.imagenAsset,
|
||||
required this.color,
|
||||
required this.contenido,
|
||||
required this.consejoRapido,
|
||||
});
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// DATOS
|
||||
// ================================================================
|
||||
|
||||
const _articulos = [
|
||||
_Articulo(
|
||||
id: 'separacion',
|
||||
categoria: 'Separacion',
|
||||
titulo: 'Como separar correctamente tu basura',
|
||||
resumen: 'La separacion correcta es el primer paso para reciclar y reducir el impacto ambiental.',
|
||||
imagenAsset: 'assets/images/recycle.jpg',
|
||||
color: Color(0xFF2E7D32),
|
||||
consejoRapido: 'Regla facil: si vino de la naturaleza y se pudre, es organico. Si es artificial y esta limpio, es reciclable.',
|
||||
contenido: [
|
||||
_Subseccion('Residuos Organicos', 'Restos de comida, cascaras de frutas y verduras, posos de cafe, bolsas de te, restos de jardin. Van en bolsa oscura o cafe. Se convierten en composta.'),
|
||||
_Subseccion('Inorganicos Reciclables', 'Plasticos (botellas PET, envases), papel y carton limpios, vidrio, latas de aluminio y hojalata. Van en bolsa transparente. Deben estar limpios y secos.'),
|
||||
_Subseccion('No Reciclables', 'Papel higienico usado, panales, colillas de cigarro, envolturas metalizadas. Van en bolsa negra. No tienen valor de reciclaje.'),
|
||||
_Subseccion('Residuos Especiales', 'Pilas, medicamentos caducados, electronicos, aceite de cocina. NUNCA los mezcles con la basura regular. Lleva pilas a puntos de acopio en supermercados.'),
|
||||
],
|
||||
),
|
||||
_Articulo(
|
||||
id: 'horarios',
|
||||
categoria: 'Horarios',
|
||||
titulo: 'Cuando sacar tu basura',
|
||||
resumen: 'Sacar la basura en el momento correcto evita plagas, malos olores y que el camion se la pierda.',
|
||||
imagenAsset: 'assets/images/megafono.png',
|
||||
color: Color(0xFF1565C0),
|
||||
consejoRapido: 'Espera la alerta de EcoTrack antes de salir con tus bolsas. Te ahorra tiempo y evita dejar basura expuesta.',
|
||||
contenido: [
|
||||
_Subseccion('El momento ideal', 'Saca tu basura cuando recibas la alerta de "Camion Cercano" en EcoTrack. Eso significa que el camion esta a menos de 15 minutos de tu domicilio.'),
|
||||
_Subseccion('Por que no de noche', 'Las bolsas en la acera de noche atraen perros y fauna nocturna que las rompen y dispersan los residuos. Ademas el plastico se deteriora con la humedad nocturna.'),
|
||||
_Subseccion('Si me lo pierdo', 'Si el camion ya paso, guarda tu basura hasta el siguiente dia. Nunca dejes bolsas en la via publica fuera del horario de recoleccion.'),
|
||||
_Subseccion('Dias festivos', 'En dias festivos el servicio puede retrasarse o cancelarse. Activa las notificaciones de EcoTrack para recibir alertas de retraso o cambio de horario.'),
|
||||
],
|
||||
),
|
||||
_Articulo(
|
||||
id: 'plasticos',
|
||||
categoria: 'Reciclaje',
|
||||
titulo: 'Guia de plasticos: cuales si y cuales no',
|
||||
resumen: 'No todos los plasticos son iguales. Aprende a leer el numero en el triangulo de reciclaje.',
|
||||
imagenAsset: 'assets/images/bottle.png',
|
||||
color: Color(0xFF00838F),
|
||||
consejoRapido: 'Busca el numero dentro del triangulo en el fondo del envase. Los numeros 1 y 2 siempre van al reciclaje.',
|
||||
contenido: [
|
||||
_Subseccion('Plastico #1 PET', 'Botellas de agua y refrescos. El mas reciclado. Aplastalo para ahorrar espacio. Quita la tapa porque es un material diferente.'),
|
||||
_Subseccion('Plastico #2 HDPE', 'Garrafones, botellas de leche, shampoo. Tambien muy reciclable. Enjuagalo antes de separarlo.'),
|
||||
_Subseccion('Plastico #5 PP', 'Tapas de botellas, envases de yogur. Si se recicla pero menos centros lo aceptan.'),
|
||||
_Subseccion('Plasticos 3, 6 y 7', 'PVC, unicel, policarbonato. Dificiles o imposibles de reciclar. Van a basura no reciclable.'),
|
||||
_Subseccion('Bolsas de plastico', 'No van en el reciclaje de casa porque tapan las maquinas clasificadoras. Lleva tus bolsas a centros de acopio en supermercados.'),
|
||||
],
|
||||
),
|
||||
_Articulo(
|
||||
id: 'composta',
|
||||
categoria: 'Compostaje',
|
||||
titulo: 'Haz composta en casa',
|
||||
resumen: 'Convierte tus residuos organicos en abono natural. Es mas facil de lo que crees.',
|
||||
imagenAsset: 'assets/images/planta.png',
|
||||
color: Color(0xFF558B2F),
|
||||
consejoRapido: 'La composta lista huele a tierra mojada, no a podrido. Si huele mal, agrega mas material seco y volteala.',
|
||||
contenido: [
|
||||
_Subseccion('Que necesitas', 'Un contenedor con tapa, residuos organicos, tierra o tierra de hojarasca, y un poco de paciencia.'),
|
||||
_Subseccion('Que puedes compostar', 'Cascaras de frutas y verduras, restos de comida cocida sin carne, posos de cafe y filtros de papel, cascaras de huevo, hojas secas.'),
|
||||
_Subseccion('Que NO debes compostar', 'Carnes, pescados, lacteos, aceites ya que atraen plagas, excrementos de mascotas, plasticos ni metales.'),
|
||||
_Subseccion('El proceso', 'Alterna capas de residuos organicos humedos con capas de material seco. Voltea la mezcla cada semana. En 2 a 3 meses tendras composta lista para tus plantas.'),
|
||||
],
|
||||
),
|
||||
_Articulo(
|
||||
id: 'peligrosos',
|
||||
categoria: 'Residuos Especiales',
|
||||
titulo: 'Residuos peligrosos: como deshacerte de ellos',
|
||||
resumen: 'Pilas, medicamentos y electronicos requieren un manejo especial para no contaminar el suelo y el agua.',
|
||||
imagenAsset: 'assets/images/megafono.png',
|
||||
color: Color(0xFFE65100),
|
||||
consejoRapido: 'Guarda una caja en casa exclusiva para residuos peligrosos. Cuando este llena, busca el punto de acopio mas cercano.',
|
||||
contenido: [
|
||||
_Subseccion('Pilas y baterias', 'Una sola pila AA puede contaminar 600,000 litros de agua. Guardalas en una bolsa y lleva a los puntos de acopio en Walmart, Soriana, Home Depot o OXXO.'),
|
||||
_Subseccion('Medicamentos caducados', 'No los tires al drenaje ni a la basura regular. Farmacias del Ahorro y Benavides cuentan con contenedores REPARED para medicamentos.'),
|
||||
_Subseccion('Electronicos RAEE', 'Celulares, computadoras, cables, focos LED. Contienen plomo, mercurio y cadmio. Lleva a tiendas de electronicos o espera las jornadas municipales.'),
|
||||
_Subseccion('Aceite de cocina', 'Un litro de aceite contamina hasta 1,000 litros de agua potable. Viertelo en una botella PET con tapa y lleva a centros de acopio.'),
|
||||
],
|
||||
),
|
||||
_Articulo(
|
||||
id: 'impacto',
|
||||
categoria: 'Medio Ambiente',
|
||||
titulo: 'El impacto real de reciclar',
|
||||
resumen: 'Numeros concretos para entender por que vale la pena separar tu basura cada dia.',
|
||||
imagenAsset: 'assets/images/planta.png',
|
||||
color: Color(0xFF4527A0),
|
||||
consejoRapido: 'Cada lata de aluminio que reciclas ahorra energia equivalente a medio litro de gasolina. Si importa.',
|
||||
contenido: [
|
||||
_Subseccion('Papel y carton', 'Reciclar 1 tonelada de papel salva 17 arboles y ahorra 26,000 litros de agua. Una familia promedio genera 500 kg de papel al ano.'),
|
||||
_Subseccion('Aluminio', 'Reciclar una lata de aluminio ahorra la energia suficiente para que un foco LED funcione 20 horas. El aluminio puede reciclarse infinitas veces.'),
|
||||
_Subseccion('Vidrio', 'El vidrio tarda mas de 4,000 anos en degradarse. Reciclarlo reduce en 20% las emisiones de CO2 de su produccion.'),
|
||||
_Subseccion('Residuos en Mexico', 'Mexico genera 120,000 toneladas de basura al dia. Solo el 9% se recicla formalmente. Si cada hogar separara correctamente, ese porcentaje podria triplicarse.'),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
// ── Pasos del tutorial ─────────────────────────────────────────
|
||||
|
||||
class _PasoTutorial {
|
||||
final String titulo;
|
||||
final String descripcion;
|
||||
final String imagenAsset;
|
||||
final Color color;
|
||||
final IconData icono;
|
||||
|
||||
const _PasoTutorial({
|
||||
required this.titulo,
|
||||
required this.descripcion,
|
||||
required this.imagenAsset,
|
||||
required this.color,
|
||||
required this.icono,
|
||||
});
|
||||
}
|
||||
|
||||
const _pasosTutorial = [
|
||||
_PasoTutorial(
|
||||
titulo: 'Bienvenido a EcoTrack',
|
||||
descripcion: 'EcoTrack te notifica cuando el camion recolector esta cerca de tu domicilio para que saques tu basura en el momento exacto.',
|
||||
imagenAsset: 'assets/images/recycle.jpg',
|
||||
color: Color(0xFF2E7D32),
|
||||
icono: Icons.recycling_rounded,
|
||||
),
|
||||
_PasoTutorial(
|
||||
titulo: 'Separa tus residuos',
|
||||
descripcion: 'Separa tu basura en organicos, reciclables y no reciclables. Esto facilita el trabajo del camion y reduce el impacto ambiental.',
|
||||
imagenAsset: 'assets/images/bottle.png',
|
||||
color: Color(0xFF00838F),
|
||||
icono: Icons.category_rounded,
|
||||
),
|
||||
_PasoTutorial(
|
||||
titulo: 'Espera la alerta',
|
||||
descripcion: 'Activa las notificaciones de EcoTrack. Te avisaremos cuando el camion este a menos de 15 minutos de tu casa.',
|
||||
imagenAsset: 'assets/images/megafono.png',
|
||||
color: Color(0xFF1565C0),
|
||||
icono: Icons.notifications_active_rounded,
|
||||
),
|
||||
_PasoTutorial(
|
||||
titulo: 'Saca la basura a tiempo',
|
||||
descripcion: 'Al recibir la alerta, saca tus bolsas a la acera. Evita sacarlas muy antes para no atraer fauna y mantener limpia la calle.',
|
||||
imagenAsset: 'assets/images/recycle.jpg',
|
||||
color: Color(0xFFE65100),
|
||||
icono: Icons.access_time_filled_rounded,
|
||||
),
|
||||
_PasoTutorial(
|
||||
titulo: 'Envia reportes',
|
||||
descripcion: 'Si el camion no paso o detectas alguna irregularidad, usa la seccion de Reportes para informar al municipio. Tu participacion mejora el servicio.',
|
||||
imagenAsset: 'assets/images/megafono.png',
|
||||
color: Color(0xFF6A1B9A),
|
||||
icono: Icons.report_problem_rounded,
|
||||
),
|
||||
];
|
||||
|
||||
// ================================================================
|
||||
// PANTALLA PRINCIPAL
|
||||
// ================================================================
|
||||
class InfoScreen extends StatefulWidget {
|
||||
const InfoScreen({super.key});
|
||||
|
||||
@override
|
||||
State<InfoScreen> createState() => _InfoScreenState();
|
||||
}
|
||||
|
||||
class _InfoScreenState extends State<InfoScreen> with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
String? _categoriaFiltro;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<String> get _categorias =>
|
||||
_articulos.map((a) => a.categoria).toSet().toList();
|
||||
|
||||
List<_Articulo> get _articulosFiltrados =>
|
||||
_categoriaFiltro == null
|
||||
? _articulos
|
||||
: _articulos.where((a) => a.categoria == _categoriaFiltro).toList();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F7F5),
|
||||
appBar: AppBar(
|
||||
title: const Text('EcoTrack — Aprende'),
|
||||
backgroundColor: const Color(0xFF2E7D32),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
indicatorColor: Colors.white,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelColor: Colors.white60,
|
||||
tabs: const [
|
||||
Tab(icon: Icon(Icons.play_circle_outline_rounded), text: 'Tutorial'),
|
||||
Tab(icon: Icon(Icons.menu_book_rounded), text: 'Guias'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_TutorialView(),
|
||||
_GuiasView(
|
||||
categorias: _categorias,
|
||||
categoriaFiltro: _categoriaFiltro,
|
||||
articulos: _articulosFiltrados,
|
||||
onFiltro: (c) => setState(() => _categoriaFiltro = c),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// TAB 1: TUTORIAL INTERACTIVO
|
||||
// ================================================================
|
||||
class _TutorialView extends StatefulWidget {
|
||||
@override
|
||||
State<_TutorialView> createState() => _TutorialViewState();
|
||||
}
|
||||
|
||||
class _TutorialViewState extends State<_TutorialView> {
|
||||
final PageController _pageCtrl = PageController();
|
||||
int _paginaActual = 0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _siguiente() {
|
||||
if (_paginaActual < _pasosTutorial.length - 1) {
|
||||
_pageCtrl.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
|
||||
void _anterior() {
|
||||
if (_paginaActual > 0) {
|
||||
_pageCtrl.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// Indicador de progreso
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||
child: Row(
|
||||
children: List.generate(_pasosTutorial.length, (i) {
|
||||
final activo = i == _paginaActual;
|
||||
final completado = i < _paginaActual;
|
||||
return Expanded(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: completado
|
||||
? const Color(0xFF2E7D32)
|
||||
: activo
|
||||
? _pasosTutorial[_paginaActual].color
|
||||
: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Paso ${_paginaActual + 1} de ${_pasosTutorial.length}',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
Text(
|
||||
_paginaActual == _pasosTutorial.length - 1 ? 'Completado' : '',
|
||||
style: const TextStyle(color: Color(0xFF2E7D32), fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Páginas del tutorial
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
controller: _pageCtrl,
|
||||
itemCount: _pasosTutorial.length,
|
||||
onPageChanged: (i) => setState(() => _paginaActual = i),
|
||||
itemBuilder: (context, i) => _PaginaTutorial(paso: _pasosTutorial[i]),
|
||||
),
|
||||
),
|
||||
|
||||
// Controles de navegación
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
|
||||
child: Row(
|
||||
children: [
|
||||
if (_paginaActual > 0)
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _anterior,
|
||||
icon: const Icon(Icons.arrow_back_rounded, size: 18),
|
||||
label: const Text('Anterior'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_paginaActual > 0) const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: _paginaActual == 0 ? 1 : 1,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _paginaActual == _pasosTutorial.length - 1 ? null : _siguiente,
|
||||
icon: Icon(
|
||||
_paginaActual == _pasosTutorial.length - 1
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.arrow_forward_rounded,
|
||||
size: 18,
|
||||
),
|
||||
label: Text(
|
||||
_paginaActual == _pasosTutorial.length - 1 ? 'Listo' : 'Siguiente',
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _pasosTutorial[_paginaActual].color,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PaginaTutorial extends StatelessWidget {
|
||||
final _PasoTutorial paso;
|
||||
const _PaginaTutorial({required this.paso});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
// Imagen con overlay de icono
|
||||
Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Image.asset(
|
||||
paso.imagenAsset,
|
||||
height: 220,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
height: 220,
|
||||
decoration: BoxDecoration(
|
||||
color: paso.color.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Icon(paso.icono, size: 80, color: paso.color.withValues(alpha: 0.4)),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Badge del icono
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
child: Container(
|
||||
width: 52, height: 52,
|
||||
decoration: BoxDecoration(
|
||||
color: paso.color,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [BoxShadow(color: paso.color.withValues(alpha: 0.4), blurRadius: 12)],
|
||||
),
|
||||
child: Icon(paso.icono, color: Colors.white, size: 26),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Titulo
|
||||
Text(
|
||||
paso.titulo,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: paso.color, height: 1.2),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Descripcion
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 8)],
|
||||
),
|
||||
child: Text(
|
||||
paso.descripcion,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 15, color: Color(0xFF444444), height: 1.6),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// TAB 2: GUIAS DE INFORMACION
|
||||
// ================================================================
|
||||
class _GuiasView extends StatelessWidget {
|
||||
final List<String> categorias;
|
||||
final String? categoriaFiltro;
|
||||
final List<_Articulo> articulos;
|
||||
final void Function(String?) onFiltro;
|
||||
|
||||
const _GuiasView({
|
||||
required this.categorias,
|
||||
required this.categoriaFiltro,
|
||||
required this.articulos,
|
||||
required this.onFiltro,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// Filtros de categoria
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
children: [
|
||||
_FiltroChip(
|
||||
label: 'Todas',
|
||||
seleccionado: categoriaFiltro == null,
|
||||
color: const Color(0xFF2E7D32),
|
||||
onTap: () => onFiltro(null),
|
||||
),
|
||||
...categorias.map((cat) {
|
||||
final a = _articulos.firstWhere((x) => x.categoria == cat, orElse: () => _articulos.first);
|
||||
return _FiltroChip(
|
||||
label: cat,
|
||||
seleccionado: categoriaFiltro == cat,
|
||||
color: a.color,
|
||||
onTap: () => onFiltro(cat),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Lista de articulos
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 24),
|
||||
itemCount: articulos.length,
|
||||
itemBuilder: (context, i) => _TarjetaArticulo(
|
||||
articulo: articulos[i],
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => _DetalleArticuloScreen(articulo: articulos[i])),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// WIDGET: Chip de filtro
|
||||
// ================================================================
|
||||
class _FiltroChip extends StatelessWidget {
|
||||
final String label;
|
||||
final bool seleccionado;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _FiltroChip({required this.label, required this.seleccionado, required this.color, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: seleccionado ? color : Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: color, width: 1.5),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(color: seleccionado ? Colors.white : color, fontWeight: FontWeight.w600, fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// WIDGET: Tarjeta de articulo
|
||||
// ================================================================
|
||||
class _TarjetaArticulo extends StatelessWidget {
|
||||
final _Articulo articulo;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _TarjetaArticulo({required this.articulo, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.06), blurRadius: 8, offset: const Offset(0, 2))],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Imagen lateral
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(left: Radius.circular(16)),
|
||||
child: Image.asset(
|
||||
articulo.imagenAsset,
|
||||
width: 90,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
width: 90, height: 100,
|
||||
color: articulo.color.withValues(alpha: 0.1),
|
||||
child: Icon(Icons.eco_rounded, color: articulo.color, size: 36),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Contenido
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: articulo.color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(articulo.categoria,
|
||||
style: TextStyle(color: articulo.color, fontSize: 10, fontWeight: FontWeight.w700)),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(articulo.titulo,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A)),
|
||||
maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 4),
|
||||
Text(articulo.resumen,
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600, height: 1.3),
|
||||
maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: Icon(Icons.chevron_right_rounded, color: Colors.grey.shade400),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// PANTALLA DE DETALLE
|
||||
// ================================================================
|
||||
class _DetalleArticuloScreen extends StatelessWidget {
|
||||
final _Articulo articulo;
|
||||
const _DetalleArticuloScreen({required this.articulo});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F7F5),
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
expandedHeight: 220,
|
||||
pinned: true,
|
||||
backgroundColor: articulo.color,
|
||||
foregroundColor: Colors.white,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: Text(
|
||||
articulo.titulo,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
|
||||
maxLines: 2,
|
||||
),
|
||||
background: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Image.asset(
|
||||
articulo.imagenAsset,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(color: articulo.color),
|
||||
),
|
||||
// Gradiente oscuro para legibilidad del titulo
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Colors.transparent, articulo.color.withValues(alpha: 0.85)],
|
||||
stops: const [0.4, 1.0],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Badge categoria
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: articulo.color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(articulo.categoria,
|
||||
style: TextStyle(color: articulo.color, fontWeight: FontWeight.w700, fontSize: 12)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Resumen
|
||||
Text(articulo.resumen,
|
||||
style: const TextStyle(fontSize: 15, color: Color(0xFF333333), height: 1.5)),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Secciones
|
||||
...articulo.contenido.map((s) => _SeccionCard(seccion: s, color: articulo.color)),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Consejo destacado
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: articulo.color,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.lightbulb_outline_rounded, color: Colors.white.withValues(alpha: 0.9), size: 20),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Consejo rapido',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(articulo.consejoRapido,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14, height: 1.5)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SeccionCard extends StatelessWidget {
|
||||
final _Subseccion seccion;
|
||||
final Color color;
|
||||
const _SeccionCard({required this.seccion, required this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border(left: BorderSide(color: color, width: 4)),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 6, offset: const Offset(0, 2))],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(seccion.subtitulo,
|
||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: color)),
|
||||
const SizedBox(height: 6),
|
||||
Text(seccion.texto,
|
||||
style: const TextStyle(fontSize: 13, color: Color(0xFF444444), height: 1.5)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
402
HackOnLinces_app/aplicacion_hack/lib/screens/login_screen.dart
Normal file
@@ -0,0 +1,402 @@
|
||||
// ================================================================
|
||||
// lib/screens/login_screen.dart — EcoTrack
|
||||
// ================================================================
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../services/api_service.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final _emailCtrl = TextEditingController();
|
||||
final _passwordCtrl = TextEditingController();
|
||||
final _nameCtrl = TextEditingController();
|
||||
final _dirCtrl = TextEditingController();
|
||||
|
||||
String? _coloniaSeleccionada;
|
||||
List<String> _colonias = [];
|
||||
bool _esRegistro = false;
|
||||
bool _cargandoColonias = true;
|
||||
String? _errorColonias;
|
||||
bool _logueando = false;
|
||||
bool _mostrarPassword = false;
|
||||
|
||||
final ApiService _apiService = ApiService();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_cargarColonias();
|
||||
_verificarSesion();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailCtrl.dispose();
|
||||
_passwordCtrl.dispose();
|
||||
_nameCtrl.dispose();
|
||||
_dirCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _verificarSesion() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final id = prefs.getInt('usuario_id');
|
||||
if (id != null && mounted) {
|
||||
Navigator.pushReplacementNamed(context, '/home', arguments: id);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cargarColonias() async {
|
||||
try {
|
||||
final colonias = await _apiService.obtenerColonias();
|
||||
if (mounted) setState(() { _colonias = colonias; _cargandoColonias = false; });
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_colonias = ['Zona Centro', 'Las Arboledas', 'Trojes', 'San Juanico', 'Los Olivos', 'Rancho Seco', 'Las Insurgentes'];
|
||||
_cargandoColonias = false;
|
||||
_errorColonias = 'Sin conexión al backend. Usando lista local.';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _iniciarSesion() async {
|
||||
final email = _emailCtrl.text.trim();
|
||||
final password = _passwordCtrl.text;
|
||||
if (email.isEmpty) { _error('Ingresa tu correo.'); return; }
|
||||
if (password.isEmpty) { _error('Ingresa tu contraseña.'); return; }
|
||||
|
||||
setState(() => _logueando = true);
|
||||
try {
|
||||
final r = await _apiService.loginConCorreo(email, password);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('usuario_id', r['usuario_id']);
|
||||
await prefs.setString('nombre', r['nombre'] ?? '');
|
||||
await prefs.setString('email', email);
|
||||
if (mounted) Navigator.pushReplacementNamed(context, '/home', arguments: r['usuario_id']);
|
||||
} catch (e) {
|
||||
_error(e.toString().replaceFirst('Exception: ', ''));
|
||||
} finally {
|
||||
if (mounted) setState(() => _logueando = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _registrarse() async {
|
||||
final nombre = _nameCtrl.text.trim();
|
||||
final email = _emailCtrl.text.trim();
|
||||
final password = _passwordCtrl.text;
|
||||
final dir = _dirCtrl.text.trim();
|
||||
|
||||
if (nombre.isEmpty) { _error('Ingresa tu nombre.'); return; }
|
||||
if (email.isEmpty) { _error('Ingresa tu correo.'); return; }
|
||||
if (password.length < 6) { _error('Contraseña de al menos 6 caracteres.'); return; }
|
||||
if (_coloniaSeleccionada == null) { _error('Selecciona tu colonia.'); return; }
|
||||
if (dir.isEmpty) { _error('Ingresa tu dirección.'); return; }
|
||||
|
||||
setState(() => _logueando = true);
|
||||
try {
|
||||
final id = await _apiService.registrarUsuario(nombre, email, password, dir, _coloniaSeleccionada!);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('usuario_id', id);
|
||||
await prefs.setString('nombre', nombre);
|
||||
await prefs.setString('email', email);
|
||||
if (mounted) Navigator.pushReplacementNamed(context, '/home', arguments: id);
|
||||
} catch (e) {
|
||||
_error(e.toString().replaceFirst('Exception: ', ''));
|
||||
} finally {
|
||||
if (mounted) setState(() => _logueando = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _error(String msg) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(msg),
|
||||
backgroundColor: Colors.red.shade700,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
));
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// BUILD
|
||||
// ================================================================
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Theme.of(context).colorScheme.primary;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF6FAF6),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── LOGO ─────────────────────────────────────────────
|
||||
Center(
|
||||
child: Container(
|
||||
width: 84, height: 84,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
boxShadow: [BoxShadow(color: color.withValues(alpha: 0.3), blurRadius: 20, offset: const Offset(0, 8))],
|
||||
),
|
||||
child: const Icon(Icons.recycling_rounded, size: 48, color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text('EcoTrack',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 32, fontWeight: FontWeight.w900, color: color, letterSpacing: -0.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text('Recolección inteligente de residuos',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// ── TABS LOGIN / REGISTRO ─────────────────────────────
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_Tab(label: 'Iniciar sesión', seleccionado: !_esRegistro, color: color,
|
||||
onTap: () => setState(() { _esRegistro = false; _passwordCtrl.clear(); })),
|
||||
_Tab(label: 'Registrarse', seleccionado: _esRegistro, color: color,
|
||||
onTap: () => setState(() { _esRegistro = true; _passwordCtrl.clear(); })),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ── CAMPOS COMUNES ────────────────────────────────────
|
||||
if (_esRegistro) ...[
|
||||
_Campo(ctrl: _nameCtrl, label: 'Nombre completo', icon: Icons.person_outline),
|
||||
const SizedBox(height: 14),
|
||||
],
|
||||
_Campo(ctrl: _emailCtrl, label: 'Correo electrónico', icon: Icons.email_outlined, tipo: TextInputType.emailAddress),
|
||||
const SizedBox(height: 14),
|
||||
_Campo(
|
||||
ctrl: _passwordCtrl,
|
||||
label: 'Contraseña',
|
||||
icon: Icons.lock_outline,
|
||||
obscure: !_mostrarPassword,
|
||||
sufijo: IconButton(
|
||||
icon: Icon(_mostrarPassword ? Icons.visibility_off : Icons.visibility, size: 20),
|
||||
onPressed: () => setState(() => _mostrarPassword = !_mostrarPassword),
|
||||
),
|
||||
),
|
||||
|
||||
// ── CAMPOS EXTRA REGISTRO ─────────────────────────────
|
||||
if (_esRegistro) ...[
|
||||
const SizedBox(height: 14),
|
||||
_Campo(ctrl: _dirCtrl, label: 'Dirección', icon: Icons.home_outlined),
|
||||
const SizedBox(height: 14),
|
||||
if (_cargandoColonias)
|
||||
const Center(child: Padding(padding: EdgeInsets.all(12), child: CircularProgressIndicator()))
|
||||
else ...[
|
||||
if (_errorColonias != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Text(_errorColonias!, style: TextStyle(fontSize: 11, color: Colors.orange.shade700)),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: _coloniaSeleccionada,
|
||||
hint: const Text('Selecciona tu colonia'),
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.location_city_outlined),
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
),
|
||||
items: _colonias.map((c) => DropdownMenuItem(value: c, child: Text(c))).toList(),
|
||||
onChanged: (v) => setState(() => _coloniaSeleccionada = v),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
_IndicadorFortaleza(password: _passwordCtrl.text),
|
||||
],
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ── BOTÓN PRINCIPAL ───────────────────────────────────
|
||||
SizedBox(
|
||||
height: 52,
|
||||
child: ElevatedButton(
|
||||
onPressed: _logueando ? null : (_esRegistro ? _registrarse : _iniciarSesion),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: color,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
elevation: 0,
|
||||
),
|
||||
child: _logueando
|
||||
? const SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: Text(_esRegistro ? 'Crear cuenta' : 'Entrar',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
_esRegistro
|
||||
? 'Tu contraseña se almacena de forma segura con bcrypt. Minimo 6 caracteres.'
|
||||
: 'Tus datos son privados y solo se usan para notificarte cuando el camion se acerca.',
|
||||
style: TextStyle(fontSize: 11, color: color),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// WIDGETS AUXILIARES
|
||||
// ================================================================
|
||||
|
||||
class _Tab extends StatelessWidget {
|
||||
final String label;
|
||||
final bool seleccionado;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _Tab({required this.label, required this.seleccionado, required this.color, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
margin: const EdgeInsets.all(4),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: seleccionado ? Colors.white : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
boxShadow: seleccionado ? [BoxShadow(color: Colors.black.withValues(alpha: 0.08), blurRadius: 6)] : [],
|
||||
),
|
||||
child: Text(label,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: seleccionado ? FontWeight.bold : FontWeight.normal,
|
||||
color: seleccionado ? color : Colors.grey.shade500,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Campo extends StatelessWidget {
|
||||
final TextEditingController ctrl;
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final bool obscure;
|
||||
final TextInputType tipo;
|
||||
final Widget? sufijo;
|
||||
|
||||
const _Campo({
|
||||
required this.ctrl,
|
||||
required this.label,
|
||||
required this.icon,
|
||||
this.obscure = false,
|
||||
this.tipo = TextInputType.text,
|
||||
this.sufijo,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: ctrl,
|
||||
obscureText: obscure,
|
||||
keyboardType: tipo,
|
||||
textCapitalization: tipo == TextInputType.text ? TextCapitalization.words : TextCapitalization.none,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
prefixIcon: Icon(icon, size: 20),
|
||||
suffixIcon: sufijo,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200)),
|
||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200)),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Theme.of(context).colorScheme.primary, width: 1.5)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _IndicadorFortaleza extends StatelessWidget {
|
||||
final String password;
|
||||
const _IndicadorFortaleza({required this.password});
|
||||
|
||||
(int, String, Color) _evaluar() {
|
||||
if (password.isEmpty) return (0, '', Colors.grey);
|
||||
int pts = 0;
|
||||
if (password.length >= 8) pts++;
|
||||
if (password.contains(RegExp(r'[A-Z]'))) pts++;
|
||||
if (password.contains(RegExp(r'[0-9]'))) pts++;
|
||||
if (password.contains(RegExp(r'[!@#\$%^&*]'))) pts++;
|
||||
if (pts <= 1) return (1, 'Debil', Colors.red);
|
||||
if (pts == 2) return (2, 'Regular', Colors.orange);
|
||||
if (pts == 3) return (3, 'Buena', Colors.lightGreen);
|
||||
return (4, 'Excelente', Colors.green);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (password.isEmpty) return const SizedBox.shrink();
|
||||
final (nivel, etiqueta, color) = _evaluar();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: List.generate(4, (i) => Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: i < nivel ? color : Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
))),
|
||||
const SizedBox(height: 4),
|
||||
Text('Contrasena: $etiqueta', style: TextStyle(fontSize: 11, color: color)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
// ================================================================
|
||||
// lib/screens/mapa_rutas_screen.dart (v2 — flutter_map)
|
||||
// Mapa real de Celaya con rutas estilo metro sobre OpenStreetMap
|
||||
// ================================================================
|
||||
//
|
||||
// DEPENDENCIAS (agregar en pubspec.yaml):
|
||||
// flutter_map: ^7.0.2
|
||||
// latlong2: ^0.9.1
|
||||
//
|
||||
// SIN API KEY — usa tiles de OpenStreetMap (gratis, libre)
|
||||
//
|
||||
// FILTRADO:
|
||||
// Solo las rutas del usuario se ven en color vivo.
|
||||
// Las demás se muestran al 20% de opacidad.
|
||||
// ================================================================
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../services/api_service.dart';
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// DATOS DE RUTAS
|
||||
// ----------------------------------------------------------------
|
||||
class _RutaInfo {
|
||||
final String routeId;
|
||||
final String nombre;
|
||||
final String colonia;
|
||||
final Color color;
|
||||
final List<LatLng> puntos;
|
||||
|
||||
const _RutaInfo({
|
||||
required this.routeId,
|
||||
required this.nombre,
|
||||
required this.colonia,
|
||||
required this.color,
|
||||
required this.puntos,
|
||||
});
|
||||
}
|
||||
|
||||
final _todasLasRutas = [
|
||||
_RutaInfo(
|
||||
routeId: 'RUTA-01',
|
||||
nombre: 'Zona Centro - Las Arboledas',
|
||||
colonia: 'Zona Centro',
|
||||
color: const Color(0xFF1565C0),
|
||||
puntos: [
|
||||
LatLng(20.5111, -100.9037),
|
||||
LatLng(20.5185, -100.8450),
|
||||
LatLng(20.5215, -100.8142),
|
||||
LatLng(20.5212, -100.8175),
|
||||
LatLng(20.5210, -100.8210),
|
||||
LatLng(20.5235, -100.8212),
|
||||
LatLng(20.5260, -100.8215),
|
||||
LatLng(20.5111, -100.9037),
|
||||
],
|
||||
),
|
||||
_RutaInfo(
|
||||
routeId: 'RUTA-03',
|
||||
nombre: 'Sector Poniente - San Juanico',
|
||||
colonia: 'San Juanico',
|
||||
color: const Color(0xFF2E7D32),
|
||||
puntos: [
|
||||
LatLng(20.5111, -100.9037),
|
||||
LatLng(20.5250, -100.8510),
|
||||
LatLng(20.5290, -100.8320),
|
||||
LatLng(20.5315, -100.8355),
|
||||
LatLng(20.5340, -100.8390),
|
||||
LatLng(20.5362, -100.8425),
|
||||
LatLng(20.5330, -100.8430),
|
||||
LatLng(20.5111, -100.9037),
|
||||
],
|
||||
),
|
||||
_RutaInfo(
|
||||
routeId: 'RUTA-04',
|
||||
nombre: 'Oriente - Los Olivos',
|
||||
colonia: 'Los Olivos',
|
||||
color: const Color(0xFFE65100),
|
||||
puntos: [
|
||||
LatLng(20.5111, -100.9037),
|
||||
LatLng(20.5260, -100.8010),
|
||||
LatLng(20.5295, -100.7890),
|
||||
LatLng(20.5320, -100.7850),
|
||||
LatLng(20.5350, -100.7790),
|
||||
LatLng(20.5310, -100.7760),
|
||||
LatLng(20.5270, -100.7820),
|
||||
LatLng(20.5111, -100.9037),
|
||||
],
|
||||
),
|
||||
_RutaInfo(
|
||||
routeId: 'RUTA-05',
|
||||
nombre: 'Sector Sur - Rancho Seco',
|
||||
colonia: 'Rancho Seco',
|
||||
color: const Color(0xFF6A1B9A),
|
||||
puntos: [
|
||||
LatLng(20.5111, -100.9037),
|
||||
LatLng(20.5050, -100.8620),
|
||||
LatLng(20.5020, -100.8350),
|
||||
LatLng(20.4995, -100.8210),
|
||||
LatLng(20.4970, -100.8150),
|
||||
LatLng(20.5010, -100.8120),
|
||||
LatLng(20.5060, -100.8160),
|
||||
LatLng(20.5111, -100.9037),
|
||||
],
|
||||
),
|
||||
_RutaInfo(
|
||||
routeId: 'RUTA-12',
|
||||
nombre: 'Nororiente - Las Insurgentes',
|
||||
colonia: 'Las Insurgentes',
|
||||
color: const Color(0xFFC62828),
|
||||
puntos: [
|
||||
LatLng(20.5111, -100.9037),
|
||||
LatLng(20.5280, -100.8080),
|
||||
LatLng(20.5320, -100.7980),
|
||||
LatLng(20.5340, -100.7940),
|
||||
LatLng(20.5360, -100.7900),
|
||||
LatLng(20.5310, -100.7920),
|
||||
LatLng(20.5270, -100.8020),
|
||||
LatLng(20.5111, -100.9037),
|
||||
],
|
||||
),
|
||||
_RutaInfo(
|
||||
routeId: 'RUTA-13',
|
||||
nombre: 'Sector Norte - Trojes e Irrigación',
|
||||
colonia: 'Trojes',
|
||||
color: const Color(0xFF00838F),
|
||||
puntos: [
|
||||
LatLng(20.5111, -100.9037),
|
||||
LatLng(20.5360, -100.8190),
|
||||
LatLng(20.5420, -100.8080),
|
||||
LatLng(20.5440, -100.8040),
|
||||
LatLng(20.5460, -100.8000),
|
||||
LatLng(20.5410, -100.8020),
|
||||
LatLng(20.5370, -100.8120),
|
||||
LatLng(20.5111, -100.9037),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
const _coloniaARuta = {
|
||||
'Zona Centro': 'RUTA-01',
|
||||
'Las Arboledas': 'RUTA-01',
|
||||
'San Juanico': 'RUTA-03',
|
||||
'Los Olivos': 'RUTA-04',
|
||||
'Rancho Seco': 'RUTA-05',
|
||||
'Las Insurgentes': 'RUTA-12',
|
||||
'Trojes': 'RUTA-13',
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// PANTALLA
|
||||
// ================================================================
|
||||
class MapaRutasScreen extends StatefulWidget {
|
||||
const MapaRutasScreen({super.key});
|
||||
|
||||
@override
|
||||
State<MapaRutasScreen> createState() => _MapaRutasScreenState();
|
||||
}
|
||||
|
||||
class _MapaRutasScreenState extends State<MapaRutasScreen> {
|
||||
Set<String> _rutasDelUsuario = {};
|
||||
List<RouteInfo> _estadosBackend = [];
|
||||
String? _rutaSeleccionada;
|
||||
bool _cargando = true;
|
||||
bool _mostrarTodas = false;
|
||||
|
||||
final _mapController = MapController();
|
||||
final ApiService _apiService = ApiService();
|
||||
|
||||
static const _centro = LatLng(20.5230, -100.8550);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_cargarDatos();
|
||||
}
|
||||
|
||||
Future<void> _cargarDatos() async {
|
||||
setState(() => _cargando = true);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final usuarioId = prefs.getInt('usuario_id');
|
||||
if (usuarioId == null) {
|
||||
if (mounted) setState(() => _cargando = false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final rutas = await _apiService.obtenerRutas(usuarioId);
|
||||
final usuario = await _apiService.obtenerUsuario(usuarioId);
|
||||
final ids = <String>{};
|
||||
for (final d in usuario.direcciones) {
|
||||
final r = _coloniaARuta[d.colonia];
|
||||
if (r != null) ids.add(r);
|
||||
}
|
||||
for (final r in rutas) ids.add(r.routeId);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_estadosBackend = rutas;
|
||||
_rutasDelUsuario = ids;
|
||||
_cargando = false;
|
||||
});
|
||||
}
|
||||
} catch (_) {
|
||||
if (mounted) setState(() => _cargando = false);
|
||||
}
|
||||
}
|
||||
|
||||
RouteInfo? _estadoRuta(String routeId) {
|
||||
try {
|
||||
return _estadosBackend.firstWhere((r) => r.routeId == routeId);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Mapa de rutas — Celaya'),
|
||||
backgroundColor: const Color(0xFF1B5E20),
|
||||
foregroundColor: Colors.white,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(_mostrarTodas ? Icons.visibility_off : Icons.visibility),
|
||||
tooltip: _mostrarTodas ? 'Solo mis rutas' : 'Ver todas',
|
||||
onPressed: () => setState(() => _mostrarTodas = !_mostrarTodas),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.my_location),
|
||||
onPressed: () => _mapController.move(_centro, 12.5),
|
||||
),
|
||||
IconButton(icon: const Icon(Icons.refresh), onPressed: _cargarDatos),
|
||||
],
|
||||
),
|
||||
body: _cargando
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
children: [
|
||||
Expanded(flex: 3, child: _buildMapa()),
|
||||
_buildLeyenda(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMapa() {
|
||||
return FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: const MapOptions(
|
||||
initialCenter: _centro,
|
||||
initialZoom: 12.5,
|
||||
minZoom: 11,
|
||||
maxZoom: 17,
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'com.example.aplicacion_hack',
|
||||
maxZoom: 19,
|
||||
),
|
||||
PolylineLayer(polylines: _buildPolylines()),
|
||||
MarkerLayer(markers: _buildMarkers()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Polyline> _buildPolylines() {
|
||||
final list = <Polyline>[];
|
||||
for (final ruta in _todasLasRutas) {
|
||||
final esDelUsuario = _rutasDelUsuario.contains(ruta.routeId);
|
||||
if (!_mostrarTodas && !esDelUsuario) continue;
|
||||
|
||||
final estado = _estadoRuta(ruta.routeId);
|
||||
final posActual = estado?.lastPositionId ?? 0;
|
||||
final seleccionada = _rutaSeleccionada == ruta.routeId;
|
||||
final grosorBase = esDelUsuario ? 5.0 : 2.0;
|
||||
final grosor = seleccionada ? grosorBase + 2 : grosorBase;
|
||||
|
||||
if (esDelUsuario && posActual > 1) {
|
||||
// Tramo recorrido
|
||||
final recorridos = ruta.puntos.take(posActual).toList();
|
||||
if (recorridos.length >= 2) {
|
||||
list.add(Polyline(
|
||||
points: recorridos,
|
||||
color: ruta.color,
|
||||
strokeWidth: grosor,
|
||||
));
|
||||
}
|
||||
// Tramo pendiente (punteado)
|
||||
final pendientes = ruta.puntos.skip(posActual - 1).toList();
|
||||
if (pendientes.length >= 2) {
|
||||
list.add(Polyline(
|
||||
points: pendientes,
|
||||
color: ruta.color.withValues(alpha: 0.35),
|
||||
strokeWidth: grosor - 1,
|
||||
pattern: StrokePattern.dashed(segments: [12, 8]),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
list.add(Polyline(
|
||||
points: ruta.puntos,
|
||||
color: esDelUsuario ? ruta.color : ruta.color.withValues(alpha: 0.2),
|
||||
strokeWidth: grosor,
|
||||
));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
List<Marker> _buildMarkers() {
|
||||
final list = <Marker>[];
|
||||
|
||||
// Base / Relleno Sanitario
|
||||
list.add(Marker(
|
||||
point: LatLng(20.5111, -100.9037),
|
||||
width: 70,
|
||||
height: 36,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade800,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: const Text('🏭 Base',
|
||||
style: TextStyle(color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
Container(width: 2, height: 4, color: Colors.grey.shade800),
|
||||
Container(
|
||||
width: 8, height: 8,
|
||||
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
for (final ruta in _todasLasRutas) {
|
||||
final esDelUsuario = _rutasDelUsuario.contains(ruta.routeId);
|
||||
if (!esDelUsuario) continue;
|
||||
|
||||
final estado = _estadoRuta(ruta.routeId);
|
||||
final posActual = estado?.lastPositionId ?? 0;
|
||||
|
||||
for (int i = 1; i < ruta.puntos.length - 1; i++) {
|
||||
final punto = ruta.puntos[i];
|
||||
final posId = i + 1;
|
||||
final esCamion = posId == posActual;
|
||||
|
||||
if (esCamion) {
|
||||
list.add(Marker(
|
||||
point: punto,
|
||||
width: 46,
|
||||
height: 54,
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() =>
|
||||
_rutaSeleccionada = _rutaSeleccionada == ruta.routeId ? null : ruta.routeId),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: ruta.color,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
boxShadow: [BoxShadow(color: ruta.color.withValues(alpha: 0.5), blurRadius: 6)],
|
||||
),
|
||||
child: Text(
|
||||
ruta.routeId.replaceAll('RUTA-', 'R'),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 8, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const Text('🚛', style: TextStyle(fontSize: 20)),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
} else {
|
||||
list.add(Marker(
|
||||
point: punto,
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() =>
|
||||
_rutaSeleccionada = _rutaSeleccionada == ruta.routeId ? null : ruta.routeId),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: posId < posActual ? ruta.color : Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: ruta.color, width: 2.5),
|
||||
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 2)],
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
Widget _buildLeyenda() {
|
||||
final rutasUsuario = _todasLasRutas
|
||||
.where((r) => _rutasDelUsuario.contains(r.routeId))
|
||||
.toList();
|
||||
|
||||
if (rutasUsuario.isEmpty) {
|
||||
return Container(
|
||||
height: 52,
|
||||
color: const Color(0xFF1B5E20),
|
||||
alignment: Alignment.center,
|
||||
child: const Text('Sin rutas asignadas',
|
||||
style: TextStyle(color: Colors.white54, fontSize: 13)),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: 96,
|
||||
color: const Color(0xFF1B5E20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 5, 0, 2),
|
||||
child: Text(
|
||||
_mostrarTodas ? 'Todas las rutas • Las tuyas están resaltadas' : 'Tus rutas — toca para centrar',
|
||||
style: const TextStyle(color: Colors.white54, fontSize: 10),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3),
|
||||
itemCount: rutasUsuario.length,
|
||||
itemBuilder: (context, i) {
|
||||
final ruta = rutasUsuario[i];
|
||||
final estado = _estadoRuta(ruta.routeId);
|
||||
final posActual = estado?.lastPositionId ?? 0;
|
||||
final status = estado?.status ?? 'EN_RUTA';
|
||||
final seleccionada = _rutaSeleccionada == ruta.routeId;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() =>
|
||||
_rutaSeleccionada = seleccionada ? null : ruta.routeId);
|
||||
if (!seleccionada && ruta.puntos.length > 1) {
|
||||
_mapController.move(ruta.puntos[1], 13.5);
|
||||
}
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: seleccionada
|
||||
? ruta.color.withValues(alpha: 0.3)
|
||||
: Colors.white.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: seleccionada ? ruta.color : Colors.white24,
|
||||
width: seleccionada ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: ClipRect(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 9, height: 9,
|
||||
decoration: BoxDecoration(color: ruta.color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(ruta.routeId,
|
||||
style: TextStyle(color: ruta.color, fontWeight: FontWeight.bold, fontSize: 11)),
|
||||
const SizedBox(width: 4),
|
||||
Text(status == 'COMPLETADO' ? '✅' : '🚛',
|
||||
style: const TextStyle(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
SizedBox(
|
||||
width: 115,
|
||||
child: Text(ruta.colonia,
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 10),
|
||||
overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
SizedBox(
|
||||
width: 115,
|
||||
height: 3,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
child: LinearProgressIndicator(
|
||||
value: (posActual / 8).clamp(0.0, 1.0),
|
||||
backgroundColor: Colors.white12,
|
||||
valueColor: AlwaysStoppedAnimation(ruta.color),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
Text(
|
||||
posActual > 0 ? 'Pos. $posActual / 8' : 'Sin datos',
|
||||
style: TextStyle(color: ruta.color.withValues(alpha: 0.8), fontSize: 9),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
538
HackOnLinces_app/aplicacion_hack/lib/screens/reporte_screen.dart
Normal file
@@ -0,0 +1,538 @@
|
||||
// ================================================================
|
||||
// lib/screens/reporte_screen.dart
|
||||
// Pantalla para que el ciudadano envíe reportes manuales
|
||||
// ================================================================
|
||||
//
|
||||
// NAVEGAR DESDE home_screen.dart:
|
||||
// Navigator.pushNamed(context, '/reporte')
|
||||
//
|
||||
// AGREGAR EN main.dart:
|
||||
// '/reporte': (context) => const ReporteScreen(),
|
||||
//
|
||||
// SECCIONES:
|
||||
// 1. Formulario de nuevo reporte
|
||||
// 2. Historial de reportes del usuario
|
||||
// ================================================================
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// MODELOS
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
class ReporteInfo {
|
||||
final int reporteId;
|
||||
final String fecha;
|
||||
final String hora;
|
||||
final String colonia;
|
||||
final String tipo;
|
||||
final String? descripcion;
|
||||
final String estado;
|
||||
|
||||
ReporteInfo({
|
||||
required this.reporteId,
|
||||
required this.fecha,
|
||||
required this.hora,
|
||||
required this.colonia,
|
||||
required this.tipo,
|
||||
this.descripcion,
|
||||
required this.estado,
|
||||
});
|
||||
|
||||
factory ReporteInfo.fromJson(Map<String, dynamic> j) => ReporteInfo(
|
||||
reporteId: j['reporte_id'],
|
||||
fecha: j['fecha'],
|
||||
hora: j['hora'],
|
||||
colonia: j['colonia'],
|
||||
tipo: j['tipo'],
|
||||
descripcion: j['descripcion'],
|
||||
estado: j['estado'],
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// DATOS LOCALES
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
const _tiposReporte = [
|
||||
{'valor': 'CAMION_NO_PASO', 'label': 'El camión no pasó', 'emoji': '🚫', 'color': 0xFFC62828},
|
||||
{'valor': 'VOLUMEN_ALTO', 'label': 'Volumen inusualmente alto', 'emoji': '📦', 'color': 0xFFE65100},
|
||||
{'valor': 'BASURA_FUERA_HORARIO', 'label': 'Basura fuera de horario', 'emoji': '⏰', 'color': 0xFFF57F17},
|
||||
{'valor': 'OTRO', 'label': 'Otro problema', 'emoji': '📝', 'color': 0xFF37474F},
|
||||
];
|
||||
|
||||
const _colonias = [
|
||||
'Zona Centro', 'Las Arboledas', 'Trojes',
|
||||
'San Juanico', 'Los Olivos', 'Rancho Seco', 'Las Insurgentes',
|
||||
];
|
||||
|
||||
// ================================================================
|
||||
// PANTALLA PRINCIPAL
|
||||
// ================================================================
|
||||
class ReporteScreen extends StatefulWidget {
|
||||
const ReporteScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ReporteScreen> createState() => _ReporteScreenState();
|
||||
}
|
||||
|
||||
class _ReporteScreenState extends State<ReporteScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static const _baseUrl = 'http://192.168.198.55:8000';
|
||||
|
||||
late TabController _tabController;
|
||||
int? _usuarioId;
|
||||
|
||||
// Formulario
|
||||
String? _tipoSeleccionado;
|
||||
String? _coloniaSeleccionada;
|
||||
final _descController = TextEditingController();
|
||||
bool _enviando = false;
|
||||
|
||||
// Historial
|
||||
List<ReporteInfo> _reportes = [];
|
||||
bool _cargandoHistorial = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
_cargarUsuario();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
_descController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _cargarUsuario() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final id = prefs.getInt('usuario_id');
|
||||
if (id != null && mounted) {
|
||||
setState(() => _usuarioId = id);
|
||||
_cargarHistorial();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cargarHistorial() async {
|
||||
if (_usuarioId == null) return;
|
||||
setState(() => _cargandoHistorial = true);
|
||||
try {
|
||||
final resp = await http
|
||||
.get(Uri.parse('$_baseUrl/api/reportes/usuario/$_usuarioId'))
|
||||
.timeout(const Duration(seconds: 10));
|
||||
if (resp.statusCode == 200 && mounted) {
|
||||
final lista = json.decode(resp.body) as List;
|
||||
setState(() {
|
||||
_reportes = lista.map((r) => ReporteInfo.fromJson(r)).toList();
|
||||
_cargandoHistorial = false;
|
||||
});
|
||||
} else {
|
||||
if (mounted) setState(() => _cargandoHistorial = false);
|
||||
}
|
||||
} catch (_) {
|
||||
if (mounted) setState(() => _cargandoHistorial = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _enviarReporte() async {
|
||||
if (_tipoSeleccionado == null) {
|
||||
_snack('Selecciona el tipo de problema.', error: true);
|
||||
return;
|
||||
}
|
||||
if (_coloniaSeleccionada == null) {
|
||||
_snack('Selecciona la colonia.', error: true);
|
||||
return;
|
||||
}
|
||||
if (_usuarioId == null) {
|
||||
_snack('No hay sesión activa.', error: true);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _enviando = true);
|
||||
|
||||
try {
|
||||
final resp = await http.post(
|
||||
Uri.parse('$_baseUrl/api/reportes?usuario_id=$_usuarioId'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({
|
||||
'colonia': _coloniaSeleccionada,
|
||||
'tipo': _tipoSeleccionado,
|
||||
'descripcion': _descController.text.trim().isEmpty
|
||||
? null
|
||||
: _descController.text.trim(),
|
||||
}),
|
||||
).timeout(const Duration(seconds: 10));
|
||||
|
||||
if (resp.statusCode == 200 && mounted) {
|
||||
_snack('✅ Reporte enviado correctamente. ¡Gracias!');
|
||||
setState(() {
|
||||
_tipoSeleccionado = null;
|
||||
_coloniaSeleccionada = null;
|
||||
});
|
||||
_descController.clear();
|
||||
_cargarHistorial();
|
||||
_tabController.animateTo(1); // ir al historial
|
||||
} else {
|
||||
final body = json.decode(resp.body);
|
||||
_snack(body['detail'] ?? 'Error al enviar el reporte.', error: true);
|
||||
}
|
||||
} catch (e) {
|
||||
_snack('Sin conexión al servidor.', error: true);
|
||||
} finally {
|
||||
if (mounted) setState(() => _enviando = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _snack(String msg, {bool error = false}) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(msg),
|
||||
backgroundColor: error ? Colors.red.shade700 : Colors.green.shade700,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
));
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// BUILD
|
||||
// ================================================================
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F7F5),
|
||||
appBar: AppBar(
|
||||
title: const Text('Reportes ciudadanos'),
|
||||
backgroundColor: const Color(0xFF1B5E20),
|
||||
foregroundColor: Colors.white,
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
indicatorColor: Colors.white,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelColor: Colors.white60,
|
||||
tabs: const [
|
||||
Tab(icon: Icon(Icons.add_circle_outline), text: 'Nuevo reporte'),
|
||||
Tab(icon: Icon(Icons.history), text: 'Mis reportes'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [_buildFormulario(), _buildHistorial()],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// TAB 1: FORMULARIO
|
||||
// ================================================================
|
||||
Widget _buildFormulario() {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Encabezado
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2E7D32).withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: const Color(0xFF2E7D32).withValues(alpha: 0.2)),
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('📋 Envía un reporte',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 6),
|
||||
Text(
|
||||
'Tu reporte se guarda en la base de datos y ayuda a mejorar '
|
||||
'la logística de recolección en tu colonia.',
|
||||
style: TextStyle(fontSize: 13, color: Colors.black54, height: 1.4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
_Label('¿Qué problema ocurrió?'),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Selector de tipo
|
||||
...(_tiposReporte.map((tipo) {
|
||||
final seleccionado = _tipoSeleccionado == tipo['valor'];
|
||||
final color = Color(tipo['color'] as int);
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _tipoSeleccionado = tipo['valor'] as String),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: seleccionado ? color.withValues(alpha: 0.1) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: seleccionado ? color : Colors.grey.shade200,
|
||||
width: seleccionado ? 2 : 1,
|
||||
),
|
||||
boxShadow: seleccionado
|
||||
? [BoxShadow(color: color.withValues(alpha: 0.15), blurRadius: 8)]
|
||||
: [BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 4)],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(tipo['emoji'] as String, style: const TextStyle(fontSize: 22)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
tipo['label'] as String,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: seleccionado ? FontWeight.bold : FontWeight.normal,
|
||||
color: seleccionado ? color : Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (seleccionado)
|
||||
Icon(Icons.check_circle, color: color, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
})),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
_Label('¿En qué colonia?'),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Selector de colonia
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: _coloniaSeleccionada,
|
||||
hint: const Text('Selecciona tu colonia'),
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.location_city_outlined),
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
),
|
||||
items: _colonias
|
||||
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
|
||||
.toList(),
|
||||
onChanged: (v) => setState(() => _coloniaSeleccionada = v),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
_Label('Descripción adicional (opcional)'),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _descController,
|
||||
maxLines: 4,
|
||||
maxLength: 300,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Describe el problema con más detalle...',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 28),
|
||||
|
||||
// Botón enviar
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 54,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _enviando ? null : _enviarReporte,
|
||||
icon: _enviando
|
||||
? const SizedBox(
|
||||
width: 20, height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Icon(Icons.send_rounded),
|
||||
label: Text(_enviando ? 'Enviando...' : 'Enviar reporte'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2E7D32),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: Text(
|
||||
'Tu reporte es anónimo para el operador y ayuda\na mejorar el servicio de recolección.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// TAB 2: HISTORIAL
|
||||
// ================================================================
|
||||
Widget _buildHistorial() {
|
||||
if (_cargandoHistorial) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (_reportes.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.inbox_outlined, size: 72, color: Colors.grey.shade300),
|
||||
const SizedBox(height: 16),
|
||||
const Text('No has enviado reportes aún',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey)),
|
||||
const SizedBox(height: 8),
|
||||
const Text('Usa la pestaña anterior para reportar un problema.',
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: _cargarHistorial,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: _reportes.length,
|
||||
itemBuilder: (context, i) => _TarjetaReporte(reporte: _reportes[i]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// WIDGETS AUXILIARES
|
||||
// ================================================================
|
||||
|
||||
class _Label extends StatelessWidget {
|
||||
final String text;
|
||||
const _Label(this.text);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||
);
|
||||
}
|
||||
|
||||
class _TarjetaReporte extends StatelessWidget {
|
||||
final ReporteInfo reporte;
|
||||
const _TarjetaReporte({required this.reporte});
|
||||
|
||||
Map<String, dynamic> get _tipoInfo {
|
||||
return _tiposReporte.firstWhere(
|
||||
(t) => t['valor'] == reporte.tipo,
|
||||
orElse: () => {'label': reporte.tipo, 'emoji': '📝', 'color': 0xFF37474F},
|
||||
);
|
||||
}
|
||||
|
||||
Color get _colorEstado => reporte.estado == 'ATENDIDO'
|
||||
? const Color(0xFF2E7D32)
|
||||
: const Color(0xFFE65100);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final info = _tipoInfo;
|
||||
final color = Color(info['color'] as int);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 6)
|
||||
],
|
||||
border: Border(left: BorderSide(color: color, width: 4)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(info['emoji'] as String, style: const TextStyle(fontSize: 20)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(info['label'] as String,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, color: color, fontSize: 14)),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: _colorEstado.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
reporte.estado == 'ATENDIDO' ? '✅ Atendido' : '⏳ Pendiente',
|
||||
style: TextStyle(
|
||||
color: _colorEstado, fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.location_city_outlined, size: 14, color: Colors.grey.shade500),
|
||||
const SizedBox(width: 4),
|
||||
Text(reporte.colonia,
|
||||
style: TextStyle(color: Colors.grey.shade600, fontSize: 13)),
|
||||
const SizedBox(width: 16),
|
||||
Icon(Icons.calendar_today_outlined, size: 14, color: Colors.grey.shade500),
|
||||
const SizedBox(width: 4),
|
||||
Text('${reporte.fecha} ${reporte.hora.substring(0, 5)}',
|
||||
style: TextStyle(color: Colors.grey.shade600, fontSize: 13)),
|
||||
],
|
||||
),
|
||||
if (reporte.descripcion != null && reporte.descripcion!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
reporte.descripcion!,
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey.shade700, height: 1.4),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../services/api_service.dart';
|
||||
|
||||
class RouteListScreen extends StatefulWidget {
|
||||
const RouteListScreen({super.key});
|
||||
|
||||
@override
|
||||
State<RouteListScreen> createState() => _RouteListScreenState();
|
||||
}
|
||||
|
||||
class _RouteListScreenState extends State<RouteListScreen> {
|
||||
final ApiService _apiService = ApiService();
|
||||
bool _cargando = true;
|
||||
bool _avanzando = false;
|
||||
String? _error;
|
||||
List<RouteInfo> _rutas = [];
|
||||
int? _usuarioId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_cargarUsuarioYRutas();
|
||||
}
|
||||
|
||||
Future<void> _cargarUsuarioYRutas() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final usuarioId = prefs.getInt('usuario_id');
|
||||
|
||||
if (usuarioId == null) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_error =
|
||||
'No se encontró sesión activa. Por favor inicia sesión de nuevo.';
|
||||
_cargando = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_usuarioId = usuarioId;
|
||||
await _cargarRutas();
|
||||
}
|
||||
|
||||
Future<void> _cargarRutas() async {
|
||||
setState(() {
|
||||
_cargando = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
if (_usuarioId == null) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_error =
|
||||
'No se encontró sesión activa. Por favor inicia sesión de nuevo.';
|
||||
_cargando = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final rutas = await _apiService.obtenerRutas(_usuarioId!);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_rutas = rutas;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_error = 'No se pudieron cargar las rutas. Verifica el backend.';
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_cargando = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _simularAvance(String routeId) async {
|
||||
setState(() {
|
||||
_avanzando = true;
|
||||
});
|
||||
|
||||
if (_usuarioId == null) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content:
|
||||
Text('No se encontró sesión activa. Inicia sesión de nuevo.'),
|
||||
backgroundColor: Colors.redAccent,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final rutaActualizada =
|
||||
await _apiService.avanzarRuta(routeId, _usuarioId!);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
final index = _rutas.indexWhere((r) => r.routeId == routeId);
|
||||
if (index >= 0) {
|
||||
_rutas[index] = rutaActualizada;
|
||||
}
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'La ruta ${rutaActualizada.routeId} avanzó al siguiente tramo.'),
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error al simular avance: $e'),
|
||||
backgroundColor: Colors.red.shade700,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_avanzando = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Color _colorEstado(String status) {
|
||||
switch (status) {
|
||||
case 'COMPLETADO':
|
||||
return Colors.blue.shade600;
|
||||
case 'EN_RUTA':
|
||||
return Colors.green.shade600;
|
||||
default:
|
||||
return Colors.orange.shade600;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Rutas y estado del camión'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pushNamed(context, '/mapa'),
|
||||
icon: const Icon(Icons.map_rounded),
|
||||
tooltip: 'Ver mapa de rutas',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _cargarRutas,
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _cargando
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error_outline,
|
||||
size: 72, color: Colors.redAccent),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_error!,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _cargarRutas,
|
||||
child: const Text('Reintentar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: RefreshIndicator(
|
||||
onRefresh: _cargarRutas,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade50,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.green.shade200),
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Simulación de rutas',
|
||||
style: TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
'Esta pantalla muestra el estado actual de cada camión y permite simular el siguiente tramo de la ruta para pruebas.',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
..._rutas.map((ruta) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 14),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
ruta.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(ruta.status),
|
||||
backgroundColor:
|
||||
_colorEstado(ruta.status)
|
||||
.withValues(alpha: 0.15),
|
||||
labelStyle: TextStyle(
|
||||
color: _colorEstado(ruta.status),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
runSpacing: 6,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildTag('Ruta: ${ruta.routeId}',
|
||||
Colors.grey.shade200),
|
||||
_buildTag(
|
||||
'Posición: ${ruta.lastPositionId}',
|
||||
Colors.blue.shade50),
|
||||
_buildTag(
|
||||
ruta.gpsOk
|
||||
? 'GPS OK'
|
||||
: 'GPS desconectado',
|
||||
ruta.gpsOk
|
||||
? Colors.green.shade50
|
||||
: Colors.red.shade50,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Último reporte: ${ruta.lastTimestamp}',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade700,
|
||||
fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: ruta.status == 'COMPLETADO' ||
|
||||
_avanzando
|
||||
? null
|
||||
: () => _simularAvance(ruta.routeId),
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
label: const Text('Simular avance'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Información relevante',
|
||||
style: TextStyle(
|
||||
fontSize: 17, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
'• Separa correctamente orgánicos, reciclables y no reciclables.',
|
||||
style: TextStyle(fontSize: 14)),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'• No mezcles basura húmeda con envases secos y mantén los líquidos controlados.',
|
||||
style: TextStyle(fontSize: 14)),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'• Saca tu basura cuando el camión esté cerca: así evitamos plagas y malos olores.',
|
||||
style: TextStyle(fontSize: 14)),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'• Usa bolsas resistentes y cierra bien los residuos antes de ponerlos en la acera.',
|
||||
style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTag(String text, Color color) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
483
HackOnLinces_app/aplicacion_hack/lib/services/api_service.dart
Normal file
@@ -0,0 +1,483 @@
|
||||
// ================================================================
|
||||
// lib/services/api_service.dart (v2)
|
||||
// Servicio de comunicación con el backend FastAPI
|
||||
// ================================================================
|
||||
//
|
||||
// CAMBIOS v2:
|
||||
// - LoginResponse incluye 'nombre' del usuario
|
||||
// - ActualizarPassword requiere password_actual + password_nuevo
|
||||
// - Nuevos métodos: obtenerDashboard, historialPosiciones,
|
||||
// resumenRutas, estadisticasColonias
|
||||
// ================================================================
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// MODELOS
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
class ETAInfo {
|
||||
final int usuarioId;
|
||||
final String colonia;
|
||||
final String rutaNombre;
|
||||
final String rutaStatus;
|
||||
final bool gpsOk;
|
||||
final String etaTexto;
|
||||
final int etaMinutos;
|
||||
final String mensajePreventivo;
|
||||
|
||||
ETAInfo({
|
||||
required this.usuarioId,
|
||||
required this.colonia,
|
||||
required this.rutaNombre,
|
||||
required this.rutaStatus,
|
||||
required this.gpsOk,
|
||||
required this.etaTexto,
|
||||
required this.etaMinutos,
|
||||
required this.mensajePreventivo,
|
||||
});
|
||||
|
||||
factory ETAInfo.fromJson(Map<String, dynamic> json) {
|
||||
return ETAInfo(
|
||||
usuarioId: json['usuario_id'],
|
||||
colonia: json['colonia'],
|
||||
rutaNombre: json['ruta_nombre'] ?? '',
|
||||
rutaStatus: json['ruta_status'] ?? '',
|
||||
gpsOk: json['gps_ok'] ?? true,
|
||||
etaTexto: json['eta_texto'],
|
||||
etaMinutos: json['eta_minutos'],
|
||||
mensajePreventivo: json['mensaje_preventivo'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DireccionInfo {
|
||||
final String colonia;
|
||||
final String direccion;
|
||||
|
||||
DireccionInfo({required this.colonia, required this.direccion});
|
||||
|
||||
factory DireccionInfo.fromJson(Map<String, dynamic> json) {
|
||||
return DireccionInfo(colonia: json['colonia'], direccion: json['direccion']);
|
||||
}
|
||||
}
|
||||
|
||||
class UsuarioInfo {
|
||||
final int usuarioId;
|
||||
final String nombre;
|
||||
final String email;
|
||||
final List<DireccionInfo> direcciones;
|
||||
|
||||
UsuarioInfo({
|
||||
required this.usuarioId,
|
||||
required this.nombre,
|
||||
required this.email,
|
||||
required this.direcciones,
|
||||
});
|
||||
|
||||
factory UsuarioInfo.fromJson(Map<String, dynamic> json) {
|
||||
return UsuarioInfo(
|
||||
usuarioId: json['usuario_id'],
|
||||
nombre: json['nombre'],
|
||||
email: json['email'],
|
||||
direcciones: List<Map<String, dynamic>>.from(json['direcciones'])
|
||||
.map(DireccionInfo.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RouteInfo {
|
||||
final String routeId;
|
||||
final String name;
|
||||
final String status;
|
||||
final int lastPositionId;
|
||||
final String lastTimestamp;
|
||||
final bool gpsOk;
|
||||
|
||||
RouteInfo({
|
||||
required this.routeId,
|
||||
required this.name,
|
||||
required this.status,
|
||||
required this.lastPositionId,
|
||||
required this.lastTimestamp,
|
||||
required this.gpsOk,
|
||||
});
|
||||
|
||||
factory RouteInfo.fromJson(Map<String, dynamic> json) {
|
||||
return RouteInfo(
|
||||
routeId: json['route_id'],
|
||||
name: json['name'],
|
||||
status: json['status'],
|
||||
lastPositionId: json['last_position_id'],
|
||||
lastTimestamp: json['last_timestamp'],
|
||||
gpsOk: json['gps_ok'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Posición GPS individual de la ruta de un camión.
|
||||
class PosicionGPS {
|
||||
final int positionId;
|
||||
final double lat;
|
||||
final double lng;
|
||||
final int speed;
|
||||
final String timestamp;
|
||||
final bool esActual; // true = aquí está el camión ahora
|
||||
|
||||
PosicionGPS({
|
||||
required this.positionId,
|
||||
required this.lat,
|
||||
required this.lng,
|
||||
required this.speed,
|
||||
required this.timestamp,
|
||||
required this.esActual,
|
||||
});
|
||||
|
||||
factory PosicionGPS.fromJson(Map<String, dynamic> json) {
|
||||
return PosicionGPS(
|
||||
positionId: json['position_id'],
|
||||
lat: (json['lat'] as num).toDouble(),
|
||||
lng: (json['lng'] as num).toDouble(),
|
||||
speed: json['speed'],
|
||||
timestamp: json['timestamp'],
|
||||
esActual: json['es_actual'] ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Detalle de una ruta para el dashboard.
|
||||
class RutaDetalle {
|
||||
final String routeId;
|
||||
final String name;
|
||||
final String status;
|
||||
final int truckId;
|
||||
final int posicionActual;
|
||||
final int totalPosiciones;
|
||||
final double porcentajeCompletado;
|
||||
final int etaMinutos;
|
||||
final bool gpsOk;
|
||||
final int usuariosEnRuta;
|
||||
|
||||
RutaDetalle({
|
||||
required this.routeId,
|
||||
required this.name,
|
||||
required this.status,
|
||||
required this.truckId,
|
||||
required this.posicionActual,
|
||||
required this.totalPosiciones,
|
||||
required this.porcentajeCompletado,
|
||||
required this.etaMinutos,
|
||||
required this.gpsOk,
|
||||
required this.usuariosEnRuta,
|
||||
});
|
||||
|
||||
factory RutaDetalle.fromJson(Map<String, dynamic> json) {
|
||||
return RutaDetalle(
|
||||
routeId: json['route_id'],
|
||||
name: json['name'],
|
||||
status: json['status'],
|
||||
truckId: json['truck_id'],
|
||||
posicionActual: json['posicion_actual'],
|
||||
totalPosiciones: json['total_posiciones'],
|
||||
porcentajeCompletado: (json['porcentaje_completado'] as num).toDouble(),
|
||||
etaMinutos: json['eta_minutos'],
|
||||
gpsOk: json['gps_ok'],
|
||||
usuariosEnRuta: json['usuarios_en_ruta'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Respuesta completa del dashboard de operador.
|
||||
class DashboardInfo {
|
||||
final int totalRutas;
|
||||
final int rutasEnProgreso;
|
||||
final int rutasCompletadas;
|
||||
final int totalUsuarios;
|
||||
final int usuariosConToken;
|
||||
final double coberturaNotificaciones;
|
||||
final List<RutaDetalle> rutas;
|
||||
|
||||
DashboardInfo({
|
||||
required this.totalRutas,
|
||||
required this.rutasEnProgreso,
|
||||
required this.rutasCompletadas,
|
||||
required this.totalUsuarios,
|
||||
required this.usuariosConToken,
|
||||
required this.coberturaNotificaciones,
|
||||
required this.rutas,
|
||||
});
|
||||
|
||||
factory DashboardInfo.fromJson(Map<String, dynamic> json) {
|
||||
return DashboardInfo(
|
||||
totalRutas: json['total_rutas'],
|
||||
rutasEnProgreso: json['rutas_en_progreso'],
|
||||
rutasCompletadas: json['rutas_completadas'],
|
||||
totalUsuarios: json['total_usuarios'],
|
||||
usuariosConToken: json['usuarios_con_token'],
|
||||
coberturaNotificaciones: (json['cobertura_notificaciones'] as num).toDouble(),
|
||||
rutas: List<Map<String, dynamic>>.from(json['rutas'])
|
||||
.map(RutaDetalle.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Estadísticas de una colonia.
|
||||
class ColoniaEstadistica {
|
||||
final String colonia;
|
||||
final String routeId;
|
||||
final String rutaNombre;
|
||||
final String horario;
|
||||
final int totalUsuarios;
|
||||
final int usuariosConNotificaciones;
|
||||
|
||||
ColoniaEstadistica({
|
||||
required this.colonia,
|
||||
required this.routeId,
|
||||
required this.rutaNombre,
|
||||
required this.horario,
|
||||
required this.totalUsuarios,
|
||||
required this.usuariosConNotificaciones,
|
||||
});
|
||||
|
||||
factory ColoniaEstadistica.fromJson(Map<String, dynamic> json) {
|
||||
return ColoniaEstadistica(
|
||||
colonia: json['colonia'],
|
||||
routeId: json['route_id'],
|
||||
rutaNombre: json['ruta_nombre'],
|
||||
horario: json['horario'],
|
||||
totalUsuarios: json['total_usuarios'],
|
||||
usuariosConNotificaciones: json['usuarios_con_notificaciones'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// CLASE PRINCIPAL: ApiService
|
||||
// ----------------------------------------------------------------
|
||||
class ApiService {
|
||||
// ============================================================
|
||||
// BASE URL — Cambia solo esta línea para apuntar a otro entorno
|
||||
// Android emulator local: http://10.0.2.2:8000
|
||||
// Dispositivo físico (red local): http://192.168.X.X:8000
|
||||
// ============================================================
|
||||
static const String _baseUrl = 'http://192.168.198.55:8000';
|
||||
static const Duration _timeout = Duration(seconds: 10);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// HELPER PRIVADO: maneja errores HTTP de forma consistente
|
||||
// ----------------------------------------------------------------
|
||||
Never _throwError(http.Response response) {
|
||||
Map<String, dynamic> body = {};
|
||||
try {
|
||||
body = json.decode(response.body);
|
||||
} catch (_) {}
|
||||
final detail = body['detail'] ?? response.body;
|
||||
throw Exception(detail);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// AUTENTICACIÓN
|
||||
// ================================================================
|
||||
|
||||
/// Login con email y contraseña. Retorna [usuarioId, nombre].
|
||||
Future<Map<String, dynamic>> loginConCorreo(String email, String password) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('$_baseUrl/api/usuarios/login'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({'email': email.trim().toLowerCase(), 'password': password}),
|
||||
).timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
return {'usuario_id': data['usuario_id'], 'nombre': data['nombre']};
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
Future<int> registrarUsuario(
|
||||
String nombre,
|
||||
String email,
|
||||
String password,
|
||||
String direccion,
|
||||
String colonia,
|
||||
) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('$_baseUrl/api/usuarios/register'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({
|
||||
'nombre': nombre.trim(),
|
||||
'email': email.trim().toLowerCase(),
|
||||
'password': password,
|
||||
'colonia': colonia,
|
||||
'direccion': direccion.trim(),
|
||||
}),
|
||||
).timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return json.decode(response.body)['usuario_id'];
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// USUARIOS
|
||||
// ================================================================
|
||||
|
||||
Future<ETAInfo> obtenerETA(int usuarioId) async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/eta/$usuarioId'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return ETAInfo.fromJson(json.decode(response.body));
|
||||
} else if (response.statusCode == 404) {
|
||||
throw Exception('Usuario no encontrado. ¿Corriste /api/seed en el backend?');
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
Future<List<String>> obtenerColonias() async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/colonias'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return List<String>.from(json.decode(response.body)['colonias']);
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
Future<UsuarioInfo> obtenerUsuario(int usuarioId) async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/usuarios/$usuarioId'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return UsuarioInfo.fromJson(json.decode(response.body));
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
Future<void> agregarDireccion(int usuarioId, String colonia, String direccion) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('$_baseUrl/api/usuarios/$usuarioId/direcciones'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({'colonia': colonia, 'direccion': direccion.trim()}),
|
||||
).timeout(_timeout);
|
||||
|
||||
if (response.statusCode != 200) _throwError(response);
|
||||
}
|
||||
|
||||
/// Actualiza contraseña. Requiere la contraseña actual como confirmación.
|
||||
Future<void> actualizarPassword(int usuarioId, String passwordActual, String passwordNuevo) async {
|
||||
final response = await http.put(
|
||||
Uri.parse('$_baseUrl/api/usuarios/$usuarioId/password'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({
|
||||
'password_actual': passwordActual,
|
||||
'password_nuevo': passwordNuevo,
|
||||
}),
|
||||
).timeout(_timeout);
|
||||
|
||||
if (response.statusCode != 200) _throwError(response);
|
||||
}
|
||||
|
||||
Future<void> registrarFcmToken(int usuarioId, String fcmToken) async {
|
||||
final response = await http.put(
|
||||
Uri.parse('$_baseUrl/api/usuarios/$usuarioId/fcm-token'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({'fcm_token': fcmToken}),
|
||||
).timeout(_timeout);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Error registrando FCM token: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// RUTAS
|
||||
// ================================================================
|
||||
|
||||
Future<List<RouteInfo>> obtenerRutas(int usuarioId) async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/rutas?usuario_id=$usuarioId'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return List<Map<String, dynamic>>.from(json.decode(response.body)['rutas'])
|
||||
.map(RouteInfo.fromJson)
|
||||
.toList();
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
Future<RouteInfo> avanzarRuta(String routeId, int usuarioId) async {
|
||||
final response = await http
|
||||
.post(Uri.parse('$_baseUrl/api/rutas/$routeId/avanzar?usuario_id=$usuarioId'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return RouteInfo.fromJson(json.decode(response.body));
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// VISUALIZACIÓN — NUEVOS EN v2
|
||||
// ================================================================
|
||||
|
||||
/// Dashboard global: estado de todas las rutas + métricas de usuarios.
|
||||
Future<DashboardInfo> obtenerDashboard() async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/dashboard'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return DashboardInfo.fromJson(json.decode(response.body));
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
/// Historial de posiciones GPS de una ruta, con la posición actual marcada.
|
||||
Future<List<PosicionGPS>> historialPosiciones(String routeId) async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/rutas/$routeId/posiciones'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return List<Map<String, dynamic>>.from(json.decode(response.body))
|
||||
.map(PosicionGPS.fromJson)
|
||||
.toList();
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
/// Vista rápida y ligera de todas las rutas. Ideal para polling frecuente.
|
||||
Future<List<Map<String, dynamic>>> resumenRutas() async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/rutas/resumen'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return List<Map<String, dynamic>>.from(json.decode(response.body)['rutas']);
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
|
||||
/// Estadísticas por colonia: usuarios y cobertura de notificaciones.
|
||||
Future<List<ColoniaEstadistica>> estadisticasColonias() async {
|
||||
final response = await http
|
||||
.get(Uri.parse('$_baseUrl/api/estadisticas/colonias'))
|
||||
.timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return List<Map<String, dynamic>>.from(json.decode(response.body))
|
||||
.map(ColoniaEstadistica.fromJson)
|
||||
.toList();
|
||||
}
|
||||
_throwError(response);
|
||||
}
|
||||
}
|
||||
1
HackOnLinces_app/aplicacion_hack/linux/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
128
HackOnLinces_app/aplicacion_hack/linux/CMakeLists.txt
Normal file
@@ -0,0 +1,128 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "aplicacion_hack")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "com.example.aplicacion_hack")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Root filesystem for cross-building.
|
||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
# Define build configuration options.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# By default, "installing" just makes a relocatable bundle in the build
|
||||
# directory.
|
||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
# Start with a clean build bundle directory every time.
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endforeach(bundled_library)
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
@@ -0,0 +1,88 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
26
HackOnLinces_app/aplicacion_hack/linux/runner/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME in the
|
||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||
# work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add preprocessor definitions for the application ID.
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Add dependency libraries. Add any application-specific dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
6
HackOnLinces_app/aplicacion_hack/linux/runner/main.cc
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
148
HackOnLinces_app/aplicacion_hack/linux/runner/my_application.cc
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Called when first Flutter frame received.
|
||||
static void first_frame_cb(MyApplication* self, FlView* view) {
|
||||
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
|
||||
}
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "aplicacion_hack");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "aplicacion_hack");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(
|
||||
project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
GdkRGBA background_color;
|
||||
// Background defaults to black, override it here if necessary, e.g. #00000000
|
||||
// for transparent.
|
||||
gdk_rgba_parse(&background_color, "#000000");
|
||||
fl_view_set_background_color(view, &background_color);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
// Show the window when Flutter renders.
|
||||
// Requires the view to be realized so we can start rendering.
|
||||
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
|
||||
self);
|
||||
gtk_widget_realize(GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application,
|
||||
gchar*** arguments,
|
||||
int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line =
|
||||
my_application_local_command_line;
|
||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
// Set the program name to the application ID, which helps various systems
|
||||
// like GTK and desktop environments map this running application to its
|
||||
// corresponding .desktop file. This ensures better integration by allowing
|
||||
// the application to be recognized beyond its binary name.
|
||||
g_set_prgname(APPLICATION_ID);
|
||||
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID, "flags",
|
||||
G_APPLICATION_NON_UNIQUE, nullptr));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication,
|
||||
my_application,
|
||||
MY,
|
||||
APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
7
HackOnLinces_app/aplicacion_hack/macos/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/dgph
|
||||
**/xcuserdata/
|
||||
@@ -0,0 +1 @@
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
@@ -0,0 +1,729 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||
buildPhases = (
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Flutter Assemble";
|
||||
productName = FLX;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||
remoteInfo = FLX;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Bundle Framework";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* aplicacion_hack.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "aplicacion_hack.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||
);
|
||||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10E42044A3C60003C045 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33FAB671232836740065AC1E /* Runner */,
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10ED2044A3C60003C045 /* aplicacion_hack.app */,
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC11242044D66E0003C045 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||
);
|
||||
name = Resources;
|
||||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||
);
|
||||
path = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33FAB671232836740065AC1E /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||
33E51914231749380026EE4D /* Release.entitlements */,
|
||||
33CC11242044D66E0003C045 /* Resources */,
|
||||
33BA886A226E78AF003329D5 /* Configs */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
productName = Runner;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* aplicacion_hack.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 33CC10EC2044A3C60003C045;
|
||||
};
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
33CC111A2044C6BA0003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 33CC10E42044A3C60003C045;
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
33CC10EC2044A3C60003C045 /* Runner */,
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */,
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C80D3294CF70F00263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||
};
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||
);
|
||||
inputPaths = (
|
||||
Flutter/ephemeral/tripwire,
|
||||
);
|
||||
outputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C80D1294CF70F00263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC10EC2044A3C60003C045 /* Runner */;
|
||||
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
33CC10F52044A3C60003C045 /* Base */,
|
||||
);
|
||||
name = MainMenu.xib;
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.aplicacionHack.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/aplicacion_hack.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/aplicacion_hack";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.aplicacionHack.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/aplicacion_hack.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/aplicacion_hack";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.aplicacionHack.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/aplicacion_hack.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/aplicacion_hack";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
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_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
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_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
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_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
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_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
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_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
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_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C80DB294CF71000263BE5 /* Debug */,
|
||||
331C80DC294CF71000263BE5 /* Release */,
|
||||
331C80DD294CF71000263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10F92044A3C60003C045 /* Debug */,
|
||||
33CC10FA2044A3C60003C045 /* Release */,
|
||||
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10FC2044A3C60003C045 /* Debug */,
|
||||
33CC10FD2044A3C60003C045 /* Release */,
|
||||
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC111C2044C6BA0003C045 /* Debug */,
|
||||
33CC111D2044C6BA0003C045 /* Release */,
|
||||
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
}
|
||||
@@ -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,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Prepare Flutter Framework Script"
|
||||
scriptText = ""$FLUTTER_ROOT"/packages/flutter_tools/bin/macos_assemble.sh prepare ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "aplicacion_hack.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "aplicacion_hack.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 = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "aplicacion_hack.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
|
||||
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 = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "aplicacion_hack.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 = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "aplicacion_hack.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
7
HackOnLinces_app/aplicacion_hack/macos/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal 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,13 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_32.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_32.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_64.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_128.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_256.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_512.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_512.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_1024.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||