Initialisation du projet geosector complet (web + flutter)

This commit is contained in:
d6soft
2025-05-01 18:59:27 +02:00
commit b5aafc424b
244 changed files with 37296 additions and 0 deletions

23
flutt/.cline Normal file
View File

@@ -0,0 +1,23 @@
{
"memoryBank": {
"enabled": true,
"path": "./docs",
"maxContextSize": 100000
},
"contextSettings": {
"maxTokens": 100000,
"cline.maxAutoApprovedRequests": 100,
"cline.enableMemoryBank": true,
"cline.includeSnippetsFromMemory": true,
"cline.contextLength": 10000,
"cline.autoFormat": true
},
"mcpServers": {
"github.com/modelcontextprotocol/servers/tree/main/src/git": {
"command": "python",
"args": ["-m", "mcp_server_git"],
"disabled": false,
"autoApprove": []
}
}
}

16
flutt/.env-backup Normal file
View File

@@ -0,0 +1,16 @@
# Configuration pour le serveur de backup 1
BACKUP_SERVER1_IP="192.168.1.7"
BACKUP_SERVER1_PORT="22"
BACKUP_SERVER1_USER="root"
BACKUP_SERVER1_KEY="/Users/pierre/.ssh/id_rsa_mbpi"
BACKUP_SERVER1_PATH="/var/backups"
# Configuration pour le serveur de backup 2
BACKUP_SERVER2_IP="server2.example.com"
BACKUP_SERVER2_PORT="22"
BACKUP_SERVER2_USER="backup_user"
BACKUP_SERVER2_KEY="/path/to/private_key2"
BACKUP_SERVER2_PATH="/path/to/backups"
# Configuration générale
BACKUP_RETENTION_DAYS=30 # Nombre de jours de conservation des backups

15
flutt/.env-deploy-dev Normal file
View File

@@ -0,0 +1,15 @@
# Paramètres de connexion au host Debian 12
HOST_SSH_USER=debian
HOST_SSH_HOST=145.239.9.105
HOST_SSH_PORT=22
HOST_SSH_KEY=/Users/pierre/.ssh/id_rsa_mbpi
# Paramètres du container Incus
INCUS_PROJECT=DEV
INCUS_CONTAINER=d-apps
CONTAINER_USER=root
USE_SUDO=true
# Paramètres de déploiement
DEPLOY_TARGET_DIR=/var/www/geosector
FLUTTER_BUILD_DIR=build/web

47
flutt/.gitignore vendored Normal file
View File

@@ -0,0 +1,47 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
sync_config.jsonc

30
flutt/.metadata Normal file
View File

@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: ios
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

0
flutt/.windsurfrules Normal file
View File

16
flutt/README.md Normal file
View File

@@ -0,0 +1,16 @@
# geosector_app
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,57 @@
#!/usr/bin/env ruby
require 'xcodeproj'
# Ouvrir le projet
project_path = 'ios/Runner.xcodeproj'
project = Xcodeproj::Project.open(project_path)
# Trouver la cible Runner
target = project.targets.find { |t| t.name == 'Runner' }
# Parcourir toutes les configurations de build
target.build_configurations.each do |config|
# Obtenir les paramètres de build actuels
build_settings = config.build_settings
# Définir les chemins de recherche de frameworks
framework_search_paths = [
'$(inherited)',
'"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift"',
'"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus"',
'"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation"',
'"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios"',
'"${PODS_ROOT}/Flutter"',
'"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flutter"'
]
# Ajouter les chemins de recherche de frameworks
build_settings['FRAMEWORK_SEARCH_PATHS'] = framework_search_paths
# Ajouter les chemins de recherche d'en-têtes
header_search_paths = [
'$(inherited)',
'"${PODS_ROOT}/Flutter"',
'"${PODS_CONFIGURATION_BUILD_DIR}"'
]
build_settings['HEADER_SEARCH_PATHS'] = header_search_paths
# S'assurer que les modules sont activés
build_settings['DEFINES_MODULE'] = 'YES'
# Désactiver le bitcode
build_settings['ENABLE_BITCODE'] = 'NO'
# Inclure tous les assets d'icônes
build_settings['ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS'] = 'YES'
# Autres paramètres importants
build_settings['SWIFT_VERSION'] = '5.0'
build_settings['CLANG_ENABLE_MODULES'] = 'YES'
end
# Enregistrer les modifications
project.save
puts "✅ Chemins de recherche de frameworks ajoutés avec succès !"

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
flutt/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "fr.geosector.app.geosector_app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "fr.geosector.app.geosector_app"
// 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")
}
}
}
flutter {
source = "../.."
}

View File

@@ -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>

View File

@@ -0,0 +1,48 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Permissions pour la géolocalisation -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:label="geosector_app"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
<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" />
</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>

View File

@@ -0,0 +1,5 @@
package fr.geosector.app.geosector_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -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>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip

View File

@@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
include(":app")

View File

@@ -0,0 +1,802 @@
{
"v": "5.7.5",
"fr": 30,
"ip": 0,
"op": 90,
"w": 400,
"h": 400,
"nm": "GeoSector Animation",
"ddd": 0,
"assets": [],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "Earth",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 1,
"k": [
{
"i": { "x": [0.833], "y": [0.833] },
"o": { "x": [0.167], "y": [0.167] },
"t": 0,
"s": [0]
},
{
"t": 90,
"s": [360]
}
],
"ix": 10
},
"p": {
"a": 0,
"k": [200, 200, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0, 0],
"ix": 1
},
"s": {
"a": 1,
"k": [
{
"i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] },
"o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] },
"t": 0,
"s": [0, 0, 100]
},
{
"i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] },
"o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] },
"t": 15,
"s": [110, 110, 100]
},
{
"t": 25,
"s": [100, 100, 100]
}
],
"ix": 6
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"d": 1,
"ty": "el",
"s": {
"a": 0,
"k": [120, 120],
"ix": 2
},
"p": {
"a": 0,
"k": [0, 0],
"ix": 3
},
"nm": "Ellipse Path 1",
"mn": "ADBE Vector Shape - Ellipse",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.125, 0.553, 0.965, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 4,
"ix": 5
},
"lc": 1,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "Stroke 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.2, 0.6, 1, 1],
"ix": 4
},
"o": {
"a": 0,
"k": 70,
"ix": 5
},
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [0, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "Transform"
}
],
"nm": "Earth Base",
"np": 3,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0]
],
"v": [
[-60, 0],
[60, 0]
],
"c": false
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [1, 1, 1, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 30,
"ix": 4
},
"w": {
"a": 0,
"k": 2,
"ix": 5
},
"lc": 2,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "Stroke 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [0, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "Transform"
}
],
"nm": "Equator",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 2,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0]
],
"v": [
[0, -60],
[0, 60]
],
"c": false
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [1, 1, 1, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 30,
"ix": 4
},
"w": {
"a": 0,
"k": 2,
"ix": 5
},
"lc": 2,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "Stroke 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [0, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "Transform"
}
],
"nm": "Meridian",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 3,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"d": 1,
"ty": "el",
"s": {
"a": 0,
"k": [20, 20],
"ix": 2
},
"p": {
"a": 0,
"k": [40, -30],
"ix": 3
},
"nm": "Ellipse Path 1",
"mn": "ADBE Vector Shape - Ellipse",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.133, 0.624, 0.125, 1],
"ix": 4
},
"o": {
"a": 0,
"k": 100,
"ix": 5
},
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [0, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "Transform"
}
],
"nm": "Continent 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 4,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"d": 1,
"ty": "el",
"s": {
"a": 0,
"k": [25, 25],
"ix": 2
},
"p": {
"a": 0,
"k": [-35, 20],
"ix": 3
},
"nm": "Ellipse Path 1",
"mn": "ADBE Vector Shape - Ellipse",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.133, 0.624, 0.125, 1],
"ix": 4
},
"o": {
"a": 0,
"k": 100,
"ix": 5
},
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [0, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "Transform"
}
],
"nm": "Continent 2",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 5,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 90,
"st": 0,
"bm": 0
},
{
"ddd": 0,
"ind": 2,
"ty": 4,
"nm": "Marker Pin",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 1,
"k": [
{
"i": { "x": [0.833], "y": [0.833] },
"o": { "x": [0.167], "y": [0.167] },
"t": 30,
"s": [0]
},
{
"t": 40,
"s": [5]
},
{
"t": 50,
"s": [-5]
},
{
"t": 60,
"s": [0]
}
],
"ix": 10
},
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 25,
"s": [200, 80, 0],
"to": [0, 0, 0],
"ti": [0, 0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [200, 120, 0],
"to": [0, 0, 0],
"ti": [0, 0, 0]
},
{
"t": 35,
"s": [200, 110, 0]
}
],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0, 0],
"ix": 1
},
"s": {
"a": 1,
"k": [
{
"i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] },
"o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] },
"t": 20,
"s": [0, 0, 100]
},
{
"i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] },
"o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] },
"t": 30,
"s": [120, 120, 100]
},
{
"t": 35,
"s": [100, 100, 100]
}
],
"ix": 6
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[5.523, 0],
[0, 5.523],
[-5.523, 0],
[0, -5.523],
[0, 0]
],
"o": [
[0, 5.523],
[-5.523, 0],
[0, -5.523],
[5.523, 0],
[0, 0],
[0, 0]
],
"v": [
[10, -5],
[0, 5],
[-10, -5],
[0, -15],
[10, -5],
[0, 25]
],
"c": false
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.925, 0.267, 0.267, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 5,
"ix": 5
},
"lc": 2,
"lj": 2,
"bm": 0,
"nm": "Stroke 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.925, 0.267, 0.267, 1],
"ix": 4
},
"o": {
"a": 0,
"k": 100,
"ix": 5
},
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [0, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "Transform"
}
],
"nm": "Pin",
"np": 3,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 20,
"op": 90,
"st": 0,
"bm": 0
}
],
"markers": []
}

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="300" height="600" viewBox="0 0 300 600" xmlns="http://www.w3.org/2000/svg">
<!-- Fond de smartphone -->
<rect width="300" height="600" rx="30" fill="#2E4057" />
<rect x="10" y="30" width="280" height="540" rx="15" fill="#FFFFFF" />
<!-- Interface de l'application -->
<rect x="10" y="30" width="280" height="60" rx="15" fill="#048BA8" />
<circle cx="40" cy="60" r="15" fill="#FFFFFF" opacity="0.8" />
<rect x="70" y="50" width="120" height="20" rx="5" fill="#FFFFFF" opacity="0.8" />
<rect x="240" y="50" width="30" height="20" rx="5" fill="#FFFFFF" opacity="0.8" />
<!-- Carte -->
<rect x="20" y="100" width="260" height="200" rx="5" fill="#E5E7EB" />
<circle cx="150" cy="180" r="15" fill="#F18F01" stroke="#FFFFFF" stroke-width="3" />
<circle cx="90" cy="150" r="8" fill="#048BA8" stroke="#FFFFFF" stroke-width="2" />
<circle cx="210" cy="220" r="8" fill="#048BA8" stroke="#FFFFFF" stroke-width="2" />
<rect x="120" y="130" width="60" height="40" rx="3" fill="#2E4057" opacity="0.6" />
<rect x="70" y="200" width="40" height="30" rx="3" fill="#2E4057" opacity="0.6" />
<rect x="180" y="160" width="50" height="30" rx="3" fill="#2E4057" opacity="0.6" />
<!-- Liste d'emplacements -->
<rect x="20" y="310" width="260" height="60" rx="5" fill="#F9FAFB" stroke="#E5E7EB" stroke-width="1" />
<circle cx="45" cy="340" r="15" fill="#F18F01" opacity="0.8" />
<rect x="70" y="325" width="150" height="12" rx="2" fill="#2E4057" opacity="0.8" />
<rect x="70" y="345" width="100" height="10" rx="2" fill="#9CA3AF" opacity="0.6" />
<rect x="20" y="380" width="260" height="60" rx="5" fill="#F9FAFB" stroke="#E5E7EB" stroke-width="1" />
<circle cx="45" cy="410" r="15" fill="#048BA8" opacity="0.8" />
<rect x="70" y="395" width="150" height="12" rx="2" fill="#2E4057" opacity="0.8" />
<rect x="70" y="415" width="100" height="10" rx="2" fill="#9CA3AF" opacity="0.6" />
<rect x="20" y="450" width="260" height="60" rx="5" fill="#F9FAFB" stroke="#E5E7EB" stroke-width="1" />
<circle cx="45" cy="480" r="15" fill="#2E4057" opacity="0.8" />
<rect x="70" y="465" width="150" height="12" rx="2" fill="#2E4057" opacity="0.8" />
<rect x="70" y="485" width="100" height="10" rx="2" fill="#9CA3AF" opacity="0.6" />
<!-- Navigation en bas -->
<rect x="10" y="530" width="280" height="60" rx="15" fill="#F9FAFB" />
<circle cx="60" cy="560" r="15" fill="#048BA8" />
<circle cx="150" cy="560" r="15" fill="#9CA3AF" />
<circle cx="240" cy="560" r="15" fill="#9CA3AF" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="300" height="600" viewBox="0 0 300 600" xmlns="http://www.w3.org/2000/svg">
<!-- Fond de smartphone -->
<rect width="300" height="600" rx="30" fill="#2E4057" />
<rect x="10" y="30" width="280" height="540" rx="15" fill="#FFFFFF" />
<!-- Interface de l'application - écran de détail -->
<rect x="10" y="30" width="280" height="60" rx="15" fill="#048BA8" />
<rect x="20" y="50" width="20" height="20" rx="5" fill="#FFFFFF" opacity="0.8" />
<rect x="70" y="50" width="120" height="20" rx="5" fill="#FFFFFF" opacity="0.8" />
<rect x="240" y="50" width="30" height="20" rx="5" fill="#FFFFFF" opacity="0.8" />
<!-- En-tête -->
<rect x="20" y="100" width="260" height="120" rx="5" fill="#F9FAFB" />
<circle cx="70" cy="140" r="30" fill="#F18F01" opacity="0.8" />
<rect x="110" y="120" width="150" height="15" rx="2" fill="#2E4057" opacity="0.8" />
<rect x="110" y="145" width="120" height="10" rx="2" fill="#9CA3AF" opacity="0.6" />
<rect x="110" y="165" width="80" height="10" rx="2" fill="#9CA3AF" opacity="0.6" />
<!-- Données -->
<rect x="20" y="240" width="260" height="60" rx="5" fill="#F9FAFB" stroke="#E5E7EB" stroke-width="1" />
<rect x="30" y="255" width="80" height="15" rx="2" fill="#2E4057" opacity="0.6" />
<rect x="30" y="275" width="240" height="15" rx="2" fill="#048BA8" opacity="0.8" />
<rect x="20" y="310" width="260" height="60" rx="5" fill="#F9FAFB" stroke="#E5E7EB" stroke-width="1" />
<rect x="30" y="325" width="80" height="15" rx="2" fill="#2E4057" opacity="0.6" />
<rect x="30" y="345" width="180" height="15" rx="2" fill="#048BA8" opacity="0.8" />
<rect x="20" y="380" width="260" height="60" rx="5" fill="#F9FAFB" stroke="#E5E7EB" stroke-width="1" />
<rect x="30" y="395" width="80" height="15" rx="2" fill="#2E4057" opacity="0.6" />
<rect x="30" y="415" width="210" height="15" rx="2" fill="#048BA8" opacity="0.8" />
<!-- Boutons -->
<rect x="20" y="460" width="125" height="50" rx="25" fill="#F18F01" />
<rect x="60" y="480" width="45" height="10" rx="2" fill="#FFFFFF" />
<rect x="155" y="460" width="125" height="50" rx="25" fill="#2E4057" />
<rect x="195" y="480" width="45" height="10" rx="2" fill="#FFFFFF" />
<!-- Navigation en bas -->
<rect x="10" y="530" width="280" height="60" rx="15" fill="#F9FAFB" />
<circle cx="60" cy="560" r="15" fill="#9CA3AF" />
<circle cx="150" cy="560" r="15" fill="#048BA8" />
<circle cx="240" cy="560" r="15" fill="#9CA3AF" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="160" height="50" viewBox="0 0 160 50" xmlns="http://www.w3.org/2000/svg">
<!-- Fond du badge App Store -->
<rect width="160" height="50" rx="8" fill="#000000" />
<!-- Logo Apple -->
<path d="M39,16.5c1.7,0,3.4,1,4.6,2.8c-4,2.2-3.4,7.9,0.7,9.5c-0.6,1.7-1.3,3.4-2.7,5.1c-1.6,1.9-3.2,1.9-4.8,0.9c-1.6-0.9-3-0.9-4.6,0c-2.1,1.2-3.2,0.8-4.6-0.9c-2.8-3.2-3.8-9.1-1.6-13.1c1.6-2.9,4.2-3.3,6.4-1.9c1.2,0.8,2.3,0.8,3.6,0C37.3,17.5,38.2,16.5,39,16.5z" fill="#FFFFFF" />
<path d="M42.8,13.2c0,1.7-1.5,3.1-3.3,3.2c-0.1-2.5,2.2-3.7,3.3-3.9C42.8,12.5,42.8,12.8,42.8,13.2z" fill="#FFFFFF" />
<!-- Texte App Store -->
<text x="60" y="25" font-family="Arial" font-size="14" font-weight="600" fill="#FFFFFF">Download on the</text>
<text x="60" y="43" font-family="Arial" font-size="20" font-weight="800" fill="#FFFFFF">App Store</text>
</svg>

After

Width:  |  Height:  |  Size: 897 B

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="800" height="600" viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg">
<!-- Fond de carte léger -->
<rect width="800" height="600" fill="#F9FAFB" />
<!-- Grille principale de rues -->
<g stroke="#E5E7EB" stroke-width="2">
<!-- Rues horizontales -->
<line x1="0" y1="100" x2="800" y2="100" />
<line x1="0" y1="200" x2="800" y2="200" />
<line x1="0" y1="300" x2="800" y2="300" />
<line x1="0" y1="400" x2="800" y2="400" />
<line x1="0" y1="500" x2="800" y2="500" />
<!-- Rues verticales -->
<line x1="100" y1="0" x2="100" y2="600" />
<line x1="200" y1="0" x2="200" y2="600" />
<line x1="300" y1="0" x2="300" y2="600" />
<line x1="400" y1="0" x2="400" y2="600" />
<line x1="500" y1="0" x2="500" y2="600" />
<line x1="600" y1="0" x2="600" y2="600" />
<line x1="700" y1="0" x2="700" y2="600" />
</g>
<!-- Rues secondaires -->
<g stroke="#E5E7EB" stroke-width="1">
<line x1="50" y1="0" x2="50" y2="600" />
<line x1="150" y1="0" x2="150" y2="600" />
<line x1="250" y1="0" x2="250" y2="600" />
<line x1="350" y1="0" x2="350" y2="600" />
<line x1="450" y1="0" x2="450" y2="600" />
<line x1="550" y1="0" x2="550" y2="600" />
<line x1="650" y1="0" x2="650" y2="600" />
<line x1="750" y1="0" x2="750" y2="600" />
<line x1="0" y1="50" x2="800" y2="50" />
<line x1="0" y1="150" x2="800" y2="150" />
<line x1="0" y1="250" x2="800" y2="250" />
<line x1="0" y1="350" x2="800" y2="350" />
<line x1="0" y1="450" x2="800" y2="450" />
<line x1="0" y1="550" x2="800" y2="550" />
</g>
<!-- Points d'intérêt (POI) -->
<g fill="#D1D5DB">
<rect x="120" y="120" width="60" height="60" rx="5" />
<rect x="320" y="220" width="40" height="40" rx="5" />
<rect x="520" y="320" width="50" height="70" rx="5" />
<rect x="220" y="420" width="70" height="50" rx="5" />
<rect x="620" y="120" width="50" height="50" rx="5" />
<rect x="420" y="470" width="40" height="60" rx="5" />
</g>
<!-- Parcs et espaces verts -->
<g fill="#E0F2F1" opacity="0.6">
<circle cx="150" cy="350" r="40" />
<circle cx="550" cy="150" r="30" />
<circle cx="650" cy="450" r="50" />
<circle cx="350" cy="550" r="35" />
<rect x="250" y="180" width="80" height="60" rx="10" />
</g>
<!-- Routes principales -->
<g stroke="#9CA3AF" stroke-width="5" opacity="0.4">
<line x1="0" y1="300" x2="800" y2="300" />
<line x1="400" y1="0" x2="400" y2="600" />
<path d="M800,100 C600,200 300,300 0,500" fill="none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1000" height="1000" viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg">
<!-- Paramètres de base -->
<defs>
<!-- Style pour les rues principales -->
<style type="text/css">
.street-main {
stroke: rgba(46, 64, 87, 0.08);
stroke-width: 4;
fill: none;
}
.street-secondary {
stroke: rgba(46, 64, 87, 0.05);
stroke-width: 2;
fill: none;
}
.street-tertiary {
stroke: rgba(46, 64, 87, 0.03);
stroke-width: 1;
fill: none;
}
.block {
fill: rgba(4, 139, 168, 0.02);
stroke: rgba(4, 139, 168, 0.04);
stroke-width: 0.5;
}
.landmark {
fill: rgba(241, 143, 1, 0.03);
stroke: rgba(241, 143, 1, 0.06);
stroke-width: 0.5;
}
.water {
fill: rgba(4, 139, 168, 0.04);
stroke: none;
}
.park {
fill: rgba(4, 139, 168, 0.02);
stroke: rgba(4, 139, 168, 0.04);
stroke-width: 0.5;
}
</style>
</defs>
<!-- Fond transparent -->
<rect width="100%" height="100%" fill="none" />
<!-- Rivière/Étendue d'eau -->
<path class="water" d="M0,400 C100,380 200,450 300,430 C400,410 500,390 600,400 C700,410 800,440 900,430 C950,425 1000,420 1000,420 L1000,550 C950,545 900,540 850,535 C750,525 650,520 550,530 C450,540 350,560 250,550 C150,540 50,520 0,510 Z" />
<!-- Grille de rues principales -->
<line class="street-main" x1="100" y1="0" x2="100" y2="1000" />
<line class="street-main" x1="300" y1="0" x2="300" y2="1000" />
<line class="street-main" x1="500" y1="0" x2="500" y2="1000" />
<line class="street-main" x1="700" y1="0" x2="700" y2="1000" />
<line class="street-main" x1="900" y1="0" x2="900" y2="1000" />
<line class="street-main" x1="0" y1="100" x2="1000" y2="100" />
<line class="street-main" x1="0" y1="300" x2="1000" y2="300" />
<line class="street-main" x1="0" y1="600" x2="1000" y2="600" />
<line class="street-main" x1="0" y1="800" x2="1000" y2="800" />
<!-- Rues secondaires -->
<line class="street-secondary" x1="50" y1="0" x2="50" y2="1000" />
<line class="street-secondary" x1="200" y1="0" x2="200" y2="1000" />
<line class="street-secondary" x1="400" y1="0" x2="400" y2="1000" />
<line class="street-secondary" x1="600" y1="0" x2="600" y2="1000" />
<line class="street-secondary" x1="800" y1="0" x2="800" y2="1000" />
<line class="street-secondary" x1="0" y1="50" x2="1000" y2="50" />
<line class="street-secondary" x1="0" y1="200" x2="1000" y2="200" />
<line class="street-secondary" x1="0" y1="400" x2="1000" y2="400" />
<line class="street-secondary" x1="0" y1="500" x2="1000" y2="500" />
<line class="street-secondary" x1="0" y1="700" x2="1000" y2="700" />
<line class="street-secondary" x1="0" y1="900" x2="1000" y2="900" />
<!-- Rues tertiaires et ruelles -->
<line class="street-tertiary" x1="25" y1="0" x2="25" y2="1000" />
<line class="street-tertiary" x1="75" y1="0" x2="75" y2="1000" />
<line class="street-tertiary" x1="125" y1="0" x2="125" y2="1000" />
<line class="street-tertiary" x1="175" y1="0" x2="175" y2="1000" />
<line class="street-tertiary" x1="225" y1="0" x2="225" y2="1000" />
<line class="street-tertiary" x1="250" y1="0" x2="250" y2="1000" />
<line class="street-tertiary" x1="350" y1="0" x2="350" y2="1000" />
<line class="street-tertiary" x1="450" y1="0" x2="450" y2="1000" />
<line class="street-tertiary" x1="550" y1="0" x2="550" y2="1000" />
<line class="street-tertiary" x1="650" y1="0" x2="650" y2="1000" />
<line class="street-tertiary" x1="750" y1="0" x2="750" y2="1000" />
<line class="street-tertiary" x1="850" y1="0" x2="850" y2="1000" />
<line class="street-tertiary" x1="950" y1="0" x2="950" y2="1000" />
<line class="street-tertiary" x1="0" y1="25" x2="1000" y2="25" />
<line class="street-tertiary" x1="0" y1="75" x2="1000" y2="75" />
<line class="street-tertiary" x1="0" y1="125" x2="1000" y2="125" />
<line class="street-tertiary" x1="0" y1="175" x2="1000" y2="175" />
<line class="street-tertiary" x1="0" y1="225" x2="1000" y2="225" />
<line class="street-tertiary" x1="0" y1="250" x2="1000" y2="250" />
<line class="street-tertiary" x1="0" y1="350" x2="1000" y2="350" />
<line class="street-tertiary" x1="0" y1="450" x2="1000" y2="450" />
<line class="street-tertiary" x1="0" y1="550" x2="1000" y2="550" />
<line class="street-tertiary" x1="0" y1="650" x2="1000" y2="650" />
<line class="street-tertiary" x1="0" y1="750" x2="1000" y2="750" />
<line class="street-tertiary" x1="0" y1="850" x2="1000" y2="850" />
<line class="street-tertiary" x1="0" y1="950" x2="1000" y2="950" />
<!-- Blocs et bâtiments -->
<rect class="block" x="110" y="110" width="80" height="80" />
<rect class="block" x="310" y="110" width="80" height="80" />
<rect class="block" x="510" y="110" width="80" height="80" />
<rect class="block" x="710" y="110" width="80" height="80" />
<rect class="block" x="110" y="310" width="80" height="80" />
<rect class="block" x="310" y="310" width="80" height="80" />
<rect class="block" x="510" y="310" width="80" height="80" />
<rect class="block" x="710" y="310" width="80" height="80" />
<rect class="block" x="110" y="610" width="80" height="80" />
<rect class="block" x="310" y="610" width="80" height="80" />
<rect class="block" x="510" y="610" width="80" height="80" />
<rect class="block" x="710" y="610" width="80" height="80" />
<rect class="block" x="110" y="810" width="80" height="80" />
<rect class="block" x="310" y="810" width="80" height="80" />
<rect class="block" x="510" y="810" width="80" height="80" />
<rect class="block" x="710" y="810" width="80" height="80" />
<!-- Points d'intérêt -->
<circle class="landmark" cx="500" cy="200" r="30" />
<rect class="landmark" x="400" y="700" width="200" height="70" rx="10" ry="10" />
<polygon class="landmark" points="800,150 850,200 800,250 750,200" />
<!-- Parcs et espaces verts -->
<rect class="park" x="150" y="150" width="100" height="100" rx="20" ry="20" />
<rect class="park" x="650" y="450" width="100" height="100" rx="20" ry="20" />
<circle class="park" cx="250" cy="750" r="50" />
<ellipse class="park" cx="750" cy="650" rx="70" ry="40" />
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="180" height="50" viewBox="0 0 180 50" xmlns="http://www.w3.org/2000/svg">
<!-- Fond du badge Google Play -->
<rect width="180" height="50" rx="8" fill="#000000" />
<!-- Logo Google Play -->
<path d="M30,10l-15,15l15,15l3-3l-12-12l12-12L30,10z" fill="#EA4335" />
<path d="M30,10v30l12-15L30,10z" fill="#FBBC05" />
<path d="M30,10l-15,15l3,3l12-12V10z" fill="#4285F4" />
<path d="M30,40l-15-15l3-3l12,12V40z" fill="#34A853" />
<!-- Texte Google Play -->
<text x="60" y="25" font-family="Arial" font-size="14" font-weight="600" fill="#FFFFFF">GET IT ON</text>
<text x="60" y="43" font-family="Arial" font-size="20" font-weight="800" fill="#FFFFFF">Google Play</text>
</svg>

After

Width:  |  Height:  |  Size: 748 B

81
flutt/backup.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
# Chemin du projet
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_NAME="$(basename "$PROJECT_DIR")"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
BACKUP_FILENAME="${PROJECT_NAME}_${TIMESTAMP}.tar.gz"
TEMP_BACKUP_DIR="/tmp"
BACKUP_PATH="${TEMP_BACKUP_DIR}/${BACKUP_FILENAME}"
# Charger les variables d'environnement
if [ ! -f "${PROJECT_DIR}/.env-backup" ]; then
echo "❌ Fichier .env-backup manquant"
exit 1
fi
source "${PROJECT_DIR}/.env-backup"
# Fonction pour gérer les erreurs
error_exit() {
echo "$1"
exit 1
}
# Fonction pour nettoyer les anciens backups
cleanup_old_backups() {
local server_ip=$1
local server_port=$2
local server_user=$3
local server_key=$4
local server_path=$5
echo "🧹 Nettoyage des backups plus anciens que ${BACKUP_RETENTION_DAYS} jours sur $server_ip..."
ssh -i "$server_key" -p "$server_port" "$server_user@$server_ip" \
"find $server_path -name '${PROJECT_NAME}_*.tar.gz' -type f -mtime +${BACKUP_RETENTION_DAYS} -delete" || \
echo "⚠️ Avertissement: Nettoyage des anciens backups sur $server_ip échoué"
}
# Création du backup
echo "📦 Création du backup de ${PROJECT_NAME}..."
cd "${PROJECT_DIR}/.."
tar -czf "$BACKUP_PATH" \
--exclude="${PROJECT_NAME}/build" \
--exclude="${PROJECT_NAME}/.dart_tool" \
--exclude="${PROJECT_NAME}/.pub" \
--exclude="${PROJECT_NAME}/.flutter-plugins-dependencies" \
--exclude="${PROJECT_NAME}/ios/Pods" \
--exclude="${PROJECT_NAME}/android/.gradle" \
--exclude="${PROJECT_NAME}/.git" \
--exclude="${PROJECT_NAME}/node_modules" \
"${PROJECT_NAME}/" || error_exit "Création du backup échouée"
echo "✅ Backup créé: $BACKUP_PATH ($(du -h "$BACKUP_PATH" | cut -f1))"
# Transfert vers le serveur 1
echo "📤 Envoi du backup vers ${BACKUP_SERVER1_IP}..."
scp -i "$BACKUP_SERVER1_KEY" -P "$BACKUP_SERVER1_PORT" "$BACKUP_PATH" \
"${BACKUP_SERVER1_USER}@${BACKUP_SERVER1_IP}:${BACKUP_SERVER1_PATH}/" || \
error_exit "Transfert vers ${BACKUP_SERVER1_IP} échoué"
echo "✅ Backup envoyé vers ${BACKUP_SERVER1_IP}"
cleanup_old_backups "$BACKUP_SERVER1_IP" "$BACKUP_SERVER1_PORT" "$BACKUP_SERVER1_USER" \
"$BACKUP_SERVER1_KEY" "$BACKUP_SERVER1_PATH"
# Transfert vers le serveur 2
echo "📤 Envoi du backup vers ${BACKUP_SERVER2_IP}..."
scp -i "$BACKUP_SERVER2_KEY" -P "$BACKUP_SERVER2_PORT" "$BACKUP_PATH" \
"${BACKUP_SERVER2_USER}@${BACKUP_SERVER2_IP}:${BACKUP_SERVER2_PATH}/" || \
error_exit "Transfert vers ${BACKUP_SERVER2_IP} échoué"
echo "✅ Backup envoyé vers ${BACKUP_SERVER2_IP}"
cleanup_old_backups "$BACKUP_SERVER2_IP" "$BACKUP_SERVER2_PORT" "$BACKUP_SERVER2_USER" \
"$BACKUP_SERVER2_KEY" "$BACKUP_SERVER2_PATH"
# Nettoyage du fichier temporaire
echo "🧹 Suppression du fichier temporaire..."
rm "$BACKUP_PATH" || echo "⚠️ Avertissement: Suppression du fichier temporaire échouée"
echo "✅ Backup terminé avec succès à $(date '+%H:%M:%S') !"
echo "📂 Fichiers sauvegardés sur:"
echo " - ${BACKUP_SERVER1_IP}:${BACKUP_SERVER1_PATH}/${BACKUP_FILENAME}"
echo " - ${BACKUP_SERVER2_IP}:${BACKUP_SERVER2_PATH}/${BACKUP_FILENAME}"

39
flutt/clean_flutter.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
# Afficher les commandes en cours d'exécution
set -x
# Vérifier si nous sommes dans un projet Flutter
if [ ! -f "pubspec.yaml" ]; then
echo "Erreur: Assurez-vous d'être dans un projet Flutter (pubspec.yaml non trouvé)"
exit 1
fi
# Nettoyer Flutter et générer le code
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
flutter clean
# Nettoyer iOS
cd ios || exit
rm -rf Pods/
rm -rf .symlinks/
rm -f Podfile.lock
pod cache clean --all
rm -rf ~/Library/Developer/Xcode/DerivedData
# Retour au dossier racine et mise à jour des dépendances
cd ..
flutter pub get
# Réinstaller les pods iOS
cd ios || exit
pod deintegrate
pod cache clean --all
pod repo update
pod install
# Retour au dossier racine
cd ..
echo "Nettoyage terminé avec succès!"

58
flutt/deploy-dev.sh Executable file
View File

@@ -0,0 +1,58 @@
#!/bin/bash
cd /Users/pierre/dev/geosector/flutt
# Charger les variables d'environnement
if [ ! -f .env-deploy-dev ]; then
echo "❌ Fichier .env-deploy-dev manquant"
exit 1
fi
source .env-deploy-dev
# Fonction pour gérer les erreurs
error_exit() {
echo "$1"
exit 1
}
# Construction de l'application Flutter
echo "🧹 Cleaning previous builds..."
flutter clean || error_exit "Flutter clean failed"
echo "📦 Getting dependencies..."
flutter pub get || error_exit "Flutter pub get failed"
# Nettoyage et génération du code
echo "🗑️ Cleaning generated files..."
dart pub run build_runner clean || error_exit "Build runner clean failed"
echo "🔨 Generating code files..."
dart pub run build_runner build --delete-conflicting-outputs || error_exit "Code generation failed"
# Construction de l'application Flutter
echo "🏗️ Building Flutter web application..."
flutter build web --release || error_exit "Flutter build failed"
echo "✅ Build completed successfully!"
# Préparation de la commande SSH pour le host
SSH_HOST_CMD="ssh -i $HOST_SSH_KEY -p $HOST_SSH_PORT $HOST_SSH_USER@$HOST_SSH_HOST"
# Préparation du chemin temporaire sur le host
TEMP_DIR="/tmp/geosector-deploy-$(date +%s)"
echo "📤 Copie des fichiers vers le host temporairement..."
rsync -rltz --delete \
-e "ssh -i $HOST_SSH_KEY -p $HOST_SSH_PORT" \
$FLUTTER_BUILD_DIR/ \
$HOST_SSH_USER@$HOST_SSH_HOST:$TEMP_DIR/ || error_exit "Transfert vers le host échoué"
echo "🔄 Transfert des fichiers du host vers le container..."
$SSH_HOST_CMD "sudo incus project switch $INCUS_PROJECT && sudo incus file push -r $TEMP_DIR/* $INCUS_CONTAINER$DEPLOY_TARGET_DIR/" || error_exit "Transfert vers le container échoué"
echo "🧹 Nettoyage du répertoire temporaire sur le host..."
$SSH_HOST_CMD "rm -rf $TEMP_DIR"
echo "🔒 Configuration des permissions dans le container..."
$SSH_HOST_CMD "sudo incus project switch $INCUS_PROJECT && sudo incus exec $INCUS_CONTAINER -- chown -R www-data:www-data $DEPLOY_TARGET_DIR" || error_exit "Configuration des permissions échouée"
echo "✅ Déploiement terminé avec succès à $(date '+%H:%M:%S') !"

622
flutt/docs/chat.md Normal file
View File

@@ -0,0 +1,622 @@
# Solution de Chat pour Applications Flutter
## Présentation générale
Cette solution propose un système de chat personnalisé et autonome pour des applications Flutter, avec possibilité d'intégration web. Elle est conçue pour fonctionner dans deux contextes différents :
1. **Chat entre utilisateurs authentifiés** (cas Geosector) : communications one-to-one ou en groupe entre utilisateurs déjà enregistrés dans la base de données.
2. **Chat entre professionnels et visiteurs anonymes** (cas Resalice) : communications initiées par des visiteurs anonymes qui peuvent ensuite être convertis en clients référencés.
## Architecture technique
### 1. Structure générale
La solution s'articule autour de trois composants principaux :
- **Module Flutter** : Widgets et logique pour l'interface utilisateur mobile
- **Module Web** : Composants pour l'intégration web (compatible avec Flutter Web ou sites traditionnels)
- **API Backend** : Endpoints REST pour la gestion des messages et la synchronisation
### 2. Modèle de données
#### Entités principales
```
Conversation
├── id : Identifiant unique
├── type : Type de conversation (one_to_one, group, anonymous, broadcast, announcement)
├── title : Titre facultatif pour les groupes et obligatoire pour les annonces
├── reply_permission : Niveau de permission pour répondre (all, admins_only, sender_only, none)
├── created_at : Date de création
├── updated_at : Dernière mise à jour
├── is_pinned : Indique si la conversation est épinglée (pour annonces importantes)
├── expiry_date : Date d'expiration optionnelle (pour annonces temporaires)
└── participants : Liste des participants
Message
├── id : Identifiant unique
├── conversation_id : ID de la conversation
├── sender_id : ID de l'expéditeur (null pour anonyme)
├── sender_type : Type d'expéditeur (user, anonymous, system)
├── content : Contenu du message
├── content_type : Type de contenu (text, image, file)
├── created_at : Date d'envoi
├── delivered_at : Date de réception
├── read_at : Date de lecture
├── status : Statut du message (sent, delivered, read, error)
└── is_announcement : Indique s'il s'agit d'une annonce officielle
Participant
├── id : Identifiant unique
├── conversation_id : ID de la conversation
├── user_id : ID de l'utilisateur (si authentifié)
├── anonymous_id : ID anonyme (pour Resalice)
├── role : Rôle (admin, member, read_only)
├── joined_at : Date d'ajout à la conversation
├── via_target : Indique si l'utilisateur est inclus via un AudienceTarget
├── can_reply : Possibilité explicite de répondre (override de reply_permission)
└── last_read_message_id : ID du dernier message lu
AudienceTarget
├── id : Identifiant unique
├── conversation_id : ID de la conversation
├── target_type : Type de cible (role, entity, all, combined)
├── target_id : ID du rôle ou de l'entité ciblée (pour compatibility)
├── role_filter : Filtre de rôle pour le ciblage combiné ('all', '1', '2', etc.)
├── entity_filter : Filtre d'entité pour le ciblage combiné ('all', 'id_entité')
└── created_at : Date de création
AnonymousUser (pour Resalice)
├── id : Identifiant unique
├── device_id : Identifiant du dispositif
├── name : Nom temporaire (si fourni)
├── email : Email (si fourni)
├── created_at : Date de création
├── converted_to_user_id : ID utilisateur après conversion
└── metadata : Informations supplémentaires
```
#### Adaptations pour Hive
Ces modèles seront adaptés pour Hive avec leurs adaptateurs respectifs :
```dart
@HiveType(typeId: 20)
class ConversationModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String type;
@HiveField(2)
final String? title;
@HiveField(3)
final DateTime createdAt;
@HiveField(4)
final DateTime updatedAt;
@HiveField(5)
final List<ParticipantModel> participants;
@HiveField(6)
final bool isSynced;
@HiveField(7)
final String replyPermission;
@HiveField(8)
final bool isPinned;
@HiveField(9)
final DateTime? expiryDate;
// ... autres propriétés et méthodes
}
@HiveType(typeId: 21)
class MessageModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String conversationId;
@HiveField(2)
final String? senderId;
@HiveField(3)
final String senderType;
@HiveField(4)
final String content;
@HiveField(5)
final String contentType;
@HiveField(6)
final DateTime createdAt;
@HiveField(7)
final DateTime? deliveredAt;
@HiveField(8)
final DateTime? readAt;
@HiveField(9)
final String status;
@HiveField(10)
final bool isAnnouncement;
// ... autres propriétés et méthodes
}
@HiveType(typeId: 22)
class ParticipantModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String conversationId;
@HiveField(2)
final String? userId;
@HiveField(3)
final String? anonymousId;
@HiveField(4)
final String role;
@HiveField(5)
final DateTime joinedAt;
@HiveField(6)
final String? lastReadMessageId;
@HiveField(7)
final bool viaTarget;
@HiveField(8)
final bool? canReply;
// ... autres propriétés et méthodes
}
@HiveType(typeId: 23)
class AudienceTargetModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String conversationId;
@HiveField(2)
final String targetType;
@HiveField(3)
final String? targetId;
@HiveField(4)
final DateTime createdAt;
@HiveField(5)
final String? roleFilter; // 'all' ou ID de rôle
@HiveField(6)
final String? entityFilter; // 'all' ou ID d'entité
// ... autres propriétés et méthodes
}
```
### 3. Backend et API
#### Structure de l'API
L'API sera développée en PHP 8.3 pour s'intégrer avec vos systèmes existants :
```
/api/chat/conversations
GET - Liste des conversations de l'utilisateur
POST - Créer une nouvelle conversation
/api/chat/conversations/{id}
GET - Détails d'une conversation
PUT - Mettre à jour une conversation
DELETE - Supprimer une conversation
/api/chat/conversations/{id}/messages
GET - Messages d'une conversation (pagination)
POST - Envoyer un message
/api/chat/conversations/{id}/participants
GET - Liste des participants
POST - Ajouter un participant
DELETE - Retirer un participant
/api/chat/messages/{id}
PUT - Mettre à jour un message (ex: marquer comme lu)
DELETE - Supprimer un message
/api/chat/anonymous
POST - Démarrer une conversation anonyme
# Nouveaux endpoints pour les annonces
/api/chat/announcements
GET - Liste des annonces pour l'utilisateur
POST - Créer une nouvelle annonce
/api/chat/announcements/{id}/stats
GET - Obtenir les statistiques de lecture (qui a lu/non lu)
/api/chat/audience-targets
GET - Obtenir les cibles disponibles pour l'utilisateur actuel
/api/chat/conversations/{id}/pin
PUT - Épingler/désépingler une conversation
/api/chat/conversations/{id}/reply-permission
PUT - Modifier les permissions de réponse
```
#### Synchronisation
Le système supportera :
- Synchronisation en temps réel via WebSockets (optionnel)
- Synchronisation par polling avec gestion des messages non lus
- Enregistrement local des messages avec Hive pour le fonctionnement hors ligne
### 4. Widgets Flutter
#### Widgets principaux
1. **ChatScreen** : Écran principal d'une conversation
```dart
ChatScreen({
required String conversationId,
String? title,
Widget? header,
Widget? footer,
bool enableAttachments = true,
bool showTypingIndicator = true,
bool enableReadReceipts = true,
bool isAnnouncement = false,
bool canReply = true,
})
```
2. **ConversationsList** : Liste des conversations
```dart
ConversationsList({
List<ConversationModel>? conversations,
bool loadFromHive = true,
Function(ConversationModel)? onConversationSelected,
bool showLastMessage = true,
bool showUnreadCount = true,
bool showAnnouncementBadge = true,
bool showPinnedFirst = true,
Widget? emptyStateWidget,
})
```
3. **MessageBubble** : Bulle de message
```dart
MessageBubble({
required MessageModel message,
bool showSenderInfo = true,
bool showTimestamp = true,
bool showStatus = true,
bool isAnnouncement = false,
double maxWidth = 300,
})
```
4. **ChatInput** : Zone de saisie de message
```dart
ChatInput({
required Function(String) onSendText,
Function(File)? onSendFile,
Function(File)? onSendImage,
bool enableAttachments = true,
bool enabled = true,
String hintText = 'Saisissez votre message...',
String? disabledMessage = 'Vous ne pouvez pas répondre à cette annonce',
int? maxLength,
})
```
5. **AnonymousChatStarter** : Widget pour démarrer un chat anonyme (Resalice)
```dart
AnonymousChatStarter({
required Function(String?) onChatStarted,
bool requireName = false,
bool requireEmail = false,
String buttonLabel = 'Démarrer une conversation',
Widget? customForm,
})
```
6. **AnnouncementComposer** : Widget pour créer des annonces (Geosector uniquement)
```dart
AnnouncementComposer({
required Function(Map<String, dynamic>) onSend,
List<Map<String, dynamic>>? availableTargets,
String? initialTitle,
String? initialMessage,
bool allowAttachments = true,
bool allowPinning = true,
List<String> replyPermissionOptions = const ['all', 'admins_only', 'sender_only', 'none'],
String defaultReplyPermission = 'none',
DateTime? expiryDate,
bool isGeosector = true, // Active la sélection des destinataires
})
```
7. **AnnouncementTargetSelector** : Sélecteur de destinataires pour annonces (Geosector uniquement)
```dart
AnnouncementTargetSelector({
required Function(AudienceTargetModel) onTargetSelected,
required List<EntityModel> availableEntities,
bool showRoleFilter = true,
bool showEntityFilter = true,
String defaultRole = 'all',
String defaultEntity = 'all',
})
```
8. **AnnouncementBanner** : Bannière pour afficher une annonce importante
```dart
AnnouncementBanner({
required MessageModel announcement,
required Function() onView,
Function()? onDismiss,
bool isDismissible = true,
Duration? autoDismissAfter,
Color? backgroundColor,
Widget? icon,
})
```
#### Fonctionnalités des widgets
- Design adaptatif (mobile/web)
- Support des thèmes clairs/sombres
- Gestion des messages non lus
- Indicateurs de frappe
- Accusés de réception et de lecture
- Support des pièces jointes (fichiers, images)
- Recherche dans les conversations
- Conversion d'utilisateurs anonymes en clients (Resalice)
### 5. Gestion des données locales (Hive)
#### Organisation des boîtes Hive
```dart
// Noms des boîtes Hive
static const String conversationsBoxName = 'chat_conversations';
static const String messagesBoxName = 'chat_messages';
static const String participantsBoxName = 'chat_participants';
static const String anonymousUsersBoxName = 'chat_anonymous_users';
```
#### Stratégie de synchronisation
1. **Ouverture sélective** : Ouverture des boîtes à la demande
2. **Gestion de conflit** : Stratégie pour résoudre les conflits entre données locales et serveur
3. **Nettoyage intelligent** : Suppression des messages anciens selon des règles configurables
4. **Marqueurs de synchronisation** : Tracking des messages synchronisés/non-synchronisés
## Implémentation technique
### 1. Structure des repositories
```dart
class ChatRepository {
// Gestion des conversations
Future<List<ConversationModel>> getConversations({bool forceRefresh = false});
Future<ConversationModel> getConversation(String id);
Future<ConversationModel> createConversation(Map<String, dynamic> data);
Future<void> deleteConversation(String id);
Future<void> pinConversation(String id, bool isPinned);
Future<void> updateReplyPermission(String id, String replyPermission);
// Gestion des messages
Future<List<MessageModel>> getMessages(String conversationId, {int page = 1, int limit = 50});
Future<MessageModel> sendMessage(String conversationId, Map<String, dynamic> messageData);
Future<void> markMessageAsRead(String messageId);
// Gestion des participants
Future<void> addParticipant(String conversationId, Map<String, dynamic> participantData);
Future<void> removeParticipant(String conversationId, String participantId);
// Gestion des utilisateurs anonymes (Resalice)
Future<String> createAnonymousUser({String? name, String? email});
Future<void> convertAnonymousToUser(String anonymousId, String userId);
// Gestion des annonces
Future<List<ConversationModel>> getAnnouncements({bool forceRefresh = false});
Future<ConversationModel> createAnnouncement(Map<String, dynamic> data);
Future<Map<String, dynamic>> getAnnouncementStats(String conversationId);
// Gestion des cibles d'audience
Future<List<Map<String, dynamic>>> getAvailableAudienceTargets();
Future<void> addAudienceTarget(String conversationId, Map<String, dynamic> targetData);
Future<void> removeAudienceTarget(String conversationId, String targetId);
}
```
### 2. Intégration avec l'API
```dart
class ChatApiService {
final String baseUrl;
final String? authToken;
// Constructeur avec paramètres pour l'URL et l'authentification
ChatApiService({
required this.baseUrl,
this.authToken,
});
// Méthodes HTTP pour communiquer avec l'API
Future<Map<String, dynamic>> fetchConversations();
Future<Map<String, dynamic>> fetchMessages(String conversationId, {int page = 1, int limit = 50});
Future<Map<String, dynamic>> createConversation(Map<String, dynamic> data);
Future<Map<String, dynamic>> sendMessage(String conversationId, Map<String, dynamic> messageData);
// ...autres méthodes
}
```
### 3. Gestion hors ligne
```dart
class OfflineQueueService {
// Ajouter des opérations en attente
Future<void> addPendingOperation(String operationType, Map<String, dynamic> data);
// Traiter les opérations en attente
Future<void> processPendingOperations();
// Écouter les changements de connectivité
void listenToConnectivityChanges();
}
```
### 4. Stockage des fichiers
Le système supportera le téléchargement et le partage de fichiers :
1. **Côté serveur** : Stockage dans un répertoire sécurisé avec restriction d'accès
2. **Côté client** : Mise en cache des fichiers pour éviter des téléchargements redondants
3. **Types supportés** : Images, documents, autres fichiers selon configuration
## Cas d'utilisation spécifiques
### 1. Geosector
- **Utilisateurs authentifiés uniquement**
- **Groupes par équipe** avec administrateurs pour les communications internes
- **Historique complet** des conversations
- **Intégration avec la structure existante** des amicales et équipes
- **Annonces et broadcasts**:
- Super admin → tous les admins d'entités
- Admin d'entité → tous les utilisateurs de son entité
- Communications descendantes sans possibilité de réponse
- Statistiques de lecture des annonces importantes
- **Ciblage flexible des destinataires** :
- Par entité (toutes ou une spécifique)
- Par rôle (tous, membres, administrateurs)
- Combinaison entité + rôle (ex: admins de l'entité 5)
- Sélection via le widget `AnnouncementTargetSelector`
### 2. Resalice
- **Chats initiés par des anonymes**
- **Conversation one-to-one uniquement** entre professionnel et client/prospect
- **Conversion client** : Processus pour transformer un utilisateur anonyme en client référencé
- **Conservation des historiques** après conversion
- **Interface professionnelle** adaptée aux échanges client/professionnel
- **Pas de fonctionnalité d'annonce** - uniquement des conversations directes
- **Annonces non pertinentes** pour ce cas d'usage (pas de widget `AnnouncementTargetSelector`)
### Adaptations par projet
La solution de chat doit être adaptable selon le contexte :
1. **Configuration globale** : Un système de configuration permet de définir quelles fonctionnalités sont activées
```dart
// Configuration pour Geosector
const chatConfig = ChatConfig(
enableAnnouncements: true,
enableTargetSelection: true,
showAnnouncementStats: true,
defaultReplyPermission: 'none',
);
// Configuration pour Resalice
const chatConfig = ChatConfig(
enableAnnouncements: false,
enableTargetSelection: false,
showAnnouncementStats: false,
defaultReplyPermission: 'all',
);
```
2. **Interfaces conditionnelles** : Les widgets adaptent leur affichage selon la configuration
```dart
// Dans AnnouncementComposer
if (config.enableTargetSelection) {
children.add(AnnouncementTargetSelector(...));
}
```
3. **Types de conversation limités** : La création de certains types de conversation est restreinte
```dart
// Dans Resalice, seuls les types one_to_one et anonymous sont autorisés
if (!config.enableAnnouncements && type == 'announcement') {
throw UnsupportedConversationType();
}
```
## Adaptabilité et extensibilité
### 1. Options de personnalisation
- **Thèmes** : Adaptation aux couleurs et styles de l'application
- **Fonctionnalités** : Activation/désactivation de certaines fonctionnalités
- **Comportements** : Configuration des notifications, comportement hors ligne, etc.
### 2. Extensions possibles
- **Chatbot** : Possibilité d'intégrer des réponses automatiques
- **Transfert** : Transfert de conversations entre professionnels
- **Intégration CRM** : Liaison avec des systèmes CRM pour le suivi client
- **Analyse** : Statistiques sur les conversations, temps de réponse, etc.
## Étapes d'implémentation suggérées
1. **Phase 1 : Base du système** (3-4 semaines)
- Modèles de données et adaptateurs Hive
- Configuration de l'API backend
- Widgets de base pour affichage/envoi de messages
- Structure de base pour les annonces et broadcasts
2. **Phase 2 : Fonctionnalités avancées** (2-3 semaines)
- Gestion hors ligne et synchronisation
- Support des fichiers et images
- Indicateurs de lecture et d'écriture
- Système de ciblage d'audience pour les annonces
3. **Phase 3 : Cas spécifiques** (2-3 semaines)
- Support des conversations anonymes (Resalice)
- Groupes et permissions avancées (Geosector)
- Statistiques de lecture des annonces
- Interface administrateur pour les annonces globales
- Intégration web complète
Le temps total d'implémentation pour Geosector est estimé à 6-9 semaines pour un développeur expérimenté en Flutter et PHP. L'adaptation ultérieure à Resalice devrait prendre environ 2-3 semaines supplémentaires grâce à la conception modulaire du système.
## Conclusion
Cette solution de chat personnalisée offre un équilibre entre robustesse et simplicité d'intégration. Elle répond aux besoins spécifiques de vos applications tout en restant suffisamment flexible pour s'adapter à d'autres contextes.
Le système prend en charge non seulement les conversations classiques (one-to-one, groupes) mais aussi les communications de type annonce/broadcast où un administrateur peut communiquer des informations importantes à des groupes d'utilisateurs définis par rôle ou entité, avec ou sans possibilité de réponse. Cette fonctionnalité est particulièrement adaptée aux cas d'usage mentionnés pour Geosector, où l'admin général souhaite communiquer avec tous les admins d'entités, ou un admin d'entité avec tous les utilisateurs de son entité.
En développant cette solution en interne, vous gardez un contrôle total sur les fonctionnalités et l'expérience utilisateur, tout en assurant une cohérence avec le reste de vos applications. La conception modulaire et réutilisable permettra également un déploiement efficace sur vos différentes plateformes et applications.

34
flutt/fix-web-assets.sh Normal file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
# Script pour corriger le problème d'assets dans l'application web
echo "🔍 Fixing assets structure for web deployment..."
# Création du dossier assets/assets/animations si inexistant
mkdir -p build/web/assets/assets/animations
# Copie des animations depuis le répertoire source
cp -r assets/animations/* build/web/assets/assets/animations/
echo "✅ Assets structure fixed!"
# Si besoin de redéployer sans reconstruire l'application
if [ "$1" == "--deploy" ]; then
# Définition des variables
REMOTE_USER="root"
REMOTE_HOST="87.98.163.161"
SSH_KEY="/Users/pierre/.ssh/id_rsa_mbpi"
REMOTE_PATH="/var/www/geosector"
echo "📤 Deploying fixed assets to server..."
rsync -rltz \
-e "ssh -i ${SSH_KEY}" \
build/web/assets/ \
${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/assets/
if [ $? -ne 0 ]; then
echo "❌ Deployment failed"
exit 1
fi
echo "✅ Assets deployed successfully!"
fi

125
flutt/fix_ios_build.sh Executable file
View File

@@ -0,0 +1,125 @@
#!/bin/bash
# Script de réinitialisation complète pour résoudre les problèmes de compilation iOS
# Spécialement conçu pour résoudre l'erreur "No such module 'Flutter'"
# Version 2.0 - Avec ajout automatique des chemins de recherche de frameworks
echo "🧹 Nettoyage complet de l'environnement iOS..."
# Se placer dans le répertoire du projet
cd "$(dirname "$0")"
# Supprimer les fichiers générés par Flutter
echo "📦 Nettoyage des fichiers Flutter..."
flutter clean
# Supprimer le cache pub
echo "🗑️ Suppression du cache pub pour les plugins problématiques..."
rm -rf ~/.pub-cache/hosted/pub.dev/connectivity_plus-*
# Supprimer les fichiers de CocoaPods
echo "🗂️ Nettoyage des fichiers CocoaPods..."
cd ios
rm -rf Pods
rm -rf .symlinks
rm -f Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
# Supprimer le workspace Xcode (il sera recréé)
echo "🔄 Suppression du workspace Xcode..."
rm -rf Runner.xcworkspace
# Revenir au répertoire parent
cd ..
# Récupérer les dépendances Flutter
echo "📥 Récupération des dépendances Flutter..."
flutter pub get
# Régénérer les fichiers iOS
echo "🔨 Précaching des outils iOS..."
flutter precache --ios --force
# Forcer la génération des plugins
echo "🔌 Régénération des plugins..."
flutter pub cache repair
flutter pub get
# Réinstaller les pods avec des options supplémentaires
echo "📲 Réinstallation des pods..."
cd ios
pod deintegrate
pod cache clean --all
pod repo update
pod install --repo-update --verbose
# Ajouter automatiquement les chemins de recherche de frameworks
echo "🔍 Ajout des chemins de recherche de frameworks..."
# Créer un fichier temporaire pour stocker les chemins de recherche
cat > ios/add_framework_paths.rb << 'EOL'
#!/usr/bin/env ruby
require 'xcodeproj'
# Ouvrir le projet
project_path = 'Runner.xcodeproj'
project = Xcodeproj::Project.open(project_path)
# Trouver la cible Runner
target = project.targets.find { |t| t.name == 'Runner' }
# Parcourir toutes les configurations de build
target.build_configurations.each do |config|
# Obtenir les paramètres de build actuels
build_settings = config.build_settings
# Définir les chemins de recherche de frameworks
framework_search_paths = [
'$(inherited)',
'"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift"',
'"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus"',
'"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation"',
'"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios"',
'"${PODS_ROOT}/Flutter"',
'"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flutter"'
]
# Ajouter les chemins de recherche de frameworks
build_settings['FRAMEWORK_SEARCH_PATHS'] = framework_search_paths
# Ajouter les chemins de recherche d'en-têtes
header_search_paths = [
'$(inherited)',
'"${PODS_ROOT}/Flutter"',
'"${PODS_CONFIGURATION_BUILD_DIR}"'
]
build_settings['HEADER_SEARCH_PATHS'] = header_search_paths
# S'assurer que les modules sont activés
build_settings['DEFINES_MODULE'] = 'YES'
# Désactiver le bitcode
build_settings['ENABLE_BITCODE'] = 'NO'
# Inclure tous les assets d'icônes
build_settings['ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS'] = 'YES'
# Autres paramètres importants
build_settings['SWIFT_VERSION'] = '5.0'
build_settings['CLANG_ENABLE_MODULES'] = 'YES'
end
# Enregistrer les modifications
project.save
puts "✅ Chemins de recherche de frameworks ajoutés avec succès !"
EOL
# Exécuter le script Ruby pour ajouter les chemins de recherche
cd ios
ruby add_framework_paths.rb
cd ..
echo "✅ Réinitialisation iOS terminée ! Ouvrez le projet avec 'open ios/Runner.xcworkspace'"

67
flutt/git-create-branch.sh Executable file
View File

@@ -0,0 +1,67 @@
#!/bin/bash
# Script to create a new branch from main/origin
# Check if branch name is provided
if [ $# -eq 0 ]; then
echo "Error: Branch name is required"
echo "Usage: $0 <branch-name>"
exit 1
fi
# Store branch name from parameter
BRANCH_NAME=$1
# Ensure we have the latest from origin
echo "Fetching latest changes from origin..."
git fetch origin
# Check if we're already on main, if not switch to it
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
if [ "$CURRENT_BRANCH" != "main" ]; then
echo "Switching to main branch..."
git checkout main
fi
# Pull latest changes from main
echo "Pulling latest changes from main..."
git pull origin main
# Create and checkout the new branch
echo "Creating and checking out new branch: $BRANCH_NAME"
git checkout -b "$BRANCH_NAME"
# Stage all changes
echo "Staging all changes..."
git add .
# Ask if user wants to make an initial commit
read -p "Do you want to make an initial commit? (Y/n): " COMMIT_CHOICE
# Default to Yes if Enter is pressed without input
COMMIT_CHOICE=${COMMIT_CHOICE:-Y}
if [[ $COMMIT_CHOICE =~ ^[Yy]$ ]]; then
# Ask for commit message
read -p "Enter commit message: " COMMIT_MESSAGE
# Check if commit message is provided
if [ -n "$COMMIT_MESSAGE" ]; then
# Make the commit
echo "Creating commit with message: '$COMMIT_MESSAGE'"
git commit -m "$COMMIT_MESSAGE"
# Push to remote with upstream tracking
echo "Pushing to origin and setting upstream tracking..."
git push -u origin "$BRANCH_NAME"
echo "Branch '$BRANCH_NAME' has been pushed to origin with tracking."
else
echo "No commit message provided. Skipping commit."
fi
else
echo "Skipping initial commit. You can commit changes later."
fi
echo "Success! You are now on branch: $BRANCH_NAME"
echo "Ready to start working!"

76
flutt/git-merge.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
# Check if a branch name was provided
if [ -z "$1" ]; then
echo "Error: Please provide the name of the branch to merge"
echo "Usage: ./git-merge.sh branch_name"
exit 1
fi
BRANCH_NAME=$1
# Check if the branch exists
if ! git show-ref --verify --quiet refs/heads/$BRANCH_NAME; then
echo "Error: Branch '$BRANCH_NAME' does not exist"
exit 1
fi
# Display the steps that will be executed
echo "=== Starting merge process ==="
echo "1. Checkout to main"
echo "2. Pull latest changes"
echo "3. Merge branch $BRANCH_NAME"
echo "4. Push to origin"
echo "5. Delete local and remote branches"
# Ask for confirmation
read -p "Do you want to continue? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Operation cancelled"
exit 1
fi
# Execute commands
echo -e "\n=== Checking out to main ==="
git checkout main
if [ $? -ne 0 ]; then
echo "Error during checkout to main"
exit 1
fi
echo -e "\n=== Pulling latest changes ==="
git pull origin main
if [ $? -ne 0 ]; then
echo "Error during pull"
exit 1
fi
echo -e "\n=== Merging branch $BRANCH_NAME ==="
git merge $BRANCH_NAME
if [ $? -ne 0 ]; then
echo "Error during merge. Please resolve conflicts manually"
exit 1
fi
echo -e "\n=== Pushing to origin ==="
git push origin main
if [ $? -ne 0 ]; then
echo "Error during push"
exit 1
fi
echo -e "\n=== Deleting local branch ==="
git branch -d $BRANCH_NAME
if [ $? -ne 0 ]; then
echo "Warning: Unable to delete local branch"
echo "If you are sure everything is properly merged, use: git branch -D $BRANCH_NAME"
fi
echo -e "\n=== Deleting remote branch ==="
git push origin --delete $BRANCH_NAME
if [ $? -ne 0 ]; then
echo "Warning: Unable to delete remote branch"
fi
echo -e "\n=== Merge process completed successfully ==="

34
flutt/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

@@ -0,0 +1,2 @@
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"

View File

@@ -0,0 +1,2 @@
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"

81
flutt/ios/Podfile Normal file
View File

@@ -0,0 +1,81 @@
# Uncomment this line to define a global platform for your project
# Spécifier la version minimale d'iOS pour Stripe Tap to Pay
platform :ios, '15.4'
# Ignorer les avertissements des pods
install! 'cocoapods', :warn_for_unused_master_specs_repo => false
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
# Utiliser les frameworks dynamiques
use_frameworks!
# Désactiver les en-têtes modulaires pour éviter les conflits
# use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
# Configuration post-installation
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
# Maintenir la version minimale iOS 15.4 pour Stripe Tap to Pay
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.4'
# Désactiver Bitcode (recommandé par Flutter)
config.build_settings['ENABLE_BITCODE'] = 'NO'
# Paramètres pour la compatibilité avec Xcode récent
config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
config.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO'
# Paramètres pour éviter les erreurs de module
config.build_settings['DEFINES_MODULE'] = 'YES'
config.build_settings['SWIFT_VERSION'] = '5.0'
# Désactiver le support Mac Catalyst
config.build_settings['SUPPORTS_MACCATALYST'] = 'NO'
# Paramètres de signature de code
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
# Ajout des permissions de géolocalisation
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_LOCATION=1',
]
end
end
# Flutter post install
flutter_post_install(installer) if defined?(flutter_post_install)
end

42
flutt/ios/Podfile.lock Normal file
View File

@@ -0,0 +1,42 @@
PODS:
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
- Flutter (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- ReachabilitySwift (5.2.4)
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- Flutter (from `Flutter`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS:
trunk:
- ReachabilitySwift
EXTERNAL SOURCES:
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
Flutter:
:path: Flutter
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
PODFILE CHECKSUM: f0d28569a754ac33c3d750271af244edf72e3a3c
COCOAPODS: 1.16.2

View File

@@ -0,0 +1,814 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
B29AD2EBEFD19B161772B50D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54642C142BC98D17D428B51D /* Pods_Runner.framework */; };
C89DA9B9EA30824E0E881287 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3EE1328CE0A8907C5568E72D /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
132FE5584808793FE93F08F5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
21F13E510138E0DBAAA0667E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
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>"; };
3DE8EBA41425E2A096EE70CF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
3EE1328CE0A8907C5568E72D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54642C142BC98D17D428B51D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8814F9792D6E6BA0874B431C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
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>"; };
CE8A95C455B844D61A15C99F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
FE152E3AEF93B264AE11B5E9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
9491A6C7DD10151C405FF968 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C89DA9B9EA30824E0E881287 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B29AD2EBEFD19B161772B50D /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
5301A26C8AA9F29008B3D808 /* Frameworks */ = {
isa = PBXGroup;
children = (
54642C142BC98D17D428B51D /* Pods_Runner.framework */,
3EE1328CE0A8907C5568E72D /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
C67A4583967DEDA799C193DE /* Pods */,
5301A26C8AA9F29008B3D808 /* Frameworks */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
C67A4583967DEDA799C193DE /* Pods */ = {
isa = PBXGroup;
children = (
8814F9792D6E6BA0874B431C /* Pods-Runner.debug.xcconfig */,
CE8A95C455B844D61A15C99F /* Pods-Runner.release.xcconfig */,
FE152E3AEF93B264AE11B5E9 /* Pods-Runner.profile.xcconfig */,
3DE8EBA41425E2A096EE70CF /* Pods-RunnerTests.debug.xcconfig */,
21F13E510138E0DBAAA0667E /* Pods-RunnerTests.release.xcconfig */,
132FE5584808793FE93F08F5 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
04F44EDE70DDFF90BCE6241F /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
9491A6C7DD10151C405FF968 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9413D06037CA6133CC0A13EB /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
625AB1A439E96A5838DD474D /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1630;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
preferredProjectObjectVersion = 77;
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 */
04F44EDE70DDFF90BCE6241F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
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";
};
625AB1A439E96A5838DD474D /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9413D06037CA6133CC0A13EB /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
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 = 15.4;
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;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 6WT84NWCTC;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"",
"\"${PODS_ROOT}/Flutter\"",
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flutter\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/Flutter\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}\"",
);
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = GEOSECTOR;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.2.1;
PRODUCT_BUNDLE_IDENTIFIER = fr.geosector.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3DE8EBA41425E2A096EE70CF /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = fr.geosector.app.geosectorApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 21F13E510138E0DBAAA0667E /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = fr.geosector.app.geosectorApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 132FE5584808793FE93F08F5 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = fr.geosector.app.geosectorApp.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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
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 = 15.4;
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
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 = 15.4;
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;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 6WT84NWCTC;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"",
"\"${PODS_ROOT}/Flutter\"",
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flutter\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/Flutter\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}\"",
);
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = GEOSECTOR;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.2.1;
PRODUCT_BUNDLE_IDENTIFIER = fr.geosector.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 6WT84NWCTC;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"",
"\"${PODS_ROOT}/Flutter\"",
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flutter\"",
);
"FRAMEWORK_SEARCH_PATHS[arch=*]" = (
"$(inherited)",
"\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/Flutter\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}\"",
);
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = GEOSECTOR;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.2.1;
PRODUCT_BUNDLE_IDENTIFIER = fr.geosector.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1630"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,5 @@
<?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/>
</plist>

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View 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.

View File

@@ -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>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Geosector App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>geosector_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Cette application nécessite l'accès à votre position pour enregistrer les passages et assurer le suivi des secteurs géographiques.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Cette application nécessite l'accès à votre position pour enregistrer les passages et assurer le suivi des secteurs géographiques.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Cette application nécessite l'accès à votre position pour enregistrer les passages et assurer le suivi des secteurs géographiques.</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

32
flutt/ios_reset.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Se placer dans le répertoire du projet
cd "$(dirname "$0")"
# Supprimer les fichiers générés par Flutter
flutter clean
# Supprimer les fichiers de CocoaPods
cd ios
rm -rf Pods
rm -rf .symlinks
rm -f Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
# Supprimer le workspace Xcode (il sera recréé)
rm -rf Runner.xcworkspace
# Revenir au répertoire parent
cd ..
# Récupérer les dépendances Flutter
flutter pub get
# Régénérer les fichiers iOS
flutter precache --ios
# Réinstaller les pods
cd ios
pod install --repo-update
echo "Réinitialisation iOS terminée !"

214
flutt/lib/app.dart Normal file
View File

@@ -0,0 +1,214 @@
import 'package:flutter/material.dart';
import 'package:geosector_app/core/theme/app_theme.dart';
import 'package:go_router/go_router.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/repositories/user_repository.dart';
import 'package:geosector_app/core/repositories/operation_repository.dart';
import 'package:geosector_app/core/repositories/passage_repository.dart';
import 'package:geosector_app/core/repositories/sector_repository.dart';
import 'package:geosector_app/core/repositories/membre_repository.dart';
import 'package:geosector_app/core/services/sync_service.dart';
import 'package:geosector_app/core/services/connectivity_service.dart';
import 'package:geosector_app/presentation/auth/splash_page.dart';
import 'package:geosector_app/presentation/public/landing_page.dart';
import 'package:geosector_app/presentation/auth/login_page.dart';
import 'package:geosector_app/presentation/auth/register_page.dart';
import 'package:geosector_app/presentation/admin/admin_dashboard_page.dart';
import 'package:geosector_app/presentation/user/user_dashboard_page.dart';
// Instances globales des services et repositories
final apiService = ApiService();
final operationRepository = OperationRepository(apiService);
final passageRepository = PassageRepository(apiService);
final userRepository = UserRepository(apiService);
final sectorRepository = SectorRepository(apiService);
final membreRepository = MembreRepository(apiService);
final syncService = SyncService(userRepository: userRepository);
final connectivityService = ConnectivityService();
class GeoSectorApp extends StatelessWidget {
const GeoSectorApp({super.key});
@override
Widget build(BuildContext context) {
// Utiliser directement le router sans provider
final router = GoRouter(
initialLocation: '/',
debugLogDiagnostics: true,
refreshListenable:
userRepository, // Écouter les changements d'état d'authentification
redirect: (context, state) {
// Sauvegarder le chemin actuel pour l'utilisateur connecté, sauf pour la page de splash
if (state.uri.toString() != '/' && userRepository.isLoggedIn) {
// Ne pas sauvegarder les chemins de login/register
if (!state.uri.toString().startsWith('/login') &&
!state.uri.toString().startsWith('/register') &&
!state.uri.toString().startsWith('/public')) {
userRepository.updateLastPath(state.uri.toString());
}
}
// Vérifier si l'utilisateur est sur la page de splash
if (state.uri.toString() == '/') {
// Vérifier si l'utilisateur a une session valide
final currentUser = userRepository.getCurrentUser();
if (currentUser == null || currentUser.sessionId == null) {
// Si pas de session valide, rediriger vers la landing page
return '/public';
}
// Si l'utilisateur a une session valide et un chemin précédent, y retourner
final lastPath = userRepository.getLastPath();
if (lastPath != null && lastPath.isNotEmpty) {
return lastPath;
}
// Sinon, rediriger vers le tableau de bord approprié
if (userRepository.isAdmin()) {
return '/admin';
} else {
return '/user';
}
}
// Vérifier si l'utilisateur est sur une page d'authentification
final isLoggedIn = userRepository.isLoggedIn;
final isOnLoginPage = state.uri.toString() == '/login';
final isOnRegisterPage = state.uri.toString() == '/register';
final isOnAdminRegisterPage = state.uri.toString() == '/admin-register';
final isOnPublicPage = state.uri.toString() == '/public';
// Vérifier si l'utilisateur vient de la landing page et va vers la page de connexion
// Cette information est stockée dans les paramètres de la route
final isFromLandingPage =
state.uri.queryParameters['from'] == 'landing';
// Permettre l'accès aux pages publiques sans authentification
if (isOnPublicPage) {
return null;
}
// Si l'utilisateur vient de la landing page et va vers la page de connexion ou d'inscription,
// ne pas rediriger, même s'il est déjà connecté
if ((isOnLoginPage || isOnRegisterPage) && isFromLandingPage) {
return null;
}
// Si l'utilisateur n'est pas connecté et n'est pas sur une page d'authentification, rediriger vers la page de connexion
if (!isLoggedIn &&
!isOnLoginPage &&
!isOnRegisterPage &&
!isOnAdminRegisterPage) {
return '/login';
}
// Si l'utilisateur est connecté et se trouve sur une page d'authentification, rediriger vers le tableau de bord approprié
if (isLoggedIn &&
(isOnLoginPage || isOnRegisterPage || isOnAdminRegisterPage)) {
if (userRepository.isAdmin()) {
return '/admin';
} else {
return '/user';
}
}
// Si l'utilisateur est connecté en tant qu'administrateur mais essaie d'accéder à une page utilisateur, rediriger vers le tableau de bord admin
if (isLoggedIn &&
userRepository.isAdmin() &&
state.uri.toString().startsWith('/user')) {
return '/admin';
}
// Si l'utilisateur est connecté en tant qu'utilisateur mais essaie d'accéder à une page admin, rediriger vers le tableau de bord utilisateur
if (isLoggedIn &&
!userRepository.isAdmin() &&
state.uri.toString().startsWith('/admin')) {
return '/user';
}
return null;
},
routes: [
// Splash screen et page de démarrage
GoRoute(
path: '/',
builder: (context, state) => const SplashPage(),
),
// Pages publiques
GoRoute(
path: '/public',
builder: (context, state) => const LandingPage(),
),
// Pages d'authentification
GoRoute(
path: '/login',
builder: (context, state) {
// Extraire le type de connexion depuis les extras
Map<String, dynamic>? extras;
if (state.extra != null && state.extra is Map<String, dynamic>) {
extras = state.extra as Map<String, dynamic>;
}
String? loginType = extras?['type'];
print('DEBUG ROUTER: Type dans les extras: $loginType');
// Nettoyer le paramètre type si présent
if (loginType != null) {
loginType = loginType.trim().toLowerCase();
print('DEBUG ROUTER: Type nettoyé: $loginType');
} else {
// Fallback: essayer de récupérer depuis les paramètres d'URL
final queryParams = state.uri.queryParameters;
loginType = queryParams['type'];
if (loginType != null) {
loginType = loginType.trim().toLowerCase();
print('DEBUG ROUTER: Type récupéré des params URL: $loginType');
} else {
loginType = 'admin'; // Valeur par défaut
print('DEBUG ROUTER: Type par défaut: admin');
}
}
return LoginPage(
key: Key('login_page_${loginType}'),
loginType: loginType,
);
},
),
GoRoute(
path: '/register',
builder: (context, state) => const RegisterPage(),
),
// Pages administrateur
GoRoute(
path: '/admin',
builder: (context, state) => const AdminDashboardPage(),
routes: [
// Ajouter d'autres routes admin ici
],
),
// Pages utilisateur
GoRoute(
path: '/user',
builder: (context, state) => const UserDashboardPage(),
routes: [
// Ajouter d'autres routes utilisateur ici
],
),
],
);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'GEOSECTOR',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
routerConfig: router,
);
}
}

82
flutt/lib/chat/README.md Normal file
View File

@@ -0,0 +1,82 @@
# Module Chat GEOSECTOR
## Structure du module
Le module chat est organisé selon une architecture modulaire respectant la séparation des préoccupations :
```
lib/chat/
├── models/ # Modèles de données
│ ├── conversation_model.dart
│ ├── message_model.dart
│ ├── participant_model.dart
│ └── audience_target_model.dart
├── repositories/ # Gestion des données
│ └── chat_repository.dart
├── services/ # Services techniques
│ ├── chat_api_service.dart
│ └── offline_queue_service.dart
├── widgets/ # Composants UI
│ ├── chat_screen.dart
│ ├── conversations_list.dart
│ ├── message_bubble.dart
│ └── chat_input.dart
├── pages/ # Pages de l'application
│ └── chat_page.dart
├── chat.dart # Point d'entrée avec exports
└── README.md # Documentation du module
```
## Fonctionnalités principales
1. **Conversations** : Support des conversations one-to-one, groupes et annonces
2. **Messages** : Envoi/réception de messages texte et pièces jointes
3. **Participants** : Gestion des participants aux conversations
4. **Annonces** : Diffusion de messages à des groupes spécifiques
5. **Mode hors ligne** : File d'attente pour la synchronisation des données
## Utilisation
### Importation
```dart
import 'package:geosector/chat/chat.dart';
```
### Affichage de la page chat
```dart
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ChatPage()),
);
```
### Création d'une conversation
```dart
final chatRepository = ChatRepository();
final conversation = await chatRepository.createConversation({
'type': 'one_to_one',
'participants': [userId1, userId2],
});
```
## États d'implémentation
- [x] Structure de base
- [ ] Modèles de données complets
- [ ] Intégration avec Hive
- [ ] Services API
- [ ] Gestion hors ligne
- [ ] Widgets visuels
- [ ] Tests unitaires
## À faire
1. Compléter l'implémentation des modèles avec les adaptateurs Hive
2. Implémenter les méthodes dans les services et repositories
3. Créer les widgets visuels avec le design approprié
4. Ajouter les adaptateurs Hive pour le stockage local
5. Implémenter la gestion des pièces jointes
6. Ajouter les tests unitaires

35
flutt/lib/chat/chat.dart Normal file
View File

@@ -0,0 +1,35 @@
/// Exportation principale du module chat
///
/// Ce fichier centralise les exportations du module chat
/// pour faciliter l'importation dans d'autres parties de l'application
// Models
export 'models/conversation_model.dart';
export 'models/message_model.dart';
export 'models/participant_model.dart';
export 'models/audience_target_model.dart';
export 'models/anonymous_user_model.dart';
export 'models/chat_config.dart';
export 'models/notification_settings.dart';
// Repositories
export 'repositories/chat_repository.dart';
// Services
export 'services/chat_api_service.dart';
export 'services/offline_queue_service.dart';
export 'services/notifications/mqtt_notification_service.dart';
export 'services/notifications/mqtt_config.dart';
// Widgets
export 'widgets/chat_screen.dart';
export 'widgets/conversations_list.dart';
export 'widgets/message_bubble.dart';
export 'widgets/chat_input.dart';
export 'widgets/notification_settings_widget.dart';
// Pages
export 'pages/chat_page.dart';
// Constants
export 'constants/chat_constants.dart';

View File

@@ -0,0 +1,510 @@
# Solution de Chat pour Applications Flutter
## Présentation générale
Cette solution propose un système de chat personnalisé et autonome pour des applications Flutter, avec possibilité d'intégration web. Elle est conçue pour fonctionner dans deux contextes différents :
1. **Chat entre utilisateurs authentifiés** (cas Geosector) : communications one-to-one ou en groupe entre utilisateurs déjà enregistrés dans la base de données.
2. **Chat entre professionnels et visiteurs anonymes** (cas Resalice) : communications initiées par des visiteurs anonymes qui peuvent ensuite être convertis en clients référencés.
## Architecture technique
### 1. Structure générale
La solution s'articule autour de quatre composants principaux :
- **Module Flutter** : Widgets et logique pour l'interface utilisateur mobile
- **Module Web** : Composants pour l'intégration web (compatible avec Flutter Web ou sites traditionnels)
- **API Backend** : Endpoints REST uniquement pour la récupération de l'historique des conversations
- **Module Go Chat Service** : Service de gestion des messages MQTT, modération et synchronisation avec la base de données
### 2. Infrastructure de notifications
#### Broker MQTT
Le système utilise MQTT pour les notifications en temps réel :
- Broker Mosquitto hébergé dans un container Incus
- Connexion sécurisée via SSL/TLS (port 8883)
- Authentification par username/password
- QoS 1 (at least once) pour garantir la livraison
#### Module Go Chat Service
Un service externe en Go qui :
- Écoute les événements MQTT
- Enregistre les messages dans la base de données
- Applique des règles de modération configurables
- Synchronise les notifications avec le stockage
```go
type ChatService struct {
mqttClient mqtt.Client
db *sql.DB
moderator *Moderator
config *ChatConfig
}
type ChatConfig struct {
ApplicationID string
ModeratorEnabled bool
BadWords []string
FloodLimits int
SpamRules map[string]interface{}
Webhooks []string
}
```
### 3. Modèle de données
#### Entités principales
```
Conversation
├── id : Identifiant unique
├── type : Type de conversation (one_to_one, group, anonymous, broadcast, announcement)
├── title : Titre facultatif pour les groupes et obligatoire pour les annonces
├── reply_permission : Niveau de permission pour répondre (all, admins_only, sender_only, none)
├── created_at : Date de création
├── updated_at : Dernière mise à jour
├── is_pinned : Indique si la conversation est épinglée (pour annonces importantes)
├── expiry_date : Date d'expiration optionnelle (pour annonces temporaires)
└── participants : Liste des participants
Message
├── id : Identifiant unique
├── conversation_id : ID de la conversation
├── sender_id : ID de l'expéditeur (null pour anonyme)
├── sender_type : Type d'expéditeur (user, anonymous, system)
├── content : Contenu du message
├── content_type : Type de contenu (text, image, file)
├── created_at : Date d'envoi
├── delivered_at : Date de réception
├── read_at : Date de lecture
├── status : Statut du message (sent, delivered, read, error)
├── is_announcement : Indique s'il s'agit d'une annonce officielle
├── is_moderated : Indique si le message a été modéré
└── moderation_status : Statut de la modération (pending, approved, rejected)
Participant
├── id : Identifiant unique
├── conversation_id : ID de la conversation
├── user_id : ID de l'utilisateur (si authentifié)
├── anonymous_id : ID anonyme (pour Resalice)
├── role : Rôle (admin, member, read_only)
├── joined_at : Date d'ajout à la conversation
├── via_target : Indique si l'utilisateur est inclus via un AudienceTarget
├── can_reply : Possibilité explicite de répondre (override de reply_permission)
└── last_read_message_id : ID du dernier message lu
AudienceTarget
├── id : Identifiant unique
├── conversation_id : ID de la conversation
├── target_type : Type de cible (role, entity, all, combined)
├── target_id : ID du rôle ou de l'entité ciblée (pour compatibility)
├── role_filter : Filtre de rôle pour le ciblage combiné ('all', '1', '2', etc.)
├── entity_filter : Filtre d'entité pour le ciblage combiné ('all', 'id_entité')
└── created_at : Date de création
AnonymousUser (pour Resalice)
├── id : Identifiant unique
├── device_id : Identifiant du dispositif
├── name : Nom temporaire (si fourni)
├── email : Email (si fourni)
├── created_at : Date de création
├── converted_to_user_id : ID utilisateur après conversion
└── metadata : Informations supplémentaires
ChatNotification
├── id : Identifiant unique
├── user_id : ID de l'utilisateur destinataire
├── message_id : ID du message
├── conversation_id : ID de la conversation
├── type : Type de notification
├── status : Statut (sent, delivered, read)
├── sent_at : Date d'envoi
└── read_at : Date de lecture
```
### 4. Backend et API
#### Structure de l'API
L'API sera développée en PHP 8.3 pour s'intégrer avec vos systèmes existants :
```
/api/chat/conversations
GET - Liste des conversations de l'utilisateur
POST - Créer une nouvelle conversation
/api/chat/conversations/{id}
GET - Détails d'une conversation
PUT - Mettre à jour une conversation
DELETE - Supprimer une conversation
/api/chat/conversations/{id}/messages
GET - Messages d'une conversation (pagination) - uniquement pour l'historique
/api/chat/conversations/{id}/participants
GET - Liste des participants
POST - Ajouter un participant
DELETE - Retirer un participant
/api/chat/messages/{id}
PUT - Mettre à jour un message (ex: marquer comme lu)
DELETE - Supprimer un message
/api/chat/anonymous
POST - Démarrer une conversation anonyme
# Nouveaux endpoints pour les annonces
/api/chat/announcements
GET - Liste des annonces pour l'utilisateur
POST - Créer une nouvelle annonce
/api/chat/announcements/{id}/stats
GET - Obtenir les statistiques de lecture (qui a lu/non lu)
/api/chat/audience-targets
GET - Obtenir les cibles disponibles pour l'utilisateur actuel
/api/chat/conversations/{id}/pin
PUT - Épingler/désépingler une conversation
/api/chat/conversations/{id}/reply-permission
PUT - Modifier les permissions de réponse
/api/chat/moderation/rules
GET - Obtenir les règles de modération
PUT - Mettre à jour les règles de modération
```
#### Synchronisation
Le système supporte deux flux de données distincts :
1. **Temps réel via MQTT** :
- Envoi de messages en temps réel
- Notifications instantanées
- Gestion via le module Go
2. **Récupération historique via REST** :
- Chargement de l'historique des conversations
- Synchronisation des anciens messages
- Accès direct à la base de données
- Enregistrement local des messages avec Hive pour le fonctionnement hors ligne
### 5. Widgets Flutter
#### Widgets principaux
1. **ChatScreen** : Écran principal d'une conversation
```dart
ChatScreen({
required String conversationId,
String? title,
Widget? header,
Widget? footer,
bool enableAttachments = true,
bool showTypingIndicator = true,
bool enableReadReceipts = true,
bool isAnnouncement = false,
bool canReply = true,
})
```
2. **ConversationsList** : Liste des conversations
```dart
ConversationsList({
List<ConversationModel>? conversations,
bool loadFromHive = true,
Function(ConversationModel)? onConversationSelected,
bool showLastMessage = true,
bool showUnreadCount = true,
bool showAnnouncementBadge = true,
bool showPinnedFirst = true,
Widget? emptyStateWidget,
})
```
3. **MessageBubble** : Bulle de message
```dart
MessageBubble({
required MessageModel message,
bool showSenderInfo = true,
bool showTimestamp = true,
bool showStatus = true,
bool isAnnouncement = false,
double maxWidth = 300,
})
```
4. **ChatInput** : Zone de saisie de message
```dart
ChatInput({
required Function(String) onSendText,
Function(File)? onSendFile,
Function(File)? onSendImage,
bool enableAttachments = true,
bool enabled = true,
String hintText = 'Saisissez votre message...',
String? disabledMessage = 'Vous ne pouvez pas répondre à cette annonce',
int? maxLength,
})
```
5. **AnonymousChatStarter** : Widget pour démarrer un chat anonyme (Resalice)
```dart
AnonymousChatStarter({
required Function(String?) onChatStarted,
bool requireName = false,
bool requireEmail = false,
String buttonLabel = 'Démarrer une conversation',
Widget? customForm,
})
```
6. **AnnouncementComposer** : Widget pour créer des annonces (Geosector uniquement)
```dart
AnnouncementComposer({
required Function(Map<String, dynamic>) onSend,
List<Map<String, dynamic>>? availableTargets,
String? initialTitle,
String? initialMessage,
bool allowAttachments = true,
bool allowPinning = true,
List<String> replyPermissionOptions = const ['all', 'admins_only', 'sender_only', 'none'],
String defaultReplyPermission = 'none',
DateTime? expiryDate,
bool isGeosector = true, // Active la sélection des destinataires
})
```
### 6. Gestion des notifications MQTT
#### Service MQTT Flutter
```dart
class MqttNotificationService {
final String mqttHost;
final int mqttPort;
final String mqttUsername;
final String mqttPassword;
Future<void> initialize({required String userId}) async {
// Initialisation du client MQTT
await _initializeMqttClient();
// Abonnement aux topics de l'utilisateur
_subscribeToUserTopics(userId);
}
void _subscribeToUserTopics(String userId) {
// Topics pour les messages personnels
client.subscribe('chat/user/$userId/messages', MqttQos.atLeastOnce);
// Topics pour les annonces
client.subscribe('chat/announcement', MqttQos.atLeastOnce);
}
Future<void> _handleMessage(String topic, Map<String, dynamic> data) async {
// Traitement et affichage de la notification locale
await _showLocalNotification(data);
// Stockage local pour la synchronisation
await _syncWithHive(data);
}
// Pour envoyer un message en temps réel
Future<void> sendMessage(String conversationId, String content) async {
final message = {
'conversationId': conversationId,
'content': content,
'senderId': currentUserId,
'timestamp': DateTime.now().toIso8601String(),
};
await client.publishMessage(
'chat/message/send',
MqttQos.atLeastOnce,
MqttClientPayloadBuilder().addString(jsonEncode(message)).payload!,
);
}
}
```
#### Service REST Flutter
```dart
class ChatApiService {
Future<List<Message>> getHistoricalMessages(
String conversationId, {
int page = 1,
int limit = 50,
}) async {
final response = await get('/api/chat/conversations/$conversationId/messages');
return (response.data as List)
.map((json) => Message.fromJson(json))
.toList();
}
// Note: Pas de POST pour les messages - uniquement pour l'historique
}
```
#### Structure des topics MQTT
```
chat/user/{userId}/messages - Messages personnels
chat/conversation/{convId} - Messages de groupe
chat/announcement - Annonces générales
chat/moderation/{msgId} - Résultats de modération
chat/typing/{convId} - Indicateurs de frappe
```
### 7. Module Go Chat Service
Le module Go gère :
1. **Réception MQTT**
- Écoute les topics de chat
- Parse les messages JSON
- Valide le format
2. **Modération**
- Analyse du contenu
- Application des règles configurables
- Filtrage des mots interdits
- Détection de spam
- Notification des résultats
3. **Synchronisation base de données**
- Enregistrement des messages en base
- Création des notifications
- Mise à jour des statuts de livraison
- Gestion des acquittements
**Note importante** : Le module Go n'a aucune interaction avec l'API REST. Il est uniquement connecté au broker MQTT pour recevoir les messages et à la base de données pour les stocker.
4. **Configuration par application**
```yaml
applications:
geosector:
moderator_enabled: true
bad_words: ["liste", "des", "mots"]
flood_limit: 5
spam_rules:
url_limit: 2
repetition_threshold: 0.8
resalice:
moderator_enabled: false
# Configuration différente
```
### 8. Stockage des fichiers
Le système supportera le téléchargement et le partage de fichiers :
1. **Côté serveur** : Stockage dans un répertoire sécurisé avec restriction d'accès
2. **Côté client** : Mise en cache des fichiers pour éviter des téléchargements redondants
3. **Types supportés** : Images, documents, autres fichiers selon configuration
## Cas d'utilisation spécifiques
### 1. Geosector
- **Utilisateurs authentifiés uniquement**
- **Groupes par équipe** avec administrateurs pour les communications internes
- **Modération active** avec filtrage de contenu
- **Historique complet** des conversations
- **Intégration avec la structure existante** des amicales et équipes
- **Annonces et broadcasts**:
- Super admin → tous les admins d'entités
- Admin d'entité → tous les utilisateurs de son entité
- Communications descendantes sans possibilité de réponse
- Statistiques de lecture des annonces importantes
- **Ciblage flexible des destinataires** :
- Par entité (toutes ou une spécifique)
- Par rôle (tous, membres, administrateurs)
- Combinaison entité + rôle (ex: admins de l'entité 5)
- Sélection via le widget `AnnouncementTargetSelector`
### 2. Resalice
- **Chats initiés par des anonymes**
- **Conversation one-to-one uniquement** entre professionnel et client/prospect
- **Pas de modération active** par défaut
- **Conversion client** : Processus pour transformer un utilisateur anonyme en client référencé
- **Conservation des historiques** après conversion
- **Interface professionnelle** adaptée aux échanges client/professionnel
- **Pas de fonctionnalité d'annonce** - uniquement des conversations directes
## Adaptabilité et extensibilité
### 1. Options de personnalisation
- **Thèmes** : Adaptation aux couleurs et styles de l'application
- **Fonctionnalités** : Activation/désactivation de certaines fonctionnalités
- **Comportements** : Configuration des notifications, comportement hors ligne, etc.
- **Modération** : Configuration par application
### 2. Extensions possibles
- **Chatbot** : Possibilité d'intégrer des réponses automatiques
- **Transfert** : Transfert de conversations entre professionnels
- **Intégration CRM** : Liaison avec des systèmes CRM pour le suivi client
- **Analyse** : Statistiques sur les conversations, temps de réponse, etc.
- **Audio/Vidéo** : Support des messages vocaux et vidéo
## Étapes d'implémentation suggérées
1. **Phase 1 : Infrastructure de base** (4-5 semaines)
- Installation et configuration du broker MQTT
- Développement du module Go Chat Service
- Modèles de données et adaptateurs Hive
- Configuration de l'API backend
2. **Phase 2 : Fonctionnalités principales** (4-5 semaines)
- Widgets de base pour affichage/envoi de messages
- Gestion des notifications MQTT
- Système de modération
- Structure de base pour les annonces et broadcasts
3. **Phase 3 : Fonctionnalités avancées** (3-4 semaines)
- Gestion hors ligne et synchronisation
- Support des fichiers et images
- Indicateurs de lecture et d'écriture
- Système de ciblage d'audience pour les annonces
4. **Phase 4 : Cas spécifiques** (3-4 semaines)
- Support des conversations anonymes (Resalice)
- Groupes et permissions avancées (Geosector)
- Statistiques de lecture des annonces
- Interface administrateur pour les annonces globales
- Intégration web complète
Le temps total d'implémentation pour Geosector est estimé à 12-15 semaines pour un développeur expérimenté en Flutter, PHP et Go. L'adaptation ultérieure à Resalice devrait prendre environ 3-4 semaines supplémentaires grâce à la conception modulaire du système.
## Conclusion
Cette solution de chat personnalisée offre un équilibre entre robustesse et simplicité d'intégration. Elle répond aux besoins spécifiques de vos applications tout en restant suffisamment flexible pour s'adapter à d'autres contextes.
Le système prend en charge non seulement les conversations classiques (one-to-one, groupes) mais aussi les communications de type annonce/broadcast où un administrateur peut communiquer des informations importantes à des groupes d'utilisateurs définis par rôle ou entité, avec ou sans possibilité de réponse.
### Points clés de l'architecture
1. **Séparation des flux** :
- **Temps réel** : Via MQTT pour l'envoi de messages et les notifications
- **Historique** : Via REST pour la récupération des anciennes conversations
2. **Modération centrée** : Le module Go gère la modération sans interaction avec l'API REST
3. **Auto-hébergement** :
- Broker MQTT dans votre container Incus
- Module Go dédié pour la gestion des messages
- Contrôle total de l'infrastructure
4. **Configuration flexible** : Modération et comportement adaptables par application
En développant cette solution en interne, vous gardez un contrôle total sur les fonctionnalités et l'expérience utilisateur, tout en assurant une cohérence avec le reste de vos applications. La conception modulaire et réutilisable permettra également un déploiement efficace sur vos différentes plateformes et applications.

View File

@@ -0,0 +1,50 @@
/// Constantes spécifiques au module chat
class ChatConstants {
// Types de conversations
static const String conversationTypeOneToOne = 'one_to_one';
static const String conversationTypeGroup = 'group';
static const String conversationTypeAnonymous = 'anonymous';
static const String conversationTypeBroadcast = 'broadcast';
static const String conversationTypeAnnouncement = 'announcement';
// Types de messages
static const String messageTypeText = 'text';
static const String messageTypeImage = 'image';
static const String messageTypeFile = 'file';
static const String messageTypeSystem = 'system';
// Types d'expéditeurs
static const String senderTypeUser = 'user';
static const String senderTypeAnonymous = 'anonymous';
static const String senderTypeSystem = 'system';
// Rôles des participants
static const String participantRoleAdmin = 'admin';
static const String participantRoleMember = 'member';
static const String participantRoleReadOnly = 'read_only';
// Permissions de réponse
static const String replyPermissionAll = 'all';
static const String replyPermissionAdminsOnly = 'admins_only';
static const String replyPermissionSenderOnly = 'sender_only';
static const String replyPermissionNone = 'none';
// Types de cibles d'audience
static const String targetTypeRole = 'role';
static const String targetTypeEntity = 'entity';
static const String targetTypeAll = 'all';
// Noms des boîtes Hive
static const String conversationsBoxName = 'chat_conversations';
static const String messagesBoxName = 'chat_messages';
static const String participantsBoxName = 'chat_participants';
static const String anonymousUsersBoxName = 'chat_anonymous_users';
static const String offlineQueueBoxName = 'chat_offline_queue';
// Configurations
static const int defaultMessagePageSize = 50;
static const int maxAttachmentSizeMB = 10;
static const int maxMessageLength = 5000;
static const Duration typingIndicatorTimeout = Duration(seconds: 3);
}

Some files were not shown because too many files have changed in this diff Show More