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