Initial commit
This commit is contained in:
131
app/build.gradle.kts
Normal file
131
app/build.gradle.kts
Normal file
@@ -0,0 +1,131 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-kapt")
|
||||
id("dagger.hilt.android.plugin")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.smoa"
|
||||
compileSdk = AppConfig.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
applicationId = AppConfig.applicationId
|
||||
minSdk = AppConfig.minSdk
|
||||
targetSdk = AppConfig.targetSdk
|
||||
versionCode = AppConfig.versionCode
|
||||
versionName = AppConfig.versionName
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.4"
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
excludes += "/META-INF/DEPENDENCIES"
|
||||
excludes += "/META-INF/LICENSE*"
|
||||
excludes += "/META-INF/NOTICE*"
|
||||
pickFirsts += "META-INF/blueprint.handlers"
|
||||
pickFirsts += "META-INF/blueprint.schemas"
|
||||
pickFirsts += "META-INF/spring.schemas"
|
||||
pickFirsts += "META-INF/spring.handlers"
|
||||
pickFirsts += "META-INF/wsdl.plugin.xml"
|
||||
pickFirsts += "META-INF/cxf/bus-extensions.txt"
|
||||
pickFirsts += "org/apache/cxf/endpoint/dynamic/simple-binding.xjb"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(Dependencies.composeBom))
|
||||
implementation(Dependencies.composeUi)
|
||||
implementation(Dependencies.composeUiGraphics)
|
||||
implementation(Dependencies.composeUiToolingPreview)
|
||||
implementation(Dependencies.composeMaterial3)
|
||||
implementation(Dependencies.androidxActivityCompose)
|
||||
implementation(Dependencies.androidxCoreKtx)
|
||||
implementation(Dependencies.androidxLifecycleRuntimeKtx)
|
||||
|
||||
// Navigation
|
||||
implementation(Dependencies.navigationCompose)
|
||||
|
||||
// Hilt
|
||||
implementation(Dependencies.hiltAndroid)
|
||||
kapt(Dependencies.hiltAndroidCompiler)
|
||||
implementation(Dependencies.hiltNavigationCompose)
|
||||
|
||||
// Core modules
|
||||
implementation(project(":core:auth"))
|
||||
implementation(project(":core:security"))
|
||||
implementation(project(":core:common"))
|
||||
implementation(project(":core:barcode"))
|
||||
implementation(project(":core:as4"))
|
||||
implementation(project(":core:eidas"))
|
||||
implementation(project(":core:signing"))
|
||||
implementation(project(":core:certificates"))
|
||||
|
||||
// Feature modules
|
||||
implementation(project(":modules:credentials"))
|
||||
implementation(project(":modules:directory"))
|
||||
implementation(project(":modules:communications"))
|
||||
implementation(project(":modules:meetings"))
|
||||
implementation(project(":modules:browser"))
|
||||
implementation(project(":modules:orders"))
|
||||
implementation(project(":modules:evidence"))
|
||||
implementation(project(":modules:reports"))
|
||||
implementation(project(":modules:atf"))
|
||||
implementation(project(":modules:ncic"))
|
||||
implementation(project(":modules:military"))
|
||||
implementation(project(":modules:judicial"))
|
||||
implementation(project(":modules:intelligence"))
|
||||
|
||||
// Security
|
||||
implementation(Dependencies.securityCrypto)
|
||||
implementation(Dependencies.biometric)
|
||||
|
||||
// Coroutines
|
||||
implementation(Dependencies.coroutinesCore)
|
||||
implementation(Dependencies.coroutinesAndroid)
|
||||
|
||||
// Testing
|
||||
testImplementation(Dependencies.junit)
|
||||
androidTestImplementation(Dependencies.androidxJunit)
|
||||
androidTestImplementation(Dependencies.espressoCore)
|
||||
androidTestImplementation(platform(Dependencies.composeBom))
|
||||
androidTestImplementation(Dependencies.composeUiTestJunit4)
|
||||
debugImplementation(Dependencies.composeUiTooling)
|
||||
debugImplementation(Dependencies.composeUiTestManifest)
|
||||
}
|
||||
|
||||
26
app/proguard-rules.pro
vendored
Normal file
26
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
|
||||
# Keep Hilt classes
|
||||
-keep class dagger.hilt.** { *; }
|
||||
-keep class javax.inject.** { *; }
|
||||
-keep class * extends dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper { *; }
|
||||
|
||||
# Keep Room classes
|
||||
-keep class * extends androidx.room.RoomDatabase
|
||||
-keep @androidx.room.Entity class *
|
||||
|
||||
# Keep data classes
|
||||
-keepclassmembers class * {
|
||||
@androidx.room.* <methods>;
|
||||
}
|
||||
|
||||
# Keep security-related classes
|
||||
-keep class androidx.security.** { *; }
|
||||
-dontwarn androidx.security.**
|
||||
|
||||
# Keep Kotlin coroutines
|
||||
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
|
||||
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
|
||||
|
||||
58
app/src/main/AndroidManifest.xml
Normal file
58
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Network permissions -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<!-- Biometric permissions -->
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<!-- Audio permissions for communications and meetings -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
<!-- Camera permission for meetings -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<!-- VPN permission -->
|
||||
<uses-permission android:name="android.permission.BIND_VPN_SERVICE" />
|
||||
|
||||
<!-- Storage permissions (restricted) -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
|
||||
<application
|
||||
android:name=".SMOAApplication"
|
||||
android:allowBackup="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.SMOA"
|
||||
android:usesCleartextTraffic="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:targetApi="31">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.SMOA"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:screenOrientation="fullSensor">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
111
app/src/main/java/com/smoa/MainActivity.kt
Normal file
111
app/src/main/java/com/smoa/MainActivity.kt
Normal file
@@ -0,0 +1,111 @@
|
||||
package com.smoa
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.smoa.core.common.ConnectivityManager
|
||||
import com.smoa.core.common.FoldableStateManager
|
||||
import com.smoa.ui.main.MainScreen
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var connectivityManager: ConnectivityManager
|
||||
|
||||
@Inject
|
||||
lateinit var foldableStateManager: FoldableStateManager
|
||||
|
||||
@Inject
|
||||
lateinit var userSession: com.smoa.core.auth.UserSession
|
||||
|
||||
@Inject
|
||||
lateinit var directoryService: com.smoa.modules.directory.domain.DirectoryService
|
||||
|
||||
@Inject
|
||||
lateinit var communicationsService: com.smoa.modules.communications.domain.CommunicationsService
|
||||
|
||||
@Inject
|
||||
lateinit var meetingsService: com.smoa.modules.meetings.domain.MeetingsService
|
||||
|
||||
@Inject
|
||||
lateinit var browserService: com.smoa.modules.browser.domain.BrowserService
|
||||
|
||||
@Inject
|
||||
lateinit var urlFilter: com.smoa.modules.browser.domain.URLFilter
|
||||
|
||||
@Inject
|
||||
lateinit var screenProtection: com.smoa.core.security.ScreenProtection
|
||||
|
||||
@Inject
|
||||
lateinit var vpnManager: com.smoa.core.security.VPNManager
|
||||
|
||||
@Inject
|
||||
lateinit var rbacFramework: com.smoa.core.auth.RBACFramework
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Enable screen protection for the entire application
|
||||
screenProtection.enableScreenProtection(this)
|
||||
|
||||
// Start VPN monitoring
|
||||
vpnManager.startVPNMonitoring()
|
||||
|
||||
// Update fold state on configuration change
|
||||
updateFoldState()
|
||||
|
||||
// Initialize default user session for testing
|
||||
// In production, this would come from authentication
|
||||
userSession.setUser(
|
||||
com.smoa.core.auth.UserInfo(
|
||||
userId = "user1",
|
||||
userName = "Test User",
|
||||
role = com.smoa.core.auth.RBACFramework.Role.OPERATOR,
|
||||
unit = "Unit1",
|
||||
clearanceLevel = null,
|
||||
missionAssignment = null
|
||||
)
|
||||
)
|
||||
|
||||
setContent {
|
||||
MaterialTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
MainScreen(
|
||||
connectivityManager = connectivityManager,
|
||||
foldableStateManager = foldableStateManager,
|
||||
userSession = userSession,
|
||||
rbacFramework = rbacFramework,
|
||||
directoryService = directoryService,
|
||||
communicationsService = communicationsService,
|
||||
meetingsService = meetingsService,
|
||||
browserService = browserService,
|
||||
urlFilter = urlFilter,
|
||||
screenProtection = screenProtection,
|
||||
vpnManager = vpnManager
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
updateFoldState()
|
||||
}
|
||||
|
||||
private fun updateFoldState() {
|
||||
foldableStateManager.updateFoldState(resources.configuration)
|
||||
}
|
||||
}
|
||||
|
||||
12
app/src/main/java/com/smoa/SMOAApplication.kt
Normal file
12
app/src/main/java/com/smoa/SMOAApplication.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.smoa
|
||||
|
||||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
@HiltAndroidApp
|
||||
class SMOAApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
}
|
||||
}
|
||||
|
||||
180
app/src/main/java/com/smoa/ui/main/MainScreen.kt
Normal file
180
app/src/main/java/com/smoa/ui/main/MainScreen.kt
Normal file
@@ -0,0 +1,180 @@
|
||||
package com.smoa.ui.main
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.smoa.core.auth.RBACFramework
|
||||
import com.smoa.core.auth.UserSession
|
||||
import com.smoa.core.common.ConnectivityManager
|
||||
import com.smoa.core.common.FoldableStateManager
|
||||
import com.smoa.modules.browser.domain.BrowserService
|
||||
import com.smoa.modules.browser.domain.URLFilter
|
||||
import com.smoa.modules.communications.domain.CommunicationsService
|
||||
import com.smoa.modules.directory.domain.DirectoryService
|
||||
import com.smoa.modules.meetings.domain.MeetingsService
|
||||
import com.smoa.core.security.ScreenProtection
|
||||
import com.smoa.core.security.VPNManager
|
||||
import com.smoa.ui.navigation.SMOANavigation
|
||||
|
||||
/**
|
||||
* Main application screen with module navigation.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
connectivityManager: ConnectivityManager,
|
||||
foldableStateManager: FoldableStateManager,
|
||||
userSession: UserSession,
|
||||
rbacFramework: RBACFramework,
|
||||
directoryService: DirectoryService,
|
||||
communicationsService: CommunicationsService,
|
||||
meetingsService: MeetingsService,
|
||||
browserService: BrowserService,
|
||||
urlFilter: URLFilter,
|
||||
screenProtection: ScreenProtection,
|
||||
vpnManager: VPNManager,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val currentUser by userSession.currentUser.collectAsState()
|
||||
var drawerOpen by remember { mutableStateOf(false) }
|
||||
|
||||
val userRole = currentUser?.role ?: RBACFramework.Role.GUEST
|
||||
val userUnit = currentUser?.unit
|
||||
val userId = currentUser?.userId ?: "guest"
|
||||
|
||||
val drawerState = remember { DrawerState(DrawerValue.Closed) }
|
||||
|
||||
// Update drawer state when drawerOpen changes
|
||||
LaunchedEffect(drawerOpen) {
|
||||
if (drawerOpen) {
|
||||
drawerState.open()
|
||||
} else {
|
||||
drawerState.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Update drawerOpen when drawer state changes
|
||||
LaunchedEffect(drawerState.currentValue) {
|
||||
drawerOpen = drawerState.currentValue == DrawerValue.Open
|
||||
}
|
||||
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
com.smoa.ui.navigation.NavigationDrawer(
|
||||
navController = navController,
|
||||
userSession = userSession,
|
||||
rbacFramework = rbacFramework,
|
||||
onDrawerDismiss = { drawerOpen = false }
|
||||
)
|
||||
},
|
||||
modifier = modifier
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("SMOA") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { drawerOpen = true }) {
|
||||
Icon(Icons.Default.Menu, contentDescription = "Menu")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
// VPN status indicator
|
||||
VPNStatusIndicator(vpnManager)
|
||||
// Connectivity status indicator
|
||||
ConnectivityStatusIndicator(connectivityManager)
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
) { paddingValues ->
|
||||
SMOANavigation(
|
||||
navController = navController,
|
||||
connectivityManager = connectivityManager,
|
||||
foldableStateManager = foldableStateManager,
|
||||
directoryService = directoryService,
|
||||
communicationsService = communicationsService,
|
||||
meetingsService = meetingsService,
|
||||
browserService = browserService,
|
||||
urlFilter = urlFilter,
|
||||
screenProtection = screenProtection,
|
||||
userRole = userRole,
|
||||
userUnit = userUnit,
|
||||
userId = userId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VPNStatusIndicator(
|
||||
vpnManager: VPNManager,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val vpnState by vpnManager.vpnState.collectAsState()
|
||||
val statusText = when (vpnState) {
|
||||
com.smoa.core.security.VPNState.Connected -> "VPN"
|
||||
com.smoa.core.security.VPNState.Disconnected -> "NO VPN"
|
||||
com.smoa.core.security.VPNState.PermissionRequired -> "VPN REQ"
|
||||
com.smoa.core.security.VPNState.PermissionGranted -> "VPN OK"
|
||||
com.smoa.core.security.VPNState.Error -> "VPN ERR"
|
||||
com.smoa.core.security.VPNState.Unknown -> "VPN ?"
|
||||
}
|
||||
|
||||
Text(
|
||||
text = statusText,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = when (vpnState) {
|
||||
com.smoa.core.security.VPNState.Connected -> MaterialTheme.colorScheme.primary
|
||||
com.smoa.core.security.VPNState.Disconnected,
|
||||
com.smoa.core.security.VPNState.PermissionRequired -> MaterialTheme.colorScheme.error
|
||||
else -> MaterialTheme.colorScheme.onSurface
|
||||
},
|
||||
modifier = modifier.padding(horizontal = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConnectivityStatusIndicator(
|
||||
connectivityManager: ConnectivityManager,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val state = connectivityManager.connectivityState.value
|
||||
val statusText = when (state) {
|
||||
ConnectivityManager.ConnectivityState.Online -> "ONLINE"
|
||||
ConnectivityManager.ConnectivityState.Offline -> "OFFLINE"
|
||||
ConnectivityManager.ConnectivityState.Restricted -> "RESTRICTED"
|
||||
ConnectivityManager.ConnectivityState.Unknown -> "UNKNOWN"
|
||||
}
|
||||
|
||||
Text(
|
||||
text = statusText,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = modifier.padding(horizontal = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
117
app/src/main/java/com/smoa/ui/navigation/NavigationDrawer.kt
Normal file
117
app/src/main/java/com/smoa/ui/navigation/NavigationDrawer.kt
Normal file
@@ -0,0 +1,117 @@
|
||||
package com.smoa.ui.navigation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.smoa.core.auth.RBACFramework
|
||||
import com.smoa.core.auth.UserSession
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Navigation drawer for module selection.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NavigationDrawer(
|
||||
navController: NavController,
|
||||
userSession: UserSession,
|
||||
rbacFramework: RBACFramework,
|
||||
onDrawerDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val currentUser by userSession.currentUser.collectAsState()
|
||||
val userRole = currentUser?.role ?: RBACFramework.Role.GUEST
|
||||
|
||||
ModalDrawerSheet(
|
||||
modifier = modifier.width(280.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// User info header
|
||||
Text(
|
||||
text = currentUser?.userName ?: "Guest",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Role: ${userRole.name}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 24.dp)
|
||||
)
|
||||
|
||||
Divider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
|
||||
// Navigation items
|
||||
NavigationDrawerItem(
|
||||
label = { Text("Credentials") },
|
||||
selected = navController.currentDestination?.route == SMOARoute.Credentials.route,
|
||||
onClick = {
|
||||
navController.navigate(SMOARoute.Credentials.route)
|
||||
onDrawerDismiss()
|
||||
},
|
||||
icon = { Icon(Icons.Default.Info, contentDescription = null) }
|
||||
)
|
||||
|
||||
if (rbacFramework.canAccessModule(userRole, RBACFramework.Module.DIRECTORY)) {
|
||||
NavigationDrawerItem(
|
||||
label = { Text("Directory") },
|
||||
selected = navController.currentDestination?.route == SMOARoute.Directory.route,
|
||||
onClick = {
|
||||
navController.navigate(SMOARoute.Directory.route)
|
||||
onDrawerDismiss()
|
||||
},
|
||||
icon = { Icon(Icons.Default.Person, contentDescription = null) }
|
||||
)
|
||||
}
|
||||
|
||||
if (rbacFramework.canAccessModule(userRole, RBACFramework.Module.COMMUNICATIONS)) {
|
||||
NavigationDrawerItem(
|
||||
label = { Text("Communications") },
|
||||
selected = navController.currentDestination?.route == SMOARoute.Communications.route,
|
||||
onClick = {
|
||||
navController.navigate(SMOARoute.Communications.route)
|
||||
onDrawerDismiss()
|
||||
},
|
||||
icon = { Icon(Icons.Default.Phone, contentDescription = null) }
|
||||
)
|
||||
}
|
||||
|
||||
if (rbacFramework.canAccessModule(userRole, RBACFramework.Module.MEETINGS)) {
|
||||
NavigationDrawerItem(
|
||||
label = { Text("Meetings") },
|
||||
selected = navController.currentDestination?.route == SMOARoute.Meetings.route,
|
||||
onClick = {
|
||||
navController.navigate(SMOARoute.Meetings.route)
|
||||
onDrawerDismiss()
|
||||
},
|
||||
icon = { Icon(Icons.Default.Phone, contentDescription = null) }
|
||||
)
|
||||
}
|
||||
|
||||
if (rbacFramework.canAccessModule(userRole, RBACFramework.Module.BROWSER)) {
|
||||
NavigationDrawerItem(
|
||||
label = { Text("Browser") },
|
||||
selected = navController.currentDestination?.route == SMOARoute.Browser.route,
|
||||
onClick = {
|
||||
navController.navigate(SMOARoute.Browser.route)
|
||||
onDrawerDismiss()
|
||||
},
|
||||
icon = { Icon(Icons.Default.Info, contentDescription = null) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
app/src/main/java/com/smoa/ui/navigation/NavigationModule.kt
Normal file
99
app/src/main/java/com/smoa/ui/navigation/NavigationModule.kt
Normal file
@@ -0,0 +1,99 @@
|
||||
package com.smoa.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.smoa.core.auth.RBACFramework
|
||||
import com.smoa.modules.browser.BrowserModule
|
||||
import com.smoa.modules.browser.domain.BrowserService
|
||||
import com.smoa.modules.browser.domain.URLFilter
|
||||
import com.smoa.modules.communications.CommunicationsModule
|
||||
import com.smoa.modules.communications.domain.CommunicationsService
|
||||
import com.smoa.modules.credentials.CredentialsModule
|
||||
import com.smoa.modules.directory.DirectoryModule
|
||||
import com.smoa.modules.directory.domain.DirectoryService
|
||||
import com.smoa.modules.meetings.MeetingsModule
|
||||
import com.smoa.modules.meetings.domain.MeetingsService
|
||||
import com.smoa.core.common.ConnectivityManager
|
||||
import com.smoa.core.common.FoldableStateManager
|
||||
import com.smoa.core.security.ScreenProtection
|
||||
|
||||
/**
|
||||
* Navigation routes for SMOA modules.
|
||||
*/
|
||||
sealed class SMOARoute(val route: String) {
|
||||
object Credentials : SMOARoute("credentials")
|
||||
object Directory : SMOARoute("directory")
|
||||
object Communications : SMOARoute("communications")
|
||||
object Meetings : SMOARoute("meetings")
|
||||
object Browser : SMOARoute("browser")
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigation module for SMOA.
|
||||
* Handles navigation between different modules.
|
||||
*/
|
||||
@Composable
|
||||
fun SMOANavigation(
|
||||
navController: NavHostController,
|
||||
connectivityManager: ConnectivityManager,
|
||||
foldableStateManager: FoldableStateManager,
|
||||
directoryService: DirectoryService,
|
||||
communicationsService: CommunicationsService,
|
||||
meetingsService: MeetingsService,
|
||||
browserService: BrowserService,
|
||||
urlFilter: URLFilter,
|
||||
screenProtection: ScreenProtection,
|
||||
userRole: RBACFramework.Role,
|
||||
userUnit: String?,
|
||||
userId: String
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = SMOARoute.Credentials.route
|
||||
) {
|
||||
composable(SMOARoute.Credentials.route) {
|
||||
CredentialsModule(
|
||||
modifier = androidx.compose.ui.Modifier
|
||||
)
|
||||
}
|
||||
|
||||
composable(SMOARoute.Directory.route) {
|
||||
DirectoryModule(
|
||||
directoryService = directoryService,
|
||||
userRole = userRole,
|
||||
userUnit = userUnit,
|
||||
modifier = androidx.compose.ui.Modifier
|
||||
)
|
||||
}
|
||||
|
||||
composable(SMOARoute.Communications.route) {
|
||||
CommunicationsModule(
|
||||
communicationsService = communicationsService,
|
||||
userRole = userRole,
|
||||
userUnit = userUnit,
|
||||
modifier = androidx.compose.ui.Modifier
|
||||
)
|
||||
}
|
||||
|
||||
composable(SMOARoute.Meetings.route) {
|
||||
MeetingsModule(
|
||||
meetingsService = meetingsService,
|
||||
userRole = userRole,
|
||||
userId = userId,
|
||||
modifier = androidx.compose.ui.Modifier
|
||||
)
|
||||
}
|
||||
|
||||
composable(SMOARoute.Browser.route) {
|
||||
BrowserModule(
|
||||
browserService = browserService,
|
||||
urlFilter = urlFilter,
|
||||
screenProtection = screenProtection,
|
||||
modifier = androidx.compose.ui.Modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/white"/>
|
||||
<foreground android:drawable="@android:drawable/ic_menu_gallery"/>
|
||||
</adaptive-icon>
|
||||
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/white"/>
|
||||
<foreground android:drawable="@android:drawable/ic_menu_gallery"/>
|
||||
</adaptive-icon>
|
||||
|
||||
33
app/src/main/res/values/strings.xml
Normal file
33
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">SMOA</string>
|
||||
|
||||
<!-- Authentication -->
|
||||
<string name="auth_pin_required">Enter PIN</string>
|
||||
<string name="auth_biometric_required">Biometric Authentication Required</string>
|
||||
<string name="auth_fingerprint_required">Fingerprint Required</string>
|
||||
<string name="auth_facial_required">Facial Recognition Required</string>
|
||||
<string name="auth_all_factors_required">All authentication factors required</string>
|
||||
<string name="auth_lockout">Too many failed attempts. Account locked.</string>
|
||||
<string name="auth_retry">Retry</string>
|
||||
|
||||
<!-- Connectivity -->
|
||||
<string name="status_online">ONLINE</string>
|
||||
<string name="status_offline">OFFLINE</string>
|
||||
<string name="status_restricted">RESTRICTED</string>
|
||||
|
||||
<!-- Modules -->
|
||||
<string name="module_credentials">Issued Credentials</string>
|
||||
<string name="module_directory">Internal Directory</string>
|
||||
<string name="module_communications">Unit Communications</string>
|
||||
<string name="module_meetings">Secure Meetings</string>
|
||||
<string name="module_browser">Controlled Browser</string>
|
||||
|
||||
<!-- Common -->
|
||||
<string name="lock">Lock</string>
|
||||
<string name="unlock">Unlock</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="error">Error</string>
|
||||
</resources>
|
||||
|
||||
7
app/src/main/res/values/themes.xml
Normal file
7
app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.SMOA" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
8
app/src/main/res/xml/backup_rules.xml
Normal file
8
app/src/main/res/xml/backup_rules.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<!-- Exclude sensitive data from backups -->
|
||||
<exclude domain="sharedpref" path="secure_prefs.xml" />
|
||||
<exclude domain="database" path="smoa_database.db" />
|
||||
<exclude domain="database" path="audit_logs.db" />
|
||||
</full-backup-content>
|
||||
|
||||
9
app/src/main/res/xml/data_extraction_rules.xml
Normal file
9
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="sharedpref" path="secure_prefs.xml" />
|
||||
<exclude domain="database" path="smoa_database.db" />
|
||||
<exclude domain="database" path="audit_logs.db" />
|
||||
</cloud-backup>
|
||||
</data-extraction-rules>
|
||||
|
||||
12
app/src/main/res/xml/network_security_config.xml
Normal file
12
app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<!-- Base configuration for production -->
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<!-- Certificate pinning will be configured programmatically -->
|
||||
</network-security-config>
|
||||
|
||||
Reference in New Issue
Block a user