feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<provider tools:replace="android:authorities" android:name=".FileSystemFileProvider" android:authorities="${applicationId}.FileSystemFileProvider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_system_provider_paths" />
</provider>
</application>
<queries>
<!-- Query open documents -->
<intent>
<action android:name="android.intent.action.OPEN_DOCUMENT_TREE" />
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,26 @@
package expo.modules.filesystem
import android.content.Context
import expo.modules.core.interfaces.InternalModule
import expo.modules.interfaces.filesystem.AppDirectoriesModuleInterface
import java.io.File
/*
New Sweet API modules don't have an easy way to access scoped context. We can't initialize them with scoped context as they need a ReactApplicationContext instead.
We can't make ScopedContext inherit from ReactApplicationContext as that would require moving ScopedContext to versioned and a large refactor.
This module is a stopgap solution to provide modules with a way to access ScopedContext directories using the filesystem module, only for our internal modules.
*/
// The class needs to be 'open', because it's inherited in expoview
open class AppDirectoriesModule(private val context: Context) : AppDirectoriesModuleInterface, InternalModule {
override fun getExportedInterfaces(): List<Class<*>> =
listOf(AppDirectoriesModuleInterface::class.java)
override val cacheDirectory: File
get() = context.cacheDir
override val persistentFilesDirectory: File
get() = context.filesDir
}

View File

@@ -0,0 +1,52 @@
package expo.modules.filesystem
import okhttp3.RequestBody
import okio.Buffer
import okio.BufferedSink
import okio.ForwardingSink
import okio.Sink
import okio.buffer
import java.io.IOException
@FunctionalInterface
fun interface RequestBodyDecorator {
fun decorate(requestBody: RequestBody): RequestBody
}
@FunctionalInterface
interface CountingRequestListener {
fun onProgress(bytesWritten: Long, contentLength: Long)
}
private class CountingSink(
sink: Sink,
private val requestBody: RequestBody,
private val progressListener: CountingRequestListener
) : ForwardingSink(sink) {
private var bytesWritten = 0L
override fun write(source: Buffer, byteCount: Long) {
super.write(source, byteCount)
bytesWritten += byteCount
progressListener.onProgress(bytesWritten, requestBody.contentLength())
}
}
class CountingRequestBody(
private val requestBody: RequestBody,
private val progressListener: CountingRequestListener
) : RequestBody() {
override fun contentType() = requestBody.contentType()
@Throws(IOException::class)
override fun contentLength() = requestBody.contentLength()
override fun writeTo(sink: BufferedSink) {
val countingSink = CountingSink(sink, this, progressListener)
val bufferedSink = countingSink.buffer()
requestBody.writeTo(bufferedSink)
bufferedSink.flush()
}
}

View File

@@ -0,0 +1,48 @@
package expo.modules.filesystem
import android.content.Context
import expo.modules.interfaces.filesystem.FilePermissionModuleInterface
import expo.modules.core.interfaces.InternalModule
import expo.modules.interfaces.filesystem.Permission
import java.io.File
import java.io.IOException
import java.util.*
// The class needs to be 'open', because it's inherited in expoview
open class FilePermissionModule : FilePermissionModuleInterface, InternalModule {
override fun getExportedInterfaces(): List<Class<*>> =
listOf(FilePermissionModuleInterface::class.java)
override fun getPathPermissions(context: Context, path: String): EnumSet<Permission> =
getInternalPathPermissions(path, context) ?: getExternalPathPermissions(path)
private fun getInternalPathPermissions(path: String, context: Context): EnumSet<Permission>? {
return try {
val canonicalPath = File(path).canonicalPath
getInternalPaths(context)
.firstOrNull { dir -> canonicalPath.startsWith("$dir/") || dir == canonicalPath }
?.let { EnumSet.of(Permission.READ, Permission.WRITE) }
} catch (e: IOException) {
EnumSet.noneOf(Permission::class.java)
}
}
protected open fun getExternalPathPermissions(path: String): EnumSet<Permission> {
val file = File(path)
return EnumSet.noneOf(Permission::class.java).apply {
if (file.canRead()) {
add(Permission.READ)
}
if (file.canWrite()) {
add(Permission.WRITE)
}
}
}
@Throws(IOException::class)
private fun getInternalPaths(context: Context): List<String> =
listOf(
context.filesDir.canonicalPath,
context.cacheDir.canonicalPath
)
}

View File

@@ -0,0 +1,48 @@
package expo.modules.filesystem
import android.net.Uri
import expo.modules.kotlin.exception.CodedException
internal class FileSystemOkHttpNullException :
CodedException("okHttpClient is null")
internal class FileSystemCannotReadDirectoryException(uri: Uri?) :
CodedException("Uri '$uri' doesn't exist or isn't a directory")
internal class FileSystemCannotCreateDirectoryException(uri: Uri?) :
CodedException(
uri?.let {
"Directory '$it' could not be created or already exists"
} ?: "Unknown error"
)
internal class FileSystemUnreadableDirectoryException(uri: String) :
CodedException("No readable files with the uri '$uri'. Please use other uri")
internal class FileSystemCannotCreateFileException(uri: Uri?) :
CodedException(
uri?.let {
"Provided uri '$it' is not pointing to a directory"
} ?: "Unknown error"
)
internal class FileSystemFileNotFoundException(uri: Uri?) :
CodedException("File '$uri' could not be deleted because it could not be found")
internal class FileSystemPendingPermissionsRequestException :
CodedException("You have an unfinished permission request")
internal class FileSystemCannotMoveFileException(fromUri: Uri, toUri: Uri) :
CodedException("File '$fromUri' could not be moved to '$toUri'")
internal class FileSystemUnsupportedSchemeException :
CodedException("Can't read Storage Access Framework directory, use StorageAccessFramework.readDirectoryAsync() instead")
internal class FileSystemCannotFindTaskException :
CodedException("Cannot find task")
internal class FileSystemCopyFailedException(uri: Uri?) :
CodedException("File '$uri' could not be copied because it could not be found")
internal class CookieHandlerNotFoundException :
CodedException("Failed to find CookieHandler")

View File

@@ -0,0 +1,5 @@
package expo.modules.filesystem
import androidx.core.content.FileProvider
class FileSystemFileProvider : FileProvider()

View File

@@ -0,0 +1,10 @@
package expo.modules.filesystem
import android.content.Context
import expo.modules.core.BasePackage
import expo.modules.core.interfaces.InternalModule
class FileSystemPackage : BasePackage() {
override fun createInternalModules(context: Context): List<InternalModule> =
listOf(FilePermissionModule(), AppDirectoriesModule(context))
}

View File

@@ -0,0 +1,92 @@
package expo.modules.filesystem
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.types.Enumerable
data class InfoOptions(
@Field
val md5: Boolean?,
@Field
val size: Boolean?
) : Record
data class DeletingOptions(
@Field
val idempotent: Boolean = false
) : Record
data class ReadingOptions(
@Field
val encoding: EncodingType = EncodingType.UTF8,
@Field
val position: Int?,
@Field
val length: Int?
) : Record
enum class EncodingType(val value: String) : Enumerable {
UTF8("utf8"),
BASE64("base64")
}
enum class SessionType(val value: Int) : Enumerable {
BACKGROUND(0),
FOREGROUND(1)
}
enum class FileSystemUploadType(val value: Int) : Enumerable {
BINARY_CONTENT(0),
MULTIPART(1)
}
data class MakeDirectoryOptions(
@Field
val intermediates: Boolean = false
) : Record
data class RelocatingOptions(
@Field
val from: String,
@Field
val to: String
) : Record
data class DownloadOptions(
@Field
val md5: Boolean = false,
@Field
val cache: Boolean?,
@Field
val headers: Map<String, String>?,
@Field
val sessionType: SessionType = SessionType.BACKGROUND
) : Record
data class WritingOptions(
@Field
val encoding: EncodingType = EncodingType.UTF8
) : Record
data class FileSystemUploadOptions(
@Field
val headers: Map<String, String>?,
@Field
val httpMethod: HttpMethod = HttpMethod.POST,
@Field
val sessionType: SessionType = SessionType.BACKGROUND,
@Field
val uploadType: FileSystemUploadType,
@Field
val fieldName: String?,
@Field
val mimeType: String?,
@Field
val parameters: Map<String, String>?
) : Record
enum class HttpMethod(val value: String) : Enumerable {
POST("POST"),
PUT("PUT"),
PATCH("PATCH")
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="expo_files" path="." />
<cache-path name="cached_expo_files" path="." />
</paths>