Files Page
The "Files Page" is designed to act as a sort of central hub where users or medical professionals can browse, view, and access all the files generated during epilepsy testing. This includes filled questionnaires, timestamped test instructions (consignes), and all final recorded videos from the testing. It can be accessed via the 3rd icon of the navigation bar.
This page is essential for reviewing data (previous tests and recordings), enabling neurologists to monitor their patients' seizures.
This page features :
- A custom top bar with the application logo, a title
"Fichiers", and a user profile icon that navigates to the profile page, - A
TabRowto switch between surveys, test instructions and video tabs :
TabRow(selectedTabIndex = selectedTab) {
Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 }, text = { Text("Questionnaire") })
Tab(selected = selectedTab == 1, onClick = { selectedTab = 1 }, text = { Text("Consignes") })
Tab(selected = selectedTab == 2, onClick = { selectedTab = 2 }, text = { Text("Vidéos") })
}
- A list of files corresponding to the selected tab,
- The custom navigation bar with 4 icons to access the screens
HomeScreen.kt,DemoScreen.kt,FilesPage.kt(current screen), andSettingsPage.kt.
File lists
As explained above, there are 3 types of files that can be accessed through the tabs : filled surveys (questionnaires) as PDF files, timestamped test instructions also as PDF files, and MP4 videos recorded and saved after a testing sequence.
Every file from the type corresponding to the selected tab has to be fetched and listed on the page.
Before accessing storage, we have to check and request READ_EXTERNAL_STORAGE permission :
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
activity?.requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
}
Files can then be fetched from the following directories:
val questionnaireDirectory = File(context.getExternalFilesDir(null), "EpilepsyTests/Questionnaires")
val consigneDirectory = File(context.getExternalFilesDir(null), "EpilepsyTests/Consignes")
val videoDirectory = File(context.getExternalFilesDir(null), "EpilepsyTests/Videos")
The corresonding files are then gathered from these directories based on which tab is open, and each of them are displayed in individual cards, with the following elements inside:
- file name,
- last modified date (or when it was first saved, if not modified),
- file icon (either a PDF icon or a small video thumbnail).
All PDF file cards are then displayed with the function FileCard, and execute the function openPDF when clicked (which opens files using FileProvider) :
@Composable
fun FileCard(file: File, context: Context) {
Card(modifier = Modifier.clickable { openPDF(context, file) }) {
Row { /* Icon + file info */ }
}
}
...
fun openPDF(context: Context, file: File) {
try {
if (file.exists()) {
val uri = FileProvider.getUriForFile(
context,
"${context.packageName}.provider",
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/pdf")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NO_HISTORY)
}
context.startActivity(Intent.createChooser(intent, "Ouvrir avec"))
} else {
Log.e("FileProvider", "Le fichier PDF n'existe pas.")
Toast.makeText(context, "Le fichier PDF n'existe pas.", Toast.LENGTH_LONG).show()
}
} catch (e: Exception) {
Log.e("FileProvider", "Erreur lors de l'ouverture du fichier PDF: ${e.message}")
Toast.makeText(context, "Erreur: Impossible d'ouvrir le fichier PDF.", Toast.LENGTH_LONG).show()
}
}
Similarily to PDF files, video cards are displayed with the function VideoCard, and execute the function openVideo when clicked (also opens videos using FileProvider) :
@Composable
fun VideoCard(file: File, context: Context) {
val thumbnailBitmap by remember(file) {
mutableStateOf(
ThumbnailUtils.createVideoThumbnail(
file.absolutePath, // Absolute path is needed to find the videos
MediaStore.Video.Thumbnails.MINI_KIND
)
)
}
Card(
modifier = Modifier.clickable { openVideo(context, file)}) {
Row {/* Icon + file info */}
}
}
...
fun openVideo(context: Context, file: File) {
try {
if (file.exists()) {
val uri = FileProvider.getUriForFile(
context,
"${context.packageName}.provider",
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "video/mp4")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NO_HISTORY)
}
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(Intent.createChooser(intent, "Ouvrir avec"))
} else {
Log.e("FileProvider", "Aucune application compatible trouvée pour ouvrir la vidéo.")
Toast.makeText(context, "Aucune application compatible pour ouvrir cette vidéo.", Toast.LENGTH_LONG).show()
}
} else {
Log.e("FileProvider", "La vidéo n'existe pas.")
Toast.makeText(context, "La vidéo n'existe pas.", Toast.LENGTH_LONG).show()
}
} catch (e: Exception) {
Log.e("FileProvider", "Erreur lors de l'ouverture de la vidéo: ${e.message}")
Toast.makeText(context, "Erreur: Impossible d'ouvrir la vidéo.", Toast.LENGTH_LONG).show()
}
}