feat(ui): NavHost Card2vcf home carnet
EBO <eric.bouhana@softalys.com> committé le 2026-07-22 15:34
8c510d17e5353dfc95a90b194940325569eb4670
1 parent(s)
4 fichiers modifiés
+188
-11
M
android/app/src/main/java/fr/ebii/card2vcf/MainActivity.kt
+9
-11
@@ -11,10 +11,10 @@ import androidx.compose.foundation.layout.systemBarsPadding
| 11 | 11 | import androidx.compose.ui.Modifier |
| 12 | 12 | import fr.ebii.card2vcf.contact.ContactInsertIntent |
| 13 | 13 | import fr.ebii.card2vcf.contact.VcfShare |
| 14 | -import fr.ebii.card2vcf.ocr.TesseractOcrEngine | |
| 14 | +import fr.ebii.card2vcf.data.ContactImageStore | |
| 15 | +import fr.ebii.card2vcf.data.ContactRepository | |
| 15 | 16 | import fr.ebii.card2vcf.scan.OpenCvScanEngine |
| 16 | -import fr.ebii.card2vcf.ui.ScanCarteScreen | |
| 17 | -import fr.ebii.card2vcf.ui.ScanCarteViewModel | |
| 17 | +import fr.ebii.card2vcf.ui.nav.Card2vcfNavHost | |
| 18 | 18 | import fr.ebii.card2vcf.ui.theme.Card2vcfTheme |
| 19 | 19 | import fr.ebii.card2vcf.ui.theme.Canvas |
| 20 | 20 |
@@ -24,10 +24,9 @@ class MainActivity : ComponentActivity() {
| 24 | 24 | enableEdgeToEdge() |
| 25 | 25 | OpenCvScanEngine.ensureOpenCv() |
| 26 | 26 | |
| 27 | - val scanVm = ScanCarteViewModel( | |
| 28 | - scanEngine = OpenCvScanEngine(), | |
| 29 | - ocrEngine = TesseractOcrEngine(this), | |
| 30 | - ) | |
| 27 | + val app = application as Card2vcfApp | |
| 28 | + val images = ContactImageStore(this) | |
| 29 | + val repository = ContactRepository(app.database.crmContactDao(), images) | |
| 31 | 30 | |
| 32 | 31 | setContent { |
| 33 | 32 | Card2vcfTheme { |
@@ -37,8 +36,8 @@ class MainActivity : ComponentActivity() {
| 37 | 36 | .background(Canvas) |
| 38 | 37 | .systemBarsPadding(), |
| 39 | 38 | ) { |
| 40 | - ScanCarteScreen( | |
| 41 | - viewModel = scanVm, | |
| 39 | + Card2vcfNavHost( | |
| 40 | + repository = repository, | |
| 42 | 41 | onCreateContact = { card -> |
| 43 | 42 | startActivity(ContactInsertIntent.build(card)) |
| 44 | 43 | }, |
@@ -47,10 +46,9 @@ class MainActivity : ComponentActivity() {
| 47 | 46 | android.content.Intent.createChooser( |
| 48 | 47 | VcfShare.shareIntent(this@MainActivity, card), |
| 49 | 48 | getString(R.string.scan_exporter_vcf), |
| 50 | - ) | |
| 49 | + ), | |
| 51 | 50 | ) |
| 52 | 51 | }, |
| 53 | - onBack = { /* activité unique */ }, | |
| 54 | 52 | ) |
| 55 | 53 | } |
| 56 | 54 | } |
A
android/app/src/main/java/fr/ebii/card2vcf/ui/carnet/CarnetScreen.kt
+22
-0
@@ -0,0 +1,22 @@
| 1 | +package fr.ebii.card2vcf.ui.carnet | |
| 2 | + | |
| 3 | +import androidx.compose.foundation.layout.Box | |
| 4 | +import androidx.compose.foundation.layout.fillMaxSize | |
| 5 | +import androidx.compose.material3.Text | |
| 6 | +import androidx.compose.runtime.Composable | |
| 7 | +import androidx.compose.ui.Alignment | |
| 8 | +import androidx.compose.ui.Modifier | |
| 9 | + | |
| 10 | +/** Stub — liste / abcédaire / FAB en Task 7. */ | |
| 11 | +@Composable | |
| 12 | +fun CarnetScreen( | |
| 13 | + viewModel: CarnetViewModel, | |
| 14 | + modifier: Modifier = Modifier, | |
| 15 | +) { | |
| 16 | + Box( | |
| 17 | + modifier = modifier.fillMaxSize(), | |
| 18 | + contentAlignment = Alignment.Center, | |
| 19 | + ) { | |
| 20 | + Text("Carnet") | |
| 21 | + } | |
| 22 | +} |
A
android/app/src/main/java/fr/ebii/card2vcf/ui/carnet/CarnetViewModel.kt
+16
-0
@@ -0,0 +1,16 @@
| 1 | +package fr.ebii.card2vcf.ui.carnet | |
| 2 | + | |
| 3 | +import androidx.lifecycle.ViewModel | |
| 4 | +import fr.ebii.card2vcf.data.ContactRepository | |
| 5 | +import fr.ebii.card2vcf.data.CrmContactEntity | |
| 6 | +import kotlinx.coroutines.flow.MutableStateFlow | |
| 7 | +import kotlinx.coroutines.flow.StateFlow | |
| 8 | +import kotlinx.coroutines.flow.asStateFlow | |
| 9 | + | |
| 10 | +/** Stub ViewModel — UI carnet complète en Task 7. */ | |
| 11 | +class CarnetViewModel( | |
| 12 | + private val repository: ContactRepository, | |
| 13 | +) : ViewModel() { | |
| 14 | + private val _contacts = MutableStateFlow<List<CrmContactEntity>>(emptyList()) | |
| 15 | + val contacts: StateFlow<List<CrmContactEntity>> = _contacts.asStateFlow() | |
| 16 | +} |
A
android/app/src/main/java/fr/ebii/card2vcf/ui/nav/Card2vcfNavHost.kt
+141
-0
@@ -0,0 +1,141 @@
| 1 | +package fr.ebii.card2vcf.ui.nav | |
| 2 | + | |
| 3 | +import androidx.compose.foundation.layout.Box | |
| 4 | +import androidx.compose.foundation.layout.fillMaxSize | |
| 5 | +import androidx.compose.material3.Text | |
| 6 | +import androidx.compose.runtime.Composable | |
| 7 | +import androidx.compose.runtime.remember | |
| 8 | +import androidx.compose.ui.Alignment | |
| 9 | +import androidx.compose.ui.Modifier | |
| 10 | +import androidx.compose.ui.platform.LocalContext | |
| 11 | +import androidx.navigation.NavHostController | |
| 12 | +import androidx.navigation.NavType | |
| 13 | +import androidx.navigation.compose.NavHost | |
| 14 | +import androidx.navigation.compose.composable | |
| 15 | +import androidx.navigation.compose.rememberNavController | |
| 16 | +import androidx.navigation.navArgument | |
| 17 | +import fr.ebii.card2vcf.contact.ContactCard | |
| 18 | +import fr.ebii.card2vcf.data.ContactRepository | |
| 19 | +import fr.ebii.card2vcf.ocr.TesseractOcrEngine | |
| 20 | +import fr.ebii.card2vcf.scan.OpenCvScanEngine | |
| 21 | +import fr.ebii.card2vcf.ui.ScanCarteScreen | |
| 22 | +import fr.ebii.card2vcf.ui.ScanCarteViewModel | |
| 23 | +import fr.ebii.card2vcf.ui.carnet.CarnetScreen | |
| 24 | +import fr.ebii.card2vcf.ui.carnet.CarnetViewModel | |
| 25 | + | |
| 26 | +object Routes { | |
| 27 | + const val Carnet = "carnet" | |
| 28 | + const val Fiche = "fiche/{contactId}?sort={sort}" | |
| 29 | + const val Edit = "edit/{contactId}" | |
| 30 | + const val Duplicates = "duplicates/{contactId}" | |
| 31 | + const val Scan = "scan?editId={editId}" | |
| 32 | + const val Manual = "manual" | |
| 33 | + const val Draft = "draft" | |
| 34 | + const val ImportVcf = "import_vcf" | |
| 35 | + | |
| 36 | + fun fiche(contactId: Long, sort: String? = null): String = | |
| 37 | + if (sort != null) "fiche/$contactId?sort=$sort" else "fiche/$contactId" | |
| 38 | + | |
| 39 | + fun edit(contactId: Long): String = "edit/$contactId" | |
| 40 | + | |
| 41 | + fun duplicates(contactId: Long): String = "duplicates/$contactId" | |
| 42 | + | |
| 43 | + fun scan(editId: Long? = null): String = | |
| 44 | + if (editId != null) "scan?editId=$editId" else "scan" | |
| 45 | +} | |
| 46 | + | |
| 47 | +@Composable | |
| 48 | +fun Card2vcfNavHost( | |
| 49 | + repository: ContactRepository, | |
| 50 | + onCreateContact: (ContactCard) -> Unit, | |
| 51 | + onExportVcf: (ContactCard) -> Unit, | |
| 52 | + modifier: Modifier = Modifier, | |
| 53 | + navController: NavHostController = rememberNavController(), | |
| 54 | +) { | |
| 55 | + val carnetVm = remember(repository) { CarnetViewModel(repository) } | |
| 56 | + val context = LocalContext.current | |
| 57 | + val scanVm = remember { | |
| 58 | + ScanCarteViewModel( | |
| 59 | + scanEngine = OpenCvScanEngine(), | |
| 60 | + ocrEngine = TesseractOcrEngine(context.applicationContext), | |
| 61 | + ) | |
| 62 | + } | |
| 63 | + | |
| 64 | + NavHost( | |
| 65 | + navController = navController, | |
| 66 | + startDestination = Routes.Carnet, | |
| 67 | + modifier = modifier, | |
| 68 | + ) { | |
| 69 | + composable(Routes.Carnet) { | |
| 70 | + CarnetScreen(viewModel = carnetVm) | |
| 71 | + } | |
| 72 | + composable( | |
| 73 | + route = Routes.Fiche, | |
| 74 | + arguments = listOf( | |
| 75 | + navArgument("contactId") { type = NavType.LongType }, | |
| 76 | + navArgument("sort") { | |
| 77 | + type = NavType.StringType | |
| 78 | + nullable = true | |
| 79 | + defaultValue = null | |
| 80 | + }, | |
| 81 | + ), | |
| 82 | + ) { entry -> | |
| 83 | + val id = entry.arguments?.getLong("contactId") | |
| 84 | + PlaceholderRoute("Fiche #$id") | |
| 85 | + } | |
| 86 | + composable( | |
| 87 | + route = Routes.Edit, | |
| 88 | + arguments = listOf( | |
| 89 | + navArgument("contactId") { type = NavType.LongType }, | |
| 90 | + ), | |
| 91 | + ) { entry -> | |
| 92 | + val id = entry.arguments?.getLong("contactId") | |
| 93 | + PlaceholderRoute("Édition #$id") | |
| 94 | + } | |
| 95 | + composable( | |
| 96 | + route = Routes.Duplicates, | |
| 97 | + arguments = listOf( | |
| 98 | + navArgument("contactId") { type = NavType.LongType }, | |
| 99 | + ), | |
| 100 | + ) { entry -> | |
| 101 | + val id = entry.arguments?.getLong("contactId") | |
| 102 | + PlaceholderRoute("Doublons #$id") | |
| 103 | + } | |
| 104 | + composable( | |
| 105 | + route = Routes.Scan, | |
| 106 | + arguments = listOf( | |
| 107 | + navArgument("editId") { | |
| 108 | + type = NavType.StringType | |
| 109 | + nullable = true | |
| 110 | + defaultValue = null | |
| 111 | + }, | |
| 112 | + ), | |
| 113 | + ) { | |
| 114 | + ScanCarteScreen( | |
| 115 | + viewModel = scanVm, | |
| 116 | + onCreateContact = onCreateContact, | |
| 117 | + onExportVcf = onExportVcf, | |
| 118 | + onBack = { navController.popBackStack() }, | |
| 119 | + ) | |
| 120 | + } | |
| 121 | + composable(Routes.Manual) { | |
| 122 | + PlaceholderRoute("Saisie manuelle") | |
| 123 | + } | |
| 124 | + composable(Routes.Draft) { | |
| 125 | + PlaceholderRoute("Brouillon") | |
| 126 | + } | |
| 127 | + composable(Routes.ImportVcf) { | |
| 128 | + PlaceholderRoute("Import VCF") | |
| 129 | + } | |
| 130 | + } | |
| 131 | +} | |
| 132 | + | |
| 133 | +@Composable | |
| 134 | +private fun PlaceholderRoute(label: String) { | |
| 135 | + Box( | |
| 136 | + modifier = Modifier.fillMaxSize(), | |
| 137 | + contentAlignment = Alignment.Center, | |
| 138 | + ) { | |
| 139 | + Text(label) | |
| 140 | + } | |
| 141 | +} |
GitRust