diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 52c299bf..9de38fa0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,7 +4,6 @@ plugins {
id ("kotlin-parcelize")
id ("kotlin-kapt")
id ("com.google.dagger.hilt.android")
- id ("com.google.gms.google-services")
id ("com.google.devtools.ksp")
}
@@ -16,8 +15,8 @@ android {
applicationId = "com.aritra.notify"
minSdk = 24
targetSdk = 34
- versionCode = 4
- versionName = "1.3.0-beta01"
+ versionCode = 5
+ versionName = "1.3.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -57,9 +56,9 @@ android {
dependencies {
- val lifecycleVersion = "2.6.1"
+ val lifecycleVersion = "2.6.2"
val roomVersion = "2.5.2"
- val navVersion = "2.7.0"
+ val navVersion = "2.7.2"
implementation ("androidx.core:core-ktx:1.10.1")
@@ -91,7 +90,7 @@ dependencies {
// Material 3
implementation ("androidx.compose.material3:material3:1.1.1")
implementation ("androidx.compose.material3:material3-window-size-class:1.1.1")
- implementation ("androidx.compose.material:material-icons-extended:1.5.0")
+ implementation ("androidx.compose.material:material-icons-extended:1.5.1")
// Room
implementation ("androidx.room:room-runtime:$roomVersion")
@@ -115,15 +114,9 @@ dependencies {
// DataStore
implementation ("androidx.datastore:datastore-preferences:1.0.0")
- //Swipe
- implementation ("me.saket.swipe:swipe:1.2.0")
-
// Splash API
implementation ("androidx.core:core-splashscreen:1.0.1")
- // Firebase
- implementation("com.google.firebase:firebase-messaging-ktx:23.2.1")
-
//Coil
implementation("io.coil-kt:coil-compose:2.4.0")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eb2da3b8..0aa2c15c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,7 +4,6 @@
-
-
-
-
-
-
diff --git a/app/src/main/java/com/aritra/notify/NotifyMessagingService.kt b/app/src/main/java/com/aritra/notify/NotifyMessagingService.kt
deleted file mode 100644
index 34d158e5..00000000
--- a/app/src/main/java/com/aritra/notify/NotifyMessagingService.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.aritra.notify
-
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.NotificationManager.IMPORTANCE_DEFAULT
-import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_IMMUTABLE
-import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
-import android.os.Build
-import android.util.Log
-import androidx.core.app.NotificationCompat
-import com.aritra.notify.ui.screens.MainActivity
-import com.google.firebase.messaging.FirebaseMessagingService
-import com.google.firebase.messaging.RemoteMessage
-import kotlin.random.Random
-
-class NotifyMessagingService : FirebaseMessagingService() {
-
- private val random = Random
- override fun onMessageReceived(remoteMessage: RemoteMessage) {
- remoteMessage.notification?.let { message ->
- sendNotification(message)
- }
- }
-
- private fun sendNotification(message: RemoteMessage.Notification) {
- val intent = Intent(this, MainActivity::class.java).apply {
- addFlags(FLAG_ACTIVITY_CLEAR_TOP)
- }
-
- val pendingIntent = PendingIntent.getActivity(
- this, 0, intent, FLAG_IMMUTABLE
- )
-
- val channelId = this.getString(R.string.default_notification_channel_id)
-
- val notificationBuilder = NotificationCompat.Builder(this, channelId)
- .setContentTitle(message.title)
- .setContentText(message.body)
- .setSmallIcon(R.drawable.notify_logo)
- .setAutoCancel(true)
- .setContentIntent(pendingIntent)
-
- val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val channel = NotificationChannel(channelId, CHANNEL_NAME, IMPORTANCE_HIGH)
- manager.createNotificationChannel(channel)
- }
-
- manager.notify(random.nextInt(), notificationBuilder.build())
- }
-
- override fun onNewToken(token: String) {
- Log.d("FCM","New token: $token")
- }
-
- companion object {
- const val CHANNEL_NAME = "FCM notification channel"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/components/actions/SwipeDelete.kt b/app/src/main/java/com/aritra/notify/components/actions/SwipeDelete.kt
deleted file mode 100644
index c2d54643..00000000
--- a/app/src/main/java/com/aritra/notify/components/actions/SwipeDelete.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.aritra.notify.components.actions
-
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Icon
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.aritra.notify.R
-import com.aritra.notify.components.dialog.TextDialog
-import com.aritra.notify.components.note.NotesCard
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.ui.screens.notes.homeScreen.NoteScreenViewModel
-import me.saket.swipe.SwipeAction
-import me.saket.swipe.SwipeableActionsBox
-
-@Composable
-fun SwipeDelete(
- notesModel: Note,
- viewModel: NoteScreenViewModel,
- navigateToUpdateNoteScreen: (noteId: Int) -> Unit
-) {
-
- val deleteDialogVisible = remember { mutableStateOf(false) }
-
- val delete = SwipeAction(
- onSwipe = {
- deleteDialogVisible.value = true
- },
- icon = {
- Icon(
- modifier = Modifier.padding(12.dp),
- painter = painterResource(R.drawable.ic_delete),
- contentDescription = null,
- tint = Color.White
- )
- },
- background = Color.Red,
- )
-
- SwipeableActionsBox(
- modifier = Modifier.padding(10.dp),
- swipeThreshold = 100.dp,
- endActions = listOf(delete)
- ) {
- NotesCard(notesModel, navigateToUpdateNoteScreen)
- }
- if (deleteDialogVisible.value) {
- TextDialog(
- title = stringResource(R.string.warning),
- description = stringResource(R.string.are_you_sure_want_to_delete_these_items_it_cannot_be_recovered),
- isOpened = deleteDialogVisible.value,
- onDismissCallback = { deleteDialogVisible.value = false },
- onConfirmCallback = {
- viewModel.deleteNote(notesModel)
- deleteDialogVisible.value = false
- }
- )
- }
-}
diff --git a/app/src/main/java/com/aritra/notify/biometric/AppBioMetricManager.kt b/app/src/main/java/com/aritra/notify/components/biometric/AppBioMetricManager.kt
similarity index 97%
rename from app/src/main/java/com/aritra/notify/biometric/AppBioMetricManager.kt
rename to app/src/main/java/com/aritra/notify/components/biometric/AppBioMetricManager.kt
index c6c8725e..13be8f1f 100644
--- a/app/src/main/java/com/aritra/notify/biometric/AppBioMetricManager.kt
+++ b/app/src/main/java/com/aritra/notify/components/biometric/AppBioMetricManager.kt
@@ -1,4 +1,4 @@
-package com.aritra.notify.biometric
+package com.aritra.notify.components.biometric
import android.content.Context
import androidx.biometric.BiometricManager
diff --git a/app/src/main/java/com/aritra/notify/biometric/BiometricAuthListener.kt b/app/src/main/java/com/aritra/notify/components/biometric/BiometricAuthListener.kt
similarity index 72%
rename from app/src/main/java/com/aritra/notify/biometric/BiometricAuthListener.kt
rename to app/src/main/java/com/aritra/notify/components/biometric/BiometricAuthListener.kt
index 951c2ab2..7d1041b7 100644
--- a/app/src/main/java/com/aritra/notify/biometric/BiometricAuthListener.kt
+++ b/app/src/main/java/com/aritra/notify/components/biometric/BiometricAuthListener.kt
@@ -1,4 +1,4 @@
-package com.aritra.notify.biometric
+package com.aritra.notify.components.biometric
interface BiometricAuthListener {
fun onBiometricAuthSuccess()
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 ca9d4015..ae71cddb 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
@@ -9,29 +9,17 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.MoreVert
-import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
@@ -40,24 +28,25 @@ import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.aritra.notify.R
-import com.aritra.notify.components.dialog.TextDialog
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.ui.screens.notes.homeScreen.NoteScreenViewModel
+import com.aritra.notify.domain.models.Note
import com.aritra.notify.utils.Const
import java.text.SimpleDateFormat
+import java.util.Date
import java.util.Locale
@Composable
fun GridNoteCard(
notesModel: Note,
- viewModel: NoteScreenViewModel,
navigateToUpdateNoteScreen: (noteId: Int) -> Unit,
- isGridView: Boolean
) {
- var expanded by remember { mutableStateOf(false) }
- val deleteDialogVisible = remember { mutableStateOf(false) }
- val painter = rememberSaveable { mutableStateOf(notesModel.imagePath) }
+ val painter = rememberSaveable { mutableStateOf(notesModel.image) }
val context = LocalContext.current
+ val date = remember {
+ SimpleDateFormat(
+ Const.DATE_TIME_FORMAT,
+ Locale.getDefault()
+ ).format(notesModel.dateTime ?: Date())
+ }
OutlinedCard(
border = CardDefaults.outlinedCardBorder().copy(0.dp),
@@ -65,10 +54,12 @@ fun GridNoteCard(
.padding(10.dp)
.fillMaxWidth()
.clickable { navigateToUpdateNoteScreen(notesModel.id) },
- shape = RoundedCornerShape(8.dp)
+ shape = RoundedCornerShape(15.dp)
) {
Column(
- modifier = Modifier.padding(12.dp).fillMaxWidth()
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth()
) {
AsyncImage(
model = ImageRequest.Builder(context)
@@ -95,45 +86,6 @@ fun GridNoteCard(
overflow = TextOverflow.Ellipsis
)
}
-
- if (isGridView) {
- IconButton(
- onClick = { expanded = true }
- ) {
- Icon(Icons.Default.MoreVert, "Options")
- }
- DropdownMenu(
- expanded = expanded,
- onDismissRequest = { expanded = false },
- ) {
- DropdownMenuItem(
- text = { Text("Delete") },
- onClick = {
- deleteDialogVisible.value = true
- expanded = false
- },
- leadingIcon = {
- Icon(
- modifier = Modifier.padding(12.dp),
- imageVector = Icons.Outlined.Delete,
- contentDescription = null
- )
- }
- )
- }
- if (deleteDialogVisible.value) {
- TextDialog(
- title = stringResource(R.string.warning),
- description = stringResource(R.string.are_you_sure_want_to_delete_these_items_it_cannot_be_recovered),
- isOpened = deleteDialogVisible.value,
- onDismissCallback = { deleteDialogVisible.value = false },
- onConfirmCallback = {
- viewModel.deleteNote(notesModel)
- deleteDialogVisible.value = false
- }
- )
- }
- }
}
Spacer(modifier = Modifier.height(10.dp))
Text(
@@ -144,13 +96,8 @@ fun GridNoteCard(
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(10.dp))
- val formattedDateTime =
- SimpleDateFormat(
- Const.DATE_TIME_FORMAT,
- Locale.getDefault()
- ).format(notesModel.dateTime)
Text(
- text = formattedDateTime,
+ text = date,
fontSize = 14.sp,
fontFamily = FontFamily(Font(R.font.poppins_medium)),
color = Color.Gray
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 ef885a78..b7c6cf17 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
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
@@ -18,7 +17,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
@@ -28,7 +26,7 @@ import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.aritra.notify.R
-import com.aritra.notify.data.models.Note
+import com.aritra.notify.domain.models.Note
import com.aritra.notify.utils.Const
import java.text.SimpleDateFormat
import java.util.Locale
@@ -38,21 +36,21 @@ fun NotesCard(
noteModel: Note,
navigateToUpdateNoteScreen: (noteId: Int) -> Unit
) {
- val painter = rememberSaveable { mutableStateOf(noteModel.imagePath) }
+ val painter = rememberSaveable { mutableStateOf(noteModel.image) }
val context = LocalContext.current
OutlinedCard(
border = CardDefaults.outlinedCardBorder().copy(0.dp),
modifier = Modifier
- .padding(2.dp)
+ .padding(10.dp)
.fillMaxHeight()
.clickable { navigateToUpdateNoteScreen(noteModel.id) },
- shape = RoundedCornerShape(8.dp),
+ shape = RoundedCornerShape(15.dp),
) {
Column(
modifier = Modifier
.fillMaxWidth()
- .padding(12.dp)
+ .padding(16.dp)
) {
AsyncImage(
model = ImageRequest.Builder(context)
diff --git a/app/src/main/java/com/aritra/notify/components/topbar/AddNoteTopBar.kt b/app/src/main/java/com/aritra/notify/components/topbar/AddNoteTopBar.kt
index 2485f6c5..d18bdad5 100644
--- a/app/src/main/java/com/aritra/notify/components/topbar/AddNoteTopBar.kt
+++ b/app/src/main/java/com/aritra/notify/components/topbar/AddNoteTopBar.kt
@@ -1,8 +1,7 @@
package com.aritra.notify.components.topbar
-import android.graphics.Bitmap
-import android.widget.Toast
+import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -34,24 +33,19 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import com.aritra.notify.R
import com.aritra.notify.components.actions.ShareOption
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.ui.screens.notes.addNoteScreen.AddNoteViewModel
import com.aritra.notify.utils.shareAsImage
import com.aritra.notify.utils.shareAsPdf
import com.aritra.notify.utils.shareNoteAsText
-import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddNoteTopBar(
- viewModel: AddNoteViewModel,
- onBackPress: () -> Unit,
- onSave: () -> Unit,
title: String,
description: String,
- dateTime: Date,
- imagePath: Bitmap?,
+ modifier: Modifier = Modifier,
+ onBackPress: () -> Unit,
+ saveNote: () -> Unit,
) {
var showSheet by remember { mutableStateOf(false) }
val context = LocalContext.current
@@ -62,9 +56,14 @@ fun AddNoteTopBar(
val view = LocalView.current
val bitmapSize = view.width to view.height
-
+ BackHandler {
+ if (title.isNotEmpty() && description.isNotEmpty()) {
+ saveNote()
+ } else onBackPress()
+ }
CenterAlignedTopAppBar(
+ modifier = modifier,
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
),
@@ -130,14 +129,7 @@ fun AddNoteTopBar(
}
}
- IconButton(onClick = {
- val noteDB =
- Note(id = 0, title = title, note = description, dateTime = dateTime, imagePath = imagePath)
- viewModel.insertNote(noteDB)
- onSave()
- Toast.makeText(context, "Successfully Saved!", Toast.LENGTH_SHORT).show()
-
- }) {
+ IconButton(onClick = saveNote) {
Icon(
painterResource(R.drawable.save),
contentDescription = stringResource(R.string.save)
diff --git a/app/src/main/java/com/aritra/notify/components/topbar/EditNoteTopBar.kt b/app/src/main/java/com/aritra/notify/components/topbar/EditNoteTopBar.kt
index e1124d52..c381facd 100644
--- a/app/src/main/java/com/aritra/notify/components/topbar/EditNoteTopBar.kt
+++ b/app/src/main/java/com/aritra/notify/components/topbar/EditNoteTopBar.kt
@@ -1,7 +1,6 @@
package com.aritra.notify.components.topbar
-import android.graphics.Bitmap
-import android.widget.Toast
+import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -34,24 +33,20 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.aritra.notify.R
import com.aritra.notify.components.actions.ShareOption
import com.aritra.notify.components.dialog.TextDialog
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.ui.screens.notes.editNoteScreen.EditScreenViewModel
+import com.aritra.notify.domain.models.Note
import com.aritra.notify.ui.screens.notes.homeScreen.NoteScreenViewModel
import com.aritra.notify.utils.shareAsImage
import com.aritra.notify.utils.shareAsPdf
import com.aritra.notify.utils.shareNoteAsText
-import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditNoteTopBar(
note: Note,
- viewModel: EditScreenViewModel,
- noteId: Int,
navigateBack: () -> Unit,
title: String,
description: String,
- imagePath: Bitmap?
+ updateNote: () -> Unit,
) {
val noteScreenViewModel = hiltViewModel()
var showSheet by remember { mutableStateOf(false) }
@@ -62,9 +57,10 @@ fun EditNoteTopBar(
)
val view = LocalView.current
val bitmapSize = view.width to view.height
- val currentDateTime = Date()
val deleteDialogVisible = remember { mutableStateOf(false) }
+ BackHandler(onBack = updateNote)
+
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
@@ -76,7 +72,7 @@ fun EditNoteTopBar(
)
},
navigationIcon = {
- IconButton(onClick = { navigateBack() }) {
+ IconButton(onClick = updateNote) {
Icon(
painterResource(R.drawable.back),
contentDescription = stringResource(R.string.back)
@@ -149,18 +145,12 @@ fun EditNoteTopBar(
}
}
}
- IconButton(onClick = {
- val updateNote = Note(noteId, title, description, currentDateTime, imagePath)
- viewModel.updateNotes(updateNote)
- navigateBack()
- Toast.makeText(context, "Successfully Updated!", Toast.LENGTH_SHORT).show()
- }) {
+ IconButton(onClick = updateNote) {
Icon(
painterResource(R.drawable.save),
contentDescription = stringResource(R.string.save)
)
}
-
}
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/data/converters/DateTypeConverter.kt b/app/src/main/java/com/aritra/notify/data/converters/DateTypeConverter.kt
new file mode 100644
index 00000000..bb4b5cb1
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/data/converters/DateTypeConverter.kt
@@ -0,0 +1,29 @@
+package com.aritra.notify.data.converters
+
+import android.annotation.SuppressLint
+import androidx.room.TypeConverter
+import com.aritra.notify.utils.Const
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+object DateTypeConverter {
+ @SuppressLint("ConstantLocale")
+ private val displayDateFormat = SimpleDateFormat(Const.DATE_TIME_FORMAT, Locale.getDefault())
+
+ @TypeConverter
+ @JvmStatic
+ fun toDate(value: String?): Date? {
+ return if (value.isNullOrEmpty()) {
+ null
+ } else {
+ displayDateFormat.parse(value)
+ }
+ }
+
+ @TypeConverter
+ @JvmStatic
+ fun toString(date: Date?): String? {
+ return date?.let { displayDateFormat.format(date) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/data/converters/UriConverter.kt b/app/src/main/java/com/aritra/notify/data/converters/UriConverter.kt
new file mode 100644
index 00000000..76d10ad7
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/data/converters/UriConverter.kt
@@ -0,0 +1,20 @@
+package com.aritra.notify.data.converters
+
+import android.net.Uri
+import androidx.room.TypeConverter
+
+object UriConverter {
+ @TypeConverter
+ fun fromUri(uri: Uri?): String? {
+ return uri?.toString()
+ }
+
+ @TypeConverter
+ fun toUri(string: String?): Uri? {
+ if (string == null) {
+ return null
+ }
+
+ return Uri.parse(string)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/data/dao/NoteDao.kt b/app/src/main/java/com/aritra/notify/data/dao/NoteDao.kt
index 31b46b8e..41970e4c 100644
--- a/app/src/main/java/com/aritra/notify/data/dao/NoteDao.kt
+++ b/app/src/main/java/com/aritra/notify/data/dao/NoteDao.kt
@@ -6,7 +6,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
-import com.aritra.notify.data.models.Note
+import com.aritra.notify.domain.models.Note
import kotlinx.coroutines.flow.Flow
@Dao
@@ -20,11 +20,14 @@ interface NoteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
- suspend fun insertNote(noteModel: Note)
+ suspend fun insertNote(noteModel: Note): Long
@Update
suspend fun updateNote(noteModel: Note)
@Delete
suspend fun deleteNote(noteModel: Note)
+
+ @Query("DELETE FROM note")
+ suspend fun clear()
}
\ No newline at end of file
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 17d8fc01..b67930e4 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
@@ -4,14 +4,10 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
-import androidx.room.TypeConverter
-import androidx.room.TypeConverters
import com.aritra.notify.data.dao.NoteDao
-import com.aritra.notify.data.models.BitmapConverters
-import com.aritra.notify.data.models.Note
+import com.aritra.notify.domain.models.Note
@Database(entities = [Note::class], version = 2)
-@TypeConverters(BitmapConverters::class)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
@@ -28,7 +24,6 @@ abstract class NoteDatabase : RoomDatabase() {
"Note_database"
)
.fallbackToDestructiveMigration()
- .allowMainThreadQueries()
.build()
INSTANCE = instance
}
diff --git a/app/src/main/java/com/aritra/notify/data/models/Note.kt b/app/src/main/java/com/aritra/notify/data/models/Note.kt
deleted file mode 100644
index 2328f8a8..00000000
--- a/app/src/main/java/com/aritra/notify/data/models/Note.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.aritra.notify.data.models
-
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import android.os.Parcelable
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-import androidx.room.TypeConverter
-import androidx.room.TypeConverters
-import com.aritra.notify.utils.DateTypeConverter
-import kotlinx.parcelize.Parcelize
-import java.io.ByteArrayOutputStream
-import java.util.Date
-
-@Parcelize
-@Entity(tableName = "note")
-@TypeConverters(DateTypeConverter::class)
-data class Note(
- @PrimaryKey(autoGenerate = true)
- var id: Int = 0,
- var title: String,
- var note: String,
- var dateTime: Date?,
- @TypeConverters(BitmapConverters::class)
- var imagePath: Bitmap?
-) : Parcelable
-class BitmapConverters {
- @TypeConverter
- fun fromBitmap(bitmap: Bitmap?): ByteArray? {
- val outputStream = ByteArrayOutputStream()
- if (bitmap != null) {
- bitmap.compress(Bitmap.CompressFormat.PNG, 10, outputStream)
- return outputStream.toByteArray()
- }
- return null
- }
-
- @TypeConverter
- fun toBitmap(byteArray: ByteArray?): Bitmap? {
- return if (byteArray != null)
- BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
- else null
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/data/repository/BackupRepository.kt b/app/src/main/java/com/aritra/notify/data/repository/BackupRepository.kt
deleted file mode 100644
index 55b8a39e..00000000
--- a/app/src/main/java/com/aritra/notify/data/repository/BackupRepository.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.aritra.notify.data.repository
-
-import android.content.Context
-import android.net.Uri
-import com.aritra.notify.data.db.NoteDatabase
-import com.aritra.notify.utils.Const
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
-
-class BackupRepository(
- private val provider: NoteDatabase,
- private val context: Context,
- private val mutex: Mutex,
- private val scope: CoroutineScope,
- private val dispatcher: CoroutineDispatcher,
-) {
- suspend fun export(uri: Uri) {
- withContext(dispatcher + scope.coroutineContext) {
- mutex.withLock {
- provider.close()
-
- context.contentResolver.openOutputStream(uri)?.use { stream ->
- context.getDatabasePath(Const.DB_NAME).inputStream().copyTo(stream)
- }
- }
- }
- }
-
- suspend fun import(uri: Uri) {
- withContext(dispatcher + scope.coroutineContext) {
- mutex.withLock {
- provider.close()
-
- context.contentResolver.openInputStream(uri)?.use { stream ->
- val dbFile = context.getDatabasePath(Const.DB_NAME)
- dbFile?.delete()
- stream.copyTo(dbFile.outputStream())
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/di/AppModule.kt b/app/src/main/java/com/aritra/notify/di/AppModule.kt
index a5a02930..179ebf2c 100644
--- a/app/src/main/java/com/aritra/notify/di/AppModule.kt
+++ b/app/src/main/java/com/aritra/notify/di/AppModule.kt
@@ -2,7 +2,7 @@ package com.aritra.notify.di
import android.app.Application
import android.content.Context
-import com.aritra.notify.data.repository.NoteRepository
+import com.aritra.notify.domain.repository.NoteRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/app/src/main/java/com/aritra/notify/di/BioMetricUtil.kt b/app/src/main/java/com/aritra/notify/di/BioMetricUtil.kt
index a89629b7..601aa9f4 100644
--- a/app/src/main/java/com/aritra/notify/di/BioMetricUtil.kt
+++ b/app/src/main/java/com/aritra/notify/di/BioMetricUtil.kt
@@ -1,7 +1,7 @@
package com.aritra.notify.di
import android.content.Context
-import com.aritra.notify.biometric.AppBioMetricManager
+import com.aritra.notify.components.biometric.AppBioMetricManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/app/src/main/java/com/aritra/notify/di/ViewModelModule.kt b/app/src/main/java/com/aritra/notify/di/ViewModelModule.kt
index 1995d01c..2611e3db 100644
--- a/app/src/main/java/com/aritra/notify/di/ViewModelModule.kt
+++ b/app/src/main/java/com/aritra/notify/di/ViewModelModule.kt
@@ -1,6 +1,6 @@
package com.aritra.notify.di
-import com.aritra.notify.biometric.AppBioMetricManager
+import com.aritra.notify.components.biometric.AppBioMetricManager
import com.aritra.notify.viewmodel.MainViewModel
import dagger.Module
import dagger.Provides
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
new file mode 100644
index 00000000..1ca0cc72
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/domain/models/Note.kt
@@ -0,0 +1,23 @@
+package com.aritra.notify.domain.models
+
+import android.net.Uri
+import android.os.Parcelable
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.TypeConverters
+import com.aritra.notify.data.converters.DateTypeConverter
+import com.aritra.notify.data.converters.UriConverter
+import kotlinx.parcelize.Parcelize
+import java.util.Date
+
+@Parcelize
+@Entity(tableName = "note")
+@TypeConverters(DateTypeConverter::class, UriConverter::class)
+data class Note(
+ @PrimaryKey(autoGenerate = true)
+ var id: Int = 0,
+ var title: String,
+ var note: String,
+ var dateTime: Date?,
+ var image: Uri?
+) : Parcelable
\ No newline at end of file
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
new file mode 100644
index 00000000..56dea6fc
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/domain/repository/BackupRepository.kt
@@ -0,0 +1,177 @@
+package com.aritra.notify.domain.repository
+
+import android.content.Context
+import android.net.Uri
+import android.util.Log
+import androidx.core.content.FileProvider
+import com.aritra.notify.data.converters.DateTypeConverter
+import com.aritra.notify.data.db.NoteDatabase
+import com.aritra.notify.domain.models.Note
+import com.aritra.notify.domain.usecase.SaveSelectedImageUseCase
+import com.aritra.notify.utils.CsvIo
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileReader
+import java.io.FileWriter
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
+
+class BackupRepository(
+ private val provider: NoteDatabase,
+ private val context: Context,
+ private val mutex: Mutex,
+ private val scope: CoroutineScope,
+ private val dispatcher: CoroutineDispatcher,
+) {
+ suspend fun export(uri: Uri) {
+ withContext(dispatcher + scope.coroutineContext) {
+ mutex.withLock {
+ try {
+ // create a backup folder in the cache dir
+ val backupDir = File(context.externalCacheDir, "backup")
+ // if the backup directory already exists, delete it
+ if (backupDir.exists()) backupDir.deleteRecursively()
+ // create a new backup directory
+ backupDir.mkdir()
+ // creates a csv writer for writing the notes to a csv file in the backup directory
+ val csvWriter = CsvIo.Writer(FileWriter(File(backupDir, "notes.csv")))
+ // write the headers to the csv file
+ csvWriter.writeNext(arrayOf("Id", "Title", "Content", "Date", "Image"))
+ // 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 imageFile = File(backupDir, imageName)
+ context.contentResolver.openInputStream(image)?.use { inputStream ->
+ imageFile.outputStream().use { outputStream ->
+ inputStream.copyTo(outputStream)
+ }
+ }
+ imageName
+ }
+ csvWriter.writeNext(
+ arrayOf(
+ note.id.toString(),
+ note.title,
+ note.note,
+ DateTypeConverter.toString(note.dateTime).orEmpty(),
+ image.orEmpty()
+ )
+ )
+ }
+ // close the csv writer
+ csvWriter.close()
+ // create a zip file containing the csv and the images
+ ZipOutputStream(
+ BufferedOutputStream(
+ context.contentResolver.openOutputStream(
+ uri
+ )
+ )
+ ).use { zip ->
+ backupDir.listFiles()?.forEach { file ->
+ zip.putNextEntry(ZipEntry(file.name))
+ file.inputStream().copyTo(zip)
+ zip.closeEntry()
+ }
+ }
+ // delete the backup directory
+ backupDir.deleteRecursively()
+ } catch (e: Exception) {
+ Log.e(BackupRepository::class.simpleName, "Export", e)
+ }
+ }
+ }
+ }
+
+ suspend fun import(uri: Uri) {
+ withContext(dispatcher + scope.coroutineContext) {
+ mutex.withLock {
+ try {
+ val restoreDir = File(context.externalCacheDir, "restore")
+ // delete the restore directory if it already exists
+ if (restoreDir.exists()) restoreDir.deleteRecursively()
+ // create a new restore directory
+ restoreDir.mkdir()
+ // extract the zip file to the restore directory
+ context.contentResolver.openInputStream(uri)?.use { stream ->
+ ZipInputStream(BufferedInputStream(stream)).use { zip ->
+ var entry = zip.nextEntry
+ while (entry != null) {
+ val file = File(restoreDir, entry.name)
+ file.outputStream().use { output ->
+ zip.copyTo(output)
+ }
+ entry = zip.nextEntry
+ }
+ }
+ }
+ // open the notes csv file
+ val csvReader = CsvIo.Reader(FileReader(File(restoreDir, "notes.csv")))
+ // read all the lines and discard the headers
+ val rows = csvReader.rows().drop(1)
+ Log.d(
+ BackupRepository::class.simpleName!!,
+ "import: ${rows.map { it.toList() }}}"
+ )
+ // close the csv reader
+ csvReader.close()
+ // clear the database to remove all the existing notes
+ provider.noteDao().clear()
+ // delete all images from the cache directory
+ File(context.externalCacheDir, SaveSelectedImageUseCase.DIRECTORY)
+ .deleteRecursively()
+ // import the notes from the csv file into the database
+ rows.forEach { columns ->
+ val id = columns[0].toInt()
+ 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)
+ }
+ }
+ // 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
+ )
+ Log.d(BackupRepository::class.simpleName, "import: $note")
+ provider.noteDao().insertNote(
+ note
+ )
+ }
+ // delete the restore directory
+ restoreDir.deleteRecursively()
+ } catch (e: Exception) {
+ Log.e(BackupRepository::class.simpleName, "Import", e)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/data/repository/NoteRepository.kt b/app/src/main/java/com/aritra/notify/domain/repository/NoteRepository.kt
similarity index 81%
rename from app/src/main/java/com/aritra/notify/data/repository/NoteRepository.kt
rename to app/src/main/java/com/aritra/notify/domain/repository/NoteRepository.kt
index b886318e..5c35c152 100644
--- a/app/src/main/java/com/aritra/notify/data/repository/NoteRepository.kt
+++ b/app/src/main/java/com/aritra/notify/domain/repository/NoteRepository.kt
@@ -1,9 +1,9 @@
-package com.aritra.notify.data.repository
+package com.aritra.notify.domain.repository
import android.app.Application
import com.aritra.notify.data.dao.NoteDao
import com.aritra.notify.data.db.NoteDatabase
-import com.aritra.notify.data.models.Note
+import com.aritra.notify.domain.models.Note
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@@ -21,7 +21,7 @@ class NoteRepository @Inject constructor(application: Application) {
fun getNoteByIdFromRoom(noteId: Int): Flow = noteDao.getNoteById(noteId)
- suspend fun insertNoteToRoom(note: Note) = noteDao.insertNote(note)
+ suspend fun insertNoteToRoom(note: Note): Long = noteDao.insertNote(note)
suspend fun updateNoteInRoom(note: Note) = noteDao.updateNote(note)
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
new file mode 100644
index 00000000..0df2c4e2
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/domain/usecase/SaveSelectedImageUseCase.kt
@@ -0,0 +1,62 @@
+package com.aritra.notify.domain.usecase
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.net.Uri
+import android.os.Build
+import android.provider.MediaStore
+import androidx.core.content.FileProvider
+import java.io.File
+
+object SaveSelectedImageUseCase {
+ const val DIRECTORY = "image"
+
+ /**
+ * Returns the image file in the cache directory
+ */
+ fun image(context: Context, id: Int) = File(
+ File(context.externalCacheDir, DIRECTORY).apply {
+ if (!exists()) {
+ mkdirs()
+ }
+ },
+ "image_${id}.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 {
+ // 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 bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ ImageDecoder.decodeBitmap(
+ ImageDecoder.createSource(
+ context.contentResolver,
+ uri
+ )
+ )
+ } else {
+ MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
+ }
+ // compress the image to 80% webp quality before saving
+ bitmap.compress(
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ Bitmap.CompressFormat.WEBP_LOSSY
+ } else {
+ Bitmap.CompressFormat.WEBP
+ },
+ 80,
+ image.outputStream()
+ )
+ FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.provider",
+ image
+ )
+ } catch (_: Exception) {
+ null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/navigation/NotifyApp.kt b/app/src/main/java/com/aritra/notify/navigation/NotifyApp.kt
index 383ff0d6..ae26c595 100644
--- a/app/src/main/java/com/aritra/notify/navigation/NotifyApp.kt
+++ b/app/src/main/java/com/aritra/notify/navigation/NotifyApp.kt
@@ -38,10 +38,6 @@ import com.google.accompanist.permissions.shouldShowRationale
@Composable
fun NotifyApp(navController: NavHostController = rememberNavController()) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- RequestNotificationPermissionDialog()
- }
-
val bottomNavItem = listOf(
BottomNavItem(
name = "Notes",
@@ -136,15 +132,3 @@ fun NotifyApp(navController: NavHostController = rememberNavController()) {
}
}
}
-
-@OptIn(ExperimentalPermissionsApi::class)
-@RequiresApi(Build.VERSION_CODES.TIRAMISU)
-@Composable
-fun RequestNotificationPermissionDialog() {
- val permissionState = rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS)
-
- if (!permissionState.status.isGranted) {
- if (permissionState.status.shouldShowRationale) RationaleDialog()
- else PermissionDialog { permissionState.launchPermissionRequest() }
- }
-}
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/MainActivity.kt b/app/src/main/java/com/aritra/notify/ui/screens/MainActivity.kt
index 73b0f1bb..89d3e068 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/MainActivity.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/MainActivity.kt
@@ -10,7 +10,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
-import com.aritra.notify.biometric.AppBioMetricManager
+import com.aritra.notify.components.biometric.AppBioMetricManager
import com.aritra.notify.navigation.NotifyApp
import com.aritra.notify.ui.theme.NotifyTheme
import com.aritra.notify.viewmodel.MainViewModel
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNoteViewModel.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNoteViewModel.kt
index 2df39bf9..3859dc80 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNoteViewModel.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNoteViewModel.kt
@@ -3,11 +3,13 @@ package com.aritra.notify.ui.screens.notes.addNoteScreen
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.data.repository.NoteRepository
+import com.aritra.notify.domain.models.Note
+import com.aritra.notify.domain.repository.NoteRepository
+import com.aritra.notify.domain.usecase.SaveSelectedImageUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
@@ -16,7 +18,27 @@ class AddNoteViewModel @Inject constructor(
private val addRepository: NoteRepository
) : AndroidViewModel(application) {
- fun insertNote(note: Note) = viewModelScope.launch(Dispatchers.IO) {
- addRepository.insertNoteToRoom(note)
+ fun insertNote(note: Note, onSuccess: () -> Unit) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val id: Int = addRepository.insertNoteToRoom(note).toInt()
+
+ if (note.image != null) {
+ // update the note with the new image uri
+ addRepository.updateNoteInRoom(
+ note.copy(
+ id = id,
+ image = SaveSelectedImageUseCase(
+ context = getApplication(),
+ uri = note.image!!,
+ noteId = id
+ )
+ )
+ )
+ }
+
+ withContext(Dispatchers.Main) {
+ onSuccess()
+ }
+ }
}
}
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNotesScreen.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNotesScreen.kt
index f548de03..741dec8c 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNotesScreen.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/addNoteScreen/AddNotesScreen.kt
@@ -2,24 +2,18 @@ package com.aritra.notify.ui.screens.notes.addNoteScreen
import android.Manifest
-import android.graphics.Bitmap
-import android.graphics.ImageDecoder
import android.net.Uri
-import android.os.Build
-import android.provider.MediaStore
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.background
import androidx.compose.foundation.layout.Arrangement
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
@@ -51,7 +45,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
@@ -67,11 +60,13 @@ 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 com.aritra.notify.R
import com.aritra.notify.components.actions.BottomSheetOptions
import com.aritra.notify.components.actions.SpeechRecognizerContract
-import com.aritra.notify.components.topbar.AddNoteTopBar
import com.aritra.notify.components.dialog.TextDialog
+import com.aritra.notify.components.topbar.AddNoteTopBar
+import com.aritra.notify.domain.models.Note
import com.aritra.notify.utils.Const
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
@@ -86,10 +81,10 @@ fun AddNotesScreen(
navigateBack: () -> Unit
) {
val addViewModel = hiltViewModel()
+ val context = LocalContext.current
var title by remember { mutableStateOf("") }
var description by remember { mutableStateOf("") }
val dateTime by remember { mutableStateOf(Calendar.getInstance().time) }
- var imagePath by remember { mutableStateOf(null) }
var characterCount by remember { mutableIntStateOf(title.length + description.length) }
val cancelDialogState = remember { mutableStateOf(false) }
var showSheet by remember { mutableStateOf(false) }
@@ -103,7 +98,6 @@ fun AddNotesScreen(
val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = skipPartiallyExpanded
)
- val context = LocalContext.current
var photoUri: Uri? by remember { mutableStateOf(null) }
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
@@ -128,16 +122,31 @@ fun AddNotesScreen(
}
)
+ val saveNote = remember {
+ {
+ addViewModel.insertNote(
+ note = Note(
+ id = 0,
+ title = title,
+ note = description,
+ dateTime = dateTime,
+ image = photoUri
+ ),
+ onSuccess = {
+ navigateBack()
+ Toast.makeText(context, "Successfully Saved!", Toast.LENGTH_SHORT).show()
+ }
+ )
+ }
+ }
+
Scaffold(
topBar = {
AddNoteTopBar(
- addViewModel,
+ title = title,
+ description = description,
onBackPress = { cancelDialogState.value = true },
- onSave = { navigateBack() },
- title,
- description,
- dateTime,
- imagePath
+ saveNote = saveNote,
)
},
bottomBar = {
@@ -215,7 +224,6 @@ fun AddNotesScreen(
IconButton(
onClick = {
photoUri = null
- imagePath = null
},
) {
Icon(
@@ -225,24 +233,12 @@ fun AddNotesScreen(
}
}
- val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- ImageDecoder.decodeBitmap(
- ImageDecoder.createSource(
- context.contentResolver,
- photoUri!!
- )
- )
- } else {
- MediaStore.Images.Media.getBitmap(context.contentResolver, photoUri!!)
- }
- Image(
- bitmap = bitmap.asImageBitmap(),
+ AsyncImage(
+ model = photoUri,
contentDescription = stringResource(R.string.image),
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Crop
)
-
- imagePath = bitmap
}
TextField(
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditNotesScreen.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditNotesScreen.kt
index 46b97942..5b4ab5b3 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditNotesScreen.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditNotesScreen.kt
@@ -1,10 +1,9 @@
package com.aritra.notify.ui.screens.notes.editNoteScreen
+import android.widget.Toast
import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Arrangement
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.padding
@@ -13,23 +12,20 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Close
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
@@ -45,7 +41,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.rememberAsyncImagePainter
import com.aritra.notify.R
import com.aritra.notify.components.topbar.EditNoteTopBar
-import com.aritra.notify.data.models.Note
+import com.aritra.notify.domain.models.Note
import com.aritra.notify.utils.Const
import java.text.SimpleDateFormat
import java.util.Date
@@ -56,23 +52,35 @@ fun EditNotesScreen(
noteId: Int,
navigateBack: () -> Unit
) {
- val note = Note(noteId, "","", Date(),null)
+ val note = Note(noteId, "", "", Date(), null)
+ val context = LocalContext.current
val editViewModel = hiltViewModel()
val title = editViewModel.noteModel.observeAsState().value?.title ?: ""
val description = editViewModel.noteModel.observeAsState().value?.note ?: ""
- val imagePath = editViewModel.noteModel.observeAsState().value?.imagePath
+ val imagePath = editViewModel.noteModel.observeAsState().value?.image
val dateTime = editViewModel.noteModel.observeAsState().value?.dateTime
val focus = LocalFocusManager.current
- 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 saveNote: () -> Unit = remember {
+ {
+ editViewModel.updateNotes {
+ navigateBack()
+ Toast.makeText(context, "Successfully Updated!", Toast.LENGTH_SHORT).show()
+ }
+ navigateBack()
+ }
+ }
+
LaunchedEffect(Unit) {
editViewModel.getNoteById(noteId)
}
Scaffold(
topBar = {
- dateTime?.let {
- EditNoteTopBar(note,editViewModel, noteId, navigateBack, title, description, imagePath)
+ if (dateTime != null) {
+ EditNoteTopBar(note, navigateBack, title, description, saveNote)
}
}
) {
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditScreenViewModel.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditScreenViewModel.kt
index 23ede807..a11cf620 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditScreenViewModel.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/editNoteScreen/EditScreenViewModel.kt
@@ -1,18 +1,19 @@
package com.aritra.notify.ui.screens.notes.editNoteScreen
import android.app.Application
-import android.graphics.Bitmap
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
+import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.data.repository.NoteRepository
+import com.aritra.notify.domain.models.Note
+import com.aritra.notify.domain.repository.NoteRepository
+import com.aritra.notify.domain.usecase.SaveSelectedImageUseCase
+import com.aritra.notify.utils.toFile
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject
@@ -22,16 +23,42 @@ class EditScreenViewModel @Inject constructor(
private val editScreenRepository: NoteRepository
) : AndroidViewModel(application) {
- var noteModel = MutableLiveData(Note(0,"","",Date(),null))
-
+ var noteModel = MutableLiveData(Note(0, "", "", Date(), null))
fun getNoteById(noteId: Int) = viewModelScope.launch(Dispatchers.IO) {
editScreenRepository.getNoteByIdFromRoom(noteId).collect { response ->
noteModel.postValue(response)
}
}
- fun updateNotes(noteModel: Note) = viewModelScope.launch(Dispatchers.IO) {
- editScreenRepository.updateNoteInRoom(noteModel)
+ fun updateNotes(onSuccess: () -> Unit) = viewModelScope.launch(Dispatchers.IO) {
+ val newNote = noteModel.value ?: return@launch
+ // retrieve the note from the database to check if the image has been modified
+ val oldNote = editScreenRepository.getNoteByIdFromRoom(newNote.id).first()
+ // exit the method if the note has not been modified
+ 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()
+ }
+ editScreenRepository.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) {
+ // if the image has been modified, save the new image uri
+ SaveSelectedImageUseCase(
+ getApplication(),
+ newNote.image!!,
+ newNote.id
+ )
+ } else null
+ )
+ )
+
+ withContext(Dispatchers.Main) {
+ onSuccess()
+ }
}
fun updateTitle(title: String) {
@@ -42,7 +69,7 @@ class EditScreenViewModel @Inject constructor(
noteModel.postValue(noteModel.value?.copy(note = description))
}
- fun updateImage(image: Bitmap?) {
- noteModel.postValue(noteModel.value?.copy(imagePath = image))
+ fun updateImage(image: Uri?) {
+ noteModel.postValue(noteModel.value?.copy(image = image))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreen.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreen.kt
index 37b3de95..ecf842c5 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreen.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreen.kt
@@ -40,8 +40,8 @@ import com.aritra.notify.R
import com.aritra.notify.components.actions.BackPressHandler
import com.aritra.notify.components.actions.LayoutToggleButton
import com.aritra.notify.components.actions.NoList
-import com.aritra.notify.components.actions.SwipeDelete
import com.aritra.notify.components.note.GridNoteCard
+import com.aritra.notify.components.note.NotesCard
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -119,9 +119,7 @@ fun NoteScreen(
}) { _, notesModel ->
GridNoteCard(
notesModel,
- viewModel,
navigateToUpdateNoteScreen,
- isGridView
)
}
}
@@ -136,7 +134,7 @@ fun NoteScreen(
items(listOfAllNotes.filter { note ->
note.title.contains(searchQuery, true)
}) { notesModel ->
- SwipeDelete(notesModel, viewModel, navigateToUpdateNoteScreen)
+ NotesCard(noteModel = notesModel, navigateToUpdateNoteScreen = navigateToUpdateNoteScreen)
}
}
}
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreenViewModel.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreenViewModel.kt
index 35f6613a..2383081c 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreenViewModel.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/homeScreen/NoteScreenViewModel.kt
@@ -4,8 +4,8 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.data.repository.NoteRepository
+import com.aritra.notify.domain.models.Note
+import com.aritra.notify.domain.repository.NoteRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
diff --git a/app/src/main/java/com/aritra/notify/ui/screens/settingsScreen/SettingsViewModel.kt b/app/src/main/java/com/aritra/notify/ui/screens/settingsScreen/SettingsViewModel.kt
index 762fb1b6..7762b2d2 100644
--- a/app/src/main/java/com/aritra/notify/ui/screens/settingsScreen/SettingsViewModel.kt
+++ b/app/src/main/java/com/aritra/notify/ui/screens/settingsScreen/SettingsViewModel.kt
@@ -8,12 +8,12 @@ import androidx.compose.runtime.setValue
import androidx.datastore.preferences.core.edit
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
-import com.aritra.notify.biometric.AppBioMetricManager
-import com.aritra.notify.biometric.BiometricAuthListener
+import com.aritra.notify.components.biometric.AppBioMetricManager
+import com.aritra.notify.components.biometric.BiometricAuthListener
import com.aritra.notify.data.db.NoteDatabase
-import com.aritra.notify.data.models.Note
-import com.aritra.notify.data.repository.BackupRepository
-import com.aritra.notify.data.repository.NoteRepository
+import com.aritra.notify.domain.models.Note
+import com.aritra.notify.domain.repository.BackupRepository
+import com.aritra.notify.domain.repository.NoteRepository
import com.aritra.notify.di.DataStoreUtil
import com.aritra.notify.ui.screens.MainActivity
import dagger.hilt.android.lifecycle.HiltViewModel
diff --git a/app/src/main/java/com/aritra/notify/utils/Const.kt b/app/src/main/java/com/aritra/notify/utils/Const.kt
index 7960f635..fcce4269 100644
--- a/app/src/main/java/com/aritra/notify/utils/Const.kt
+++ b/app/src/main/java/com/aritra/notify/utils/Const.kt
@@ -2,7 +2,7 @@ package com.aritra.notify.utils
object Const {
- const val DATABASE_FILE_NAME = "Notify.db"
+ const val DATABASE_FILE_NAME = "Notify.zip"
const val DB_NAME = "Note_database"
const val DATE_TIME_FORMAT = "dd MMM, hh:mm a"
const val DATE_FORMAT = "dd MMM"
diff --git a/app/src/main/java/com/aritra/notify/utils/Context.kt b/app/src/main/java/com/aritra/notify/utils/Context.kt
new file mode 100644
index 00000000..ddc53ec3
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/utils/Context.kt
@@ -0,0 +1,20 @@
+package com.aritra.notify.utils
+
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.provider.OpenableColumns
+
+fun Context.getFileInfo(
+ file: Uri,
+ columnName: String = OpenableColumns.DISPLAY_NAME,
+ data: Cursor.(Int) -> T,
+) = contentResolver?.query(
+ file, null, null, null, null
+)?.run {
+ val index = getColumnIndex(columnName)
+ moveToFirst()
+ val result = data(index)
+ close()
+ result
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/utils/CsvIo.kt b/app/src/main/java/com/aritra/notify/utils/CsvIo.kt
new file mode 100644
index 00000000..11261eff
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/utils/CsvIo.kt
@@ -0,0 +1,140 @@
+package com.aritra.notify.utils
+
+internal object CsvIo {
+
+ /** The character used for escaping quotes. */
+ const val DEFAULT_ESCAPE_CHARACTER = '"'
+
+ /** The default separator to use if none is supplied to the constructor. */
+ const val DEFAULT_SEPARATOR = ';'
+
+ /**
+ * The default quote character to use if none is supplied to the
+ * constructor.
+ */
+ const val DEFAULT_QUOTE_CHARACTER = '"'
+
+ /** The quote constant to use when you wish to suppress all quoting. */
+ const val NO_QUOTE_CHARACTER = '\u0000'
+
+ /** The escape constant to use when you wish to suppress all escaping. */
+ const val NO_ESCAPE_CHARACTER = '\u0000'
+
+ /** Default line terminator uses platform encoding. */
+ const val DEFAULT_LINE_END = "\n"
+
+ /**
+ * Constructs a Csv Writer with supplied separator, quote char, escape char and line ending.
+ *
+ * @param writer
+ * the writer to an underlying CSV source.
+ * @param separator
+ * the delimiter to use for separating entries
+ * @param quoteChar
+ * the character to use for quoted elements
+ * @param escapeChar
+ * the character to use for escaping quoteChars or escapeChars
+ * @param lineEnd
+ * the line feed terminator to use
+ */
+ class Writer(
+ private val writer: java.io.Writer?,
+ private val separator: Char = DEFAULT_SEPARATOR,
+ private val quoteChar: Char = DEFAULT_QUOTE_CHARACTER,
+ private val escapeChar: Char = DEFAULT_ESCAPE_CHARACTER,
+ private val lineEnd: String = DEFAULT_LINE_END
+ ) {
+ init {
+ // write the separator to the top of the file
+ writer?.write("sep=$separator\n")
+ }
+
+ /**
+ * Writes the next line to the file.
+ *
+ * @param nextLine
+ * a string array with each comma-separated element as a separate
+ * entry.
+ */
+ fun writeNext(nextLine: Array) {
+ val builder = StringBuilder()
+ for (i in nextLine.indices) {
+ if (i != 0) {
+ builder.append(separator)
+ }
+ val nextElement = nextLine[i]
+
+ if (quoteChar != NO_QUOTE_CHARACTER)
+ builder.append(quoteChar)
+
+ for (element in nextElement) {
+ if (escapeChar == NO_ESCAPE_CHARACTER && (element == quoteChar || element == escapeChar)) {
+ builder.append(escapeChar)
+ }
+ builder.append(element)
+ }
+
+ if (quoteChar != NO_QUOTE_CHARACTER)
+ builder.append(quoteChar)
+ }
+ builder.append(lineEnd)
+ writer?.write(builder.toString())
+ }
+
+ /**
+ * Close the underlying stream writer flushing any buffered content.
+ */
+ fun close() = writer?.runCatching {
+ flush()
+ close()
+ }
+ }
+
+ /**
+ * Constructs a Csv Reader with supplied separator, quote char, escape char and line ending.
+ *
+ * @param reader
+ * the writer to an underlying CSV source.
+ * @param separator
+ * the delimiter to use for separating entries
+ * @param quoteChar
+ * the character to use for quoted elements
+ * @param escapeChar
+ * the character to use for escaping quoteChars or escapeChars
+ * @param lineEnd
+ * the line feed terminator to use
+ */
+ class Reader(
+ private val reader: java.io.Reader?,
+ private val separator: Char = DEFAULT_SEPARATOR,
+ private val quoteChar: Char = DEFAULT_QUOTE_CHARACTER,
+ private val escapeChar: Char = DEFAULT_ESCAPE_CHARACTER,
+ private val lineEnd: String = DEFAULT_LINE_END
+ ) {
+
+ /**
+ * Reads the entire file into a List with each element being a String[] of tokens.
+ */
+ fun rows(): List> {
+ var lines = reader?.readLines()
+ // remove the first line if it starts with "sep="
+ lines = if (lines?.firstOrNull()?.startsWith("sep=") == true) {
+ lines.drop(1)
+ } else lines
+ // split the lines into chunks and trim the chunks
+ return lines?.map { line ->
+ val tokens = line.split(separator)
+ tokens.map { token ->
+ token.replace(quoteChar, ' ').replace(escapeChar, ' ').trim()
+ }.toTypedArray()
+ } ?: emptyList()
+ }
+
+ /**
+ * Close the underlying stream reader.
+ */
+ fun close() = reader?.runCatching {
+ close()
+ }
+ }
+}
diff --git a/app/src/main/java/com/aritra/notify/utils/DateTypeConverter.kt b/app/src/main/java/com/aritra/notify/utils/DateTypeConverter.kt
deleted file mode 100644
index 0b9c1687..00000000
--- a/app/src/main/java/com/aritra/notify/utils/DateTypeConverter.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.aritra.notify.utils
-
-import androidx.room.TypeConverter
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-
-class DateTypeConverter {
-
- companion object {
- private val displayDateFormat =
- SimpleDateFormat(Const.DATE_TIME_FORMAT, Locale.getDefault())
-
- @TypeConverter
- @JvmStatic
- fun toDate(value: String?): Date? {
- return if (value.isNullOrEmpty()) {
- null
- } else {
- displayDateFormat.parse(value)
- }
- }
-
- @TypeConverter
- @JvmStatic
- fun toString(date: Date?): String? {
- return date?.let { displayDateFormat.format(date) }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/utils/Uri.kt b/app/src/main/java/com/aritra/notify/utils/Uri.kt
new file mode 100644
index 00000000..9784165e
--- /dev/null
+++ b/app/src/main/java/com/aritra/notify/utils/Uri.kt
@@ -0,0 +1,37 @@
+package com.aritra.notify.utils
+
+import android.content.Context
+import android.net.Uri
+import android.os.Environment
+import androidx.core.net.toFile
+import java.io.File
+
+/**
+ * Converts an android content uri to a file.
+ */
+fun Uri.toFile(context: Context): File? {
+ if (!exists()) return null
+
+ return try {
+ toFile()
+ } catch (e: IllegalArgumentException) {
+ context.getFileInfo(this) {
+ File(getString(it))
+ }
+ }
+}
+
+/**
+ * Checks if an android content uri exists.
+ */
+fun Uri.exists(): Boolean {
+ return try {
+ toFile().exists()
+ } catch (e: IllegalArgumentException) {
+ val path = path?.replace(
+ "external_files",
+ Environment.getExternalStorageDirectory().toString()
+ ).toString()
+ File(path).exists()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritra/notify/viewmodel/MainViewModel.kt b/app/src/main/java/com/aritra/notify/viewmodel/MainViewModel.kt
index 85db1f46..e832447a 100644
--- a/app/src/main/java/com/aritra/notify/viewmodel/MainViewModel.kt
+++ b/app/src/main/java/com/aritra/notify/viewmodel/MainViewModel.kt
@@ -2,8 +2,8 @@ package com.aritra.notify.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.aritra.notify.biometric.AppBioMetricManager
-import com.aritra.notify.biometric.BiometricAuthListener
+import com.aritra.notify.components.biometric.AppBioMetricManager
+import com.aritra.notify.components.biometric.BiometricAuthListener
import com.aritra.notify.di.DataStoreUtil
import com.aritra.notify.ui.screens.MainActivity
import dagger.hilt.android.lifecycle.HiltViewModel
diff --git a/app/src/main/res/xml/file_provider_paths.xml b/app/src/main/res/xml/file_provider_paths.xml
index 389acff7..ea608578 100644
--- a/app/src/main/res/xml/file_provider_paths.xml
+++ b/app/src/main/res/xml/file_provider_paths.xml
@@ -1,6 +1,14 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index be0c6404..6373fc0d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,7 +1,6 @@
buildscript {
dependencies {
classpath ("com.android.tools.build:gradle:3.4.0")
- classpath ("com.google.gms:google-services:4.3.15")
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {