Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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.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
Expand Down Expand Up @@ -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()
Expand Down
33 changes: 24 additions & 9 deletions app/src/main/java/com/aritra/notify/components/note/NoteCard.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <reified T> Gson.fromJson(json: String): T =
fromJson(json, object : TypeToken<T>() {}.type)

@TypeConverter
fun fromListToString(value: List<Uri?>): String {
return Gson().toJson(value.map { it?.toString() ?: "" })
}

@TypeConverter
fun fromStringToList(value: String): List<Uri?> {
return try {
val stringList = Gson().fromJson<List<String>>(value) // using extension function
stringList.map { Uri.parse(it) }
} catch (e: Exception) {
listOf()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/aritra/notify/domain/models/Note.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uri?>,
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<Uri>, noteId: Int): List<Uri?> {
val imageFileUris = mutableListOf<Uri?>()
uris.forEachIndexed { index, uri ->
imageFileUris.add(imageFileUri(context, uri, noteId, index))
}
return imageFileUris.toList()
}
}
Loading