diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cbd..8978d23d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad51d2d8..46b65ead 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -94,7 +94,6 @@ dependencies { implementation(libs.androidx.lifecycle.extensions) implementation(libs.androidx.room.ktx) implementation(libs.androidx.runtime.livedata) - annotationProcessor(libs.androidx.room.compiler) ksp(libs.androidx.room.compiler) // Lifecycle @@ -123,6 +122,12 @@ dependencies { // In-App Update implementation(libs.app.update) implementation(libs.app.update.ktx) + + // Gson + implementation(libs.gson) + + implementation(libs.zoomable) + implementation(libs.zoomable.image.coil) } ktlint { diff --git a/app/src/main/java/com/aritra/notify/components/note/GridNoteCard.kt b/app/src/main/java/com/aritra/notify/components/note/GridNoteCard.kt index f3c7282d..1d17aa4d 100644 --- a/app/src/main/java/com/aritra/notify/components/note/GridNoteCard.kt +++ b/app/src/main/java/com/aritra/notify/components/note/GridNoteCard.kt @@ -1,6 +1,7 @@ package com.aritra.notify.components.note import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CardDefaults import androidx.compose.material3.OutlinedCard @@ -63,14 +65,25 @@ fun GridNoteCard( .padding(16.dp) .fillMaxWidth() ) { - AsyncImage( - model = ImageRequest.Builder(context) - .data(painter.value) - .build(), - contentDescription = "Image", - modifier = Modifier.fillMaxWidth() - - ) + if (painter.value.isNotEmpty()) { + Row( + modifier = Modifier + .height(80.dp) + .clip(RoundedCornerShape(8.dp)) + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + painter.value.forEach { + AsyncImage( + model = ImageRequest.Builder(context) + .data(it ?: "") + .build(), + contentDescription = "Image", + modifier = Modifier.fillMaxWidth() + ) + } + } + } Row( horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() diff --git a/app/src/main/java/com/aritra/notify/components/note/NoteCard.kt b/app/src/main/java/com/aritra/notify/components/note/NoteCard.kt index 4700aafb..aa8d6f5b 100644 --- a/app/src/main/java/com/aritra/notify/components/note/NoteCard.kt +++ b/app/src/main/java/com/aritra/notify/components/note/NoteCard.kt @@ -1,13 +1,16 @@ package com.aritra.notify.components.note import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CardDefaults import androidx.compose.material3.OutlinedCard @@ -54,14 +57,26 @@ fun NotesCard( .fillMaxWidth() .padding(16.dp) ) { - AsyncImage( - model = ImageRequest.Builder(context) - .data(painter.value) - .build(), - contentDescription = "Image", - modifier = Modifier.fillMaxSize() - ) - Spacer(modifier = Modifier.height(10.dp)) + if (painter.value.isNotEmpty()) { + Row( + modifier = Modifier + .height(80.dp) + .clip(RoundedCornerShape(8.dp)) + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + painter.value.forEach { + AsyncImage( + model = ImageRequest.Builder(context) + .data(it ?: "") + .build(), + contentDescription = "Image", + modifier = Modifier.fillMaxWidth() + ) + } + } + Spacer(modifier = Modifier.height(10.dp)) + } Text( text = noteModel.title, fontSize = 22.sp, diff --git a/app/src/main/java/com/aritra/notify/data/converters/ListConverter.kt b/app/src/main/java/com/aritra/notify/data/converters/ListConverter.kt new file mode 100644 index 00000000..4057c090 --- /dev/null +++ b/app/src/main/java/com/aritra/notify/data/converters/ListConverter.kt @@ -0,0 +1,27 @@ +package com.aritra.notify.data.converters + +import android.net.Uri +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +object ListConverter { + + private inline fun Gson.fromJson(json: String): T = + fromJson(json, object : TypeToken() {}.type) + + @TypeConverter + fun fromListToString(value: List): String { + return Gson().toJson(value.map { it?.toString() ?: "" }) + } + + @TypeConverter + fun fromStringToList(value: String): List { + return try { + val stringList = Gson().fromJson>(value) // using extension function + stringList.map { Uri.parse(it) } + } catch (e: Exception) { + listOf() + } + } +} diff --git a/app/src/main/java/com/aritra/notify/data/db/NoteDatabase.kt b/app/src/main/java/com/aritra/notify/data/db/NoteDatabase.kt index 2361422d..1e014d38 100644 --- a/app/src/main/java/com/aritra/notify/data/db/NoteDatabase.kt +++ b/app/src/main/java/com/aritra/notify/data/db/NoteDatabase.kt @@ -7,7 +7,7 @@ import androidx.room.RoomDatabase import com.aritra.notify.data.dao.NoteDao import com.aritra.notify.domain.models.Note -@Database(entities = [Note::class], version = 2) +@Database(entities = [Note::class], version = 3) abstract class NoteDatabase : RoomDatabase() { abstract fun noteDao(): NoteDao diff --git a/app/src/main/java/com/aritra/notify/domain/models/Note.kt b/app/src/main/java/com/aritra/notify/domain/models/Note.kt index bfc1c2a8..95182627 100644 --- a/app/src/main/java/com/aritra/notify/domain/models/Note.kt +++ b/app/src/main/java/com/aritra/notify/domain/models/Note.kt @@ -7,17 +7,18 @@ import androidx.room.PrimaryKey import androidx.room.TypeConverters import com.aritra.notify.data.converters.DateTypeConverter import com.aritra.notify.data.converters.UriConverter +import com.aritra.notify.data.converters.ListConverter import kotlinx.parcelize.Parcelize import java.util.Date @Parcelize @Entity(tableName = "note") -@TypeConverters(DateTypeConverter::class, UriConverter::class) +@TypeConverters(DateTypeConverter::class, UriConverter::class, ListConverter::class) data class Note( @PrimaryKey(autoGenerate = true) var id: Int = 0, var title: String, var note: String, var dateTime: Date?, - var image: Uri?, + var image: List, ) : Parcelable diff --git a/app/src/main/java/com/aritra/notify/domain/repository/BackupRepository.kt b/app/src/main/java/com/aritra/notify/domain/repository/BackupRepository.kt index 9c0f7f50..a4a4d588 100644 --- a/app/src/main/java/com/aritra/notify/domain/repository/BackupRepository.kt +++ b/app/src/main/java/com/aritra/notify/domain/repository/BackupRepository.kt @@ -48,8 +48,8 @@ class BackupRepository( // write the notes to the csv file provider.noteDao().getAllNotes().first().forEach { note -> // write the image to the backup directory if it exists - val image = note.image?.let { image -> - val imageName = "image_${note.id}.webp" + val images = note.image.filterNotNull().mapIndexed { index, image -> + val imageName = "image_${note.id}_($index).webp" val imageFile = File(backupDir, imageName) context.contentResolver.openInputStream(image)?.use { inputStream -> imageFile.outputStream().use { outputStream -> @@ -63,9 +63,8 @@ class BackupRepository( note.id.toString(), note.title, note.note, - DateTypeConverter.toString(note.dateTime).orEmpty(), - image.orEmpty() - ) + DateTypeConverter.toString(note.dateTime).orEmpty() + ).plus(images) ) } // close the csv writer @@ -136,32 +135,39 @@ class BackupRepository( val title = columns[1] val text = columns[2] val dateTime = DateTypeConverter.toDate(columns[3]) - val imageName = columns[4] - val image = if (imageName.isNotEmpty()) { - val imageStore = SaveSelectedImageUseCase.image(context, id) - // copy the image from the restore directory to the cache directory - context.contentResolver.openInputStream( - Uri.fromFile(File(restoreDir, imageName)) - )?.use { inputStream -> - imageStore.outputStream().use { outputStream -> - inputStream.copyTo(outputStream) + val imageNameList = columns.slice(IntRange(4, columns.size - 1)) + val images = imageNameList.map { imageName -> + if (imageName.isNotEmpty()) { + val index = + imageName.substringBeforeLast(".webp") + .substringAfterLast('_') + .trim('(', ')') + .toInt() + val imageStore = SaveSelectedImageUseCase.image(context, id, index) + // copy the image from the restore directory to the cache directory + context.contentResolver.openInputStream( + Uri.fromFile(File(restoreDir, imageName)) + )?.use { inputStream -> + imageStore.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) + } } + // get the uri for the image in the cache directory + FileProvider.getUriForFile( + context, + "${context.packageName}.provider", + imageStore + ) + } else { + null } - // get the uri for the image in the cache directory - FileProvider.getUriForFile( - context, - "${context.packageName}.provider", - imageStore - ) - } else { - null } val note = Note( id = id, title = title, note = text, dateTime = dateTime, - image = image + image = images ) Log.d(BackupRepository::class.simpleName, "import: $note") provider.noteDao().insertNote( diff --git a/app/src/main/java/com/aritra/notify/domain/usecase/SaveSelectedImageUseCase.kt b/app/src/main/java/com/aritra/notify/domain/usecase/SaveSelectedImageUseCase.kt index ae4f75f4..80e0ff32 100644 --- a/app/src/main/java/com/aritra/notify/domain/usecase/SaveSelectedImageUseCase.kt +++ b/app/src/main/java/com/aritra/notify/domain/usecase/SaveSelectedImageUseCase.kt @@ -15,22 +15,19 @@ object SaveSelectedImageUseCase { /** * Returns the image file in the cache directory */ - fun image(context: Context, id: Int) = File( + fun image(context: Context, id: Int, index: Int) = File( File(context.externalCacheDir, DIRECTORY).apply { if (!exists()) { mkdirs() } }, - "image_$id.webp" + "image_${id}_($index).webp" ) - /** - * Saves the selected image to the cache directory and returns the uri of the saved image - */ - operator fun invoke(context: Context, uri: Uri, noteId: Int): Uri? = try { + private fun imageFileUri(context: Context, uri: Uri, noteId: Int, index: Int): Uri? = try { // copy the image to cache directory because opening the // image uri after app restart doesn't work for external storage uri on android 11 and above - val image = image(context, noteId) + val image = image(context, noteId, index) val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { ImageDecoder.decodeBitmap( ImageDecoder.createSource( @@ -59,4 +56,15 @@ object SaveSelectedImageUseCase { } catch (_: Exception) { null } + + /** + * Saves the selected image to the cache directory and returns the uri of the saved image + */ + operator fun invoke(context: Context, uris: List, noteId: Int): List { + val imageFileUris = mutableListOf() + uris.forEachIndexed { index, uri -> + imageFileUris.add(imageFileUri(context, uri, noteId, index)) + } + return imageFileUris.toList() + } } diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditScreen.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditScreen.kt index c483d9d2..39d4312a 100644 --- a/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditScreen.kt +++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditScreen.kt @@ -2,23 +2,24 @@ package com.aritra.notify.ui.screens.notes.addEditScreen import android.Manifest import android.net.Uri -import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll @@ -27,8 +28,10 @@ import androidx.compose.material.icons.outlined.Close import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold @@ -45,7 +48,9 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale @@ -63,8 +68,6 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import coil.compose.AsyncImage -import coil.compose.rememberAsyncImagePainter import com.aritra.notify.R import com.aritra.notify.components.actions.BottomSheetOptions import com.aritra.notify.components.actions.SpeechRecognizerContract @@ -75,6 +78,7 @@ import com.aritra.notify.utils.Const import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState +import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date @@ -93,13 +97,13 @@ fun AddEditScreen( val note = if (isNew) { null } else { - Note(noteId, "", "", Date(), null) + Note(noteId, "", "", Date(), emptyList()) } var title by remember { mutableStateOf("") } var description by remember { mutableStateOf("") } var dateTime by remember { mutableStateOf(Calendar.getInstance().time) } - var photoUri: Uri? by remember { mutableStateOf(null) } + var photoUri by remember { mutableStateOf(emptyList()) } var characterCount by remember { mutableIntStateOf(title.length + description.length) } val cancelDialogState = remember { mutableStateOf(false) } @@ -114,14 +118,12 @@ fun AddEditScreen( val bottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = skipPartiallyExpanded ) - val formattedDateTime = - SimpleDateFormat(Const.DATE_TIME_FORMAT, Locale.getDefault()).format(dateTime ?: 0) + val formattedDateTime = SimpleDateFormat(Const.DATE_TIME_FORMAT, Locale.getDefault()).format(dateTime ?: 0) val formattedCharacterCount = "${(title.length) + (description.length)} characters" - val launcher = - rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> - photoUri = uri - } + val launcher = rememberLauncherForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) { uris -> + photoUri = uris + } val permissionState = rememberPermissionState( permission = Manifest.permission.RECORD_AUDIO @@ -133,22 +135,19 @@ fun AddEditScreen( permissionState.launchPermissionRequest() } } - val speechRecognizerLauncher = rememberLauncherForActivityResult( - contract = SpeechRecognizerContract(), - onResult = { - it?.let { - for (st in it) { - description += " $st" - } + val speechRecognizerLauncher = rememberLauncherForActivityResult(contract = SpeechRecognizerContract(), onResult = { + it?.let { + for (st in it) { + description += " $st" } } - ) + }) // edit note if (!isNew) { title = addEditViewModel.noteModel.observeAsState().value?.title ?: "" description = addEditViewModel.noteModel.observeAsState().value?.note ?: "" - photoUri = addEditViewModel.noteModel.observeAsState().value?.image + photoUri = addEditViewModel.noteModel.observeAsState().value?.image ?: emptyList() dateTime = addEditViewModel.noteModel.observeAsState().value?.dateTime LaunchedEffect(Unit) { @@ -189,89 +188,84 @@ fun AddEditScreen( } } - Scaffold( - topBar = { - AddEditTopBar( - title = title, - description = description, - onBackPress = if (isNew) { - { cancelDialogState.value = true } - } else { - navigateBack - }, - saveNote = if (isNew) { - saveEditNote - } else { - {} - }, - updateNote = if (isNew) { - {} - } else { - saveEditNote - }, - note = note - ) - }, - bottomBar = { - if (isNew) { - Column( - Modifier - .navigationBarsPadding() - .imePadding() - ) { - BottomAppBar( - content = { - IconButton(onClick = { showSheet = true }) { - Icon( - painter = painterResource(id = R.drawable.add_box_icon), - contentDescription = stringResource(R.string.add_box) - ) - } - if (showSheet) { - ModalBottomSheet( - onDismissRequest = { showSheet = false }, - sheetState = bottomSheetState, - dragHandle = { BottomSheetDefaults.DragHandle() } - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .navigationBarsPadding() - .padding(16.dp) - ) { - BottomSheetOptions( - text = stringResource(R.string.add_image), - icon = painterResource(id = R.drawable.gallery_icon), - onClick = { - launcher.launch( - PickVisualMediaRequest( - mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly - ) - ) - showSheet = false - } - ) - BottomSheetOptions( - text = stringResource(R.string.speech_to_text), - icon = painterResource(id = R.drawable.mic_icon), - onClick = { - if (permissionState.status.isGranted) { - speechRecognizerLauncher.launch(Unit) - } else { - permissionState.launchPermissionRequest() - } - showSheet = false - } + Scaffold(topBar = { + AddEditTopBar( + title = title, + description = description, + onBackPress = if (isNew) { + { cancelDialogState.value = true } + } else { + navigateBack + }, + saveNote = if (isNew) { + saveEditNote + } else { + {} + }, + updateNote = if (isNew) { + {} + } else { + saveEditNote + }, + note = note + ) + }, bottomBar = { + if (isNew) { + Column( + Modifier + .navigationBarsPadding() + .imePadding() + ) { + BottomAppBar(content = { + IconButton(onClick = { showSheet = true }) { + Icon( + painter = painterResource(id = R.drawable.add_box_icon), + contentDescription = stringResource(R.string.add_box) + ) + } + if (showSheet) { + ModalBottomSheet( + onDismissRequest = { showSheet = false }, + sheetState = bottomSheetState, + dragHandle = { BottomSheetDefaults.DragHandle() } + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(16.dp) + ) { + BottomSheetOptions( + text = stringResource(R.string.add_image), + icon = painterResource(id = R.drawable.gallery_icon), + onClick = { + launcher.launch( + PickVisualMediaRequest( + mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly + ) ) + showSheet = false } - } + ) + BottomSheetOptions( + text = stringResource(R.string.speech_to_text), + icon = painterResource(id = R.drawable.mic_icon), + onClick = { + if (permissionState.status.isGranted) { + speechRecognizerLauncher.launch(Unit) + } else { + permissionState.launchPermissionRequest() + } + showSheet = false + } + ) } } - ) - } + } + }) } } - ) { + }) { Box( modifier = Modifier.padding(it) ) { @@ -281,56 +275,71 @@ fun AddEditScreen( .verticalScroll(rememberScrollState()) ) { if (isNew) { - photoUri?.let { - Log.d("URI", "Photo uri $photoUri") - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - IconButton( - onClick = { - photoUri = null + if (photoUri.isNotEmpty()) { + LazyRow { + items(photoUri.size) { + Box( + Modifier + .height(180.dp) + .width(180.dp) + .padding(4.dp) + .clip(RoundedCornerShape(8.dp)) + ) { + ZoomableAsyncImage( + modifier = Modifier.fillMaxSize(), + model = photoUri[it], + contentDescription = stringResource(R.string.image), + contentScale = ContentScale.Crop + ) + FilledTonalIconButton( + modifier = Modifier.align(Alignment.TopEnd), + onClick = { + photoUri = photoUri.filterIndexed { index, _ -> index != it } + }, + colors = IconButtonDefaults.filledTonalIconButtonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer.copy( + alpha = 0.6f + ) + ) + ) { + Icon( + imageVector = Icons.Outlined.Close, + contentDescription = stringResource(R.string.clear_image) + ) + } } - ) { - Icon( - imageVector = Icons.Outlined.Close, - contentDescription = stringResource(R.string.clear_image) - ) } } - - AsyncImage( - model = photoUri, - contentDescription = stringResource(R.string.image), - modifier = Modifier.fillMaxWidth(), - contentScale = ContentScale.Crop - ) } } else { - photoUri?.let { - Image( - painter = rememberAsyncImagePainter(photoUri), - contentDescription = stringResource(R.string.image), - modifier = Modifier - .fillMaxWidth() - .size(500.dp), - contentScale = ContentScale.Fit - ) + Row( + modifier = Modifier + .horizontalScroll(rememberScrollState()) + ) { + photoUri.forEach { uri -> + ZoomableAsyncImage( + modifier = Modifier + .height(180.dp) + .width(180.dp) + .padding(4.dp) + .clip(RoundedCornerShape(8.dp)), + model = uri ?: "", + contentDescription = stringResource(R.string.image), + contentScale = ContentScale.Crop + ) + } } } TextField( - modifier = Modifier.fillMaxWidth(), - value = title, - onValueChange = { newTitle -> + modifier = Modifier.fillMaxWidth(), value = title, onValueChange = { newTitle -> if (isNew) { title = newTitle characterCount = title.length + description.length } else { addEditViewModel.updateTitle(newTitle) } - }, - placeholder = { + }, placeholder = { Text( stringResource(R.string.title), fontSize = 24.sp, diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt index 4a8da60c..ac09124d 100644 --- a/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt +++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt @@ -24,21 +24,23 @@ class AddEditViewModel @Inject constructor( private val noteRepository: NoteRepository, ) : AndroidViewModel(application) { - private val _noteModel = MutableLiveData(Note(0, "", "", Date(), null)) + private val _noteModel = MutableLiveData(Note(0, "", "", Date(), emptyList())) val noteModel: LiveData get() = _noteModel fun insertNote(note: Note, onSuccess: () -> Unit) { viewModelScope.launch(Dispatchers.IO) { val id: Int = noteRepository.insertNoteToRoom(note).toInt() - if (note.image != null) { + val imageUris = note.image.filterNotNull() + + if (imageUris.isNotEmpty()) { // update the note with the new image uri noteRepository.updateNoteInRoom( note.copy( id = id, image = SaveSelectedImageUseCase( context = getApplication(), - uri = note.image!!, + uris = imageUris, noteId = id ) ) @@ -84,22 +86,24 @@ class AddEditViewModel @Inject constructor( // if (oldNote.title == newNote.title && oldNote.note == newNote.note && oldNote.image == newNote.image) return@launch // if the image has been modified, delete the old image if (oldNote.image != newNote.image) { - oldNote.image?.toFile(getApplication())?.delete() + oldNote.image.forEach { imageUri -> + imageUri?.toFile(getApplication())?.delete() + } } noteRepository.updateNoteInRoom( newNote.copy( // if the image has not been modified, use the old image uri image = if (oldNote.image == newNote.image) { oldNote.image - } else if (newNote.image != null) { + } else if (newNote.image.filterNotNull().isNotEmpty()) { // if the image has been modified, save the new image uri SaveSelectedImageUseCase( getApplication(), - newNote.image!!, + newNote.image.filterNotNull(), newNote.id ) } else { - null + emptyList() } ) ) @@ -117,7 +121,7 @@ class AddEditViewModel @Inject constructor( _noteModel.postValue(noteModel.value?.copy(note = description)) } - fun updateImage(image: Uri?) { - _noteModel.postValue(noteModel.value?.copy(image = image)) + fun updateImage(imageList: List) { + _noteModel.postValue(noteModel.value?.copy(image = imageList)) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9d363c6..7ffa6790 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ core-ktx = "1.10.1" coreSplashscreen = "1.0.1" datastorePreferences = "1.0.0" espresso-core = "3.5.1" +google-gson = "2.9.0" gradle = "3.4.0" hiltAndroid = "2.44" hiltNavigationCompose = "1.0.0" @@ -29,6 +30,8 @@ android-application = "8.0.2" kotlin-android = "1.7.20" dagger-hilt-android = "2.44" devtools-ksp = "1.8.10-1.0.9" +zoomable = "0.6.2" +zoomableImageCoil = "0.6.2" ktlint = "11.6.0" [libraries] @@ -68,9 +71,12 @@ app-update = { module = "com.google.android.play:app-update", version.ref = "app app-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "appUpdate" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" } +gson = { module = "com.google.code.gson:gson", version.ref = "google-gson" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" } hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltAndroid" } junit = { module = "junit:junit", version.ref = "junit" } +zoomable = { module = "me.saket.telephoto:zoomable", version.ref = "zoomable" } +zoomable-image-coil = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "zoomableImageCoil" } [plugins] android-application = { id = "com.android.application", version.ref = "android-application" }