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"
|
||||||
|
}
|
||||||
|
}
|
||||||