Skip to content
Open
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: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,5 @@ dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.navigation.compose)
implementation(libs.accompanist.permissions)
}
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
android:name="android.permission.FOREGROUND_SERVICE"
tools:node="remove"/>


<uses-permission android:name="android.permission.RECORD_AUDIO" />

<application
android:name=".App"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ object DatabaseConst {
/**
* Database version
*/
const val NOTES_DATABASE_VERSION = 4
const val NOTES_DATABASE_VERSION = 5

/**
* Exported notes room database name and extension
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/kin/easynotes/data/local/dao/VoiceNoteDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.kin.easynotes.data.local.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.kin.easynotes.data.local.models.VoiceNote
import kotlinx.coroutines.flow.Flow

@Dao
interface VoiceNoteDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertVoiceNote(voiceNote: VoiceNote)

@Query("SELECT * FROM voice_notes ORDER BY createdAt DESC")
fun getAllVoiceNotes(): Flow<List<VoiceNote>>

@Delete
suspend fun deleteVoiceNote(voiceNote: VoiceNote)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import com.kin.easynotes.core.constant.DatabaseConst
import com.kin.easynotes.data.local.dao.NoteDao
import com.kin.easynotes.data.local.dao.VoiceNoteDao
import com.kin.easynotes.data.local.models.VoiceNote
import com.kin.easynotes.domain.model.Note

@Database(
entities = [Note::class],
entities = [Note::class, VoiceNote::class],
version = DatabaseConst.NOTES_DATABASE_VERSION,
exportSchema = false
)
abstract class NoteDatabase : RoomDatabase() {

abstract fun noteDao(): NoteDao
abstract fun voiceNoteDao(): VoiceNoteDao
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.kin.easynotes.data.local.database


import android.app.Application
import androidx.room.Room
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.kin.easynotes.core.constant.DatabaseConst
import com.kin.easynotes.data.local.dao.NoteDao
import com.kin.easynotes.data.local.dao.VoiceNoteDao

class NoteDatabaseProvider(private val application: Application) {

Expand All @@ -21,10 +21,12 @@ class NoteDatabaseProvider(private val application: Application) {
}

private fun buildDatabase(): NoteDatabase {
return Room.databaseBuilder(application.applicationContext,
return Room.databaseBuilder(
application.applicationContext,
NoteDatabase::class.java,
DatabaseConst.NOTES_DATABASE_FILE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_2_4)
DatabaseConst.NOTES_DATABASE_FILE_NAME
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.build()
}

Expand All @@ -37,9 +39,11 @@ class NoteDatabaseProvider(private val application: Application) {
fun noteDao(): NoteDao {
return instance().noteDao()
}
}


fun voiceNoteDao(): VoiceNoteDao {
return instance().voiceNoteDao()
}
}

private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
Expand All @@ -59,9 +63,8 @@ private val MIGRATION_3_4 = object : Migration(3, 4) {
}
}

private val MIGRATION_2_4 = object : Migration(2, 4) {
private val MIGRATION_4_5 = object : Migration(4, 5) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `notes-table` ADD COLUMN `pinned` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE `notes-table` ADD COLUMN `encrypted` INTEGER NOT NULL DEFAULT 0")
db.execSQL("CREATE TABLE IF NOT EXISTS `voice_notes` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `filePath` TEXT NOT NULL, `duration` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL)")
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/com/kin/easynotes/data/local/models/VoiceNote.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.kin.easynotes.data.local.models

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "voice_notes")
data class VoiceNote(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val filePath: String,
val duration: Long,
val createdAt: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.kin.easynotes.data.repository

import com.kin.easynotes.data.local.dao.VoiceNoteDao
import com.kin.easynotes.data.local.models.VoiceNote
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class VoiceNotesRepository @Inject constructor(
private val voiceNoteDao: VoiceNoteDao
) {

fun getAllVoiceNotes(): Flow<List<VoiceNote>> = voiceNoteDao.getAllVoiceNotes()

suspend fun insertVoiceNote(voiceNote: VoiceNote) {
voiceNoteDao.insertVoiceNote(voiceNote)
}

suspend fun deleteVoiceNote(voiceNote: VoiceNote) {
voiceNoteDao.deleteVoiceNote(voiceNote)
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/com/kin/easynotes/di/DataModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import android.app.Application
import android.os.Handler
import android.content.Context
import android.os.Looper
import com.kin.easynotes.data.local.dao.VoiceNoteDao
import com.kin.easynotes.data.local.database.NoteDatabaseProvider
import com.kin.easynotes.data.repository.ImportExportRepository
import com.kin.easynotes.data.repository.NoteRepositoryImpl
import com.kin.easynotes.data.repository.SettingsRepositoryImpl
import com.kin.easynotes.data.repository.VoiceNotesRepository
import com.kin.easynotes.presentation.components.EncryptionHelper
import dagger.Module
import dagger.Provides
Expand Down Expand Up @@ -99,4 +101,16 @@ object ApplicationModule {
fun provideHandler(): Handler {
return Handler(Looper.getMainLooper())
}

@Provides
@Singleton
fun provideVoiceNoteDao(noteDatabaseProvider: NoteDatabaseProvider): VoiceNoteDao {
return noteDatabaseProvider.voiceNoteDao()
}

@Provides
@Singleton
fun provideVoiceNotesRepository(voiceNoteDao: VoiceNoteDao): VoiceNotesRepository {
return VoiceNotesRepository(voiceNoteDao)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.kin.easynotes.domain.usecase.voice_notes

import com.kin.easynotes.data.local.models.VoiceNote
import com.kin.easynotes.data.repository.VoiceNotesRepository
import javax.inject.Inject

class AddVoiceNoteUseCase @Inject constructor(
private val repository: VoiceNotesRepository
) {
suspend operator fun invoke(voiceNote: VoiceNote) {
repository.insertVoiceNote(voiceNote)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.kin.easynotes.domain.usecase.voice_notes

import com.kin.easynotes.data.local.models.VoiceNote
import com.kin.easynotes.data.repository.VoiceNotesRepository
import javax.inject.Inject

class DeleteVoiceNoteUseCase @Inject constructor(
private val repository: VoiceNotesRepository
) {
suspend operator fun invoke(voiceNote: VoiceNote) {
repository.deleteVoiceNote(voiceNote)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.kin.easynotes.domain.usecase.voice_notes

import com.kin.easynotes.data.local.models.VoiceNote
import com.kin.easynotes.data.repository.VoiceNotesRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class GetAllVoiceNotesUseCase @Inject constructor(
private val repository: VoiceNotesRepository
) {
operator fun invoke(): Flow<List<VoiceNote>> {
return repository.getAllVoiceNotes()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.kin.easynotes.presentation.components

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardVoice
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable

@Composable
fun VoiceNotesButton(onClick: () -> Unit) {
IconButton(onClick = onClick) {
Icon(imageVector = Icons.Default.KeyboardVoice, contentDescription = "Voice Notes")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.kin.easynotes.presentation.screens.edit.EditNoteView
import com.kin.easynotes.presentation.screens.home.HomeView
import com.kin.easynotes.presentation.screens.settings.model.SettingsViewModel
import com.kin.easynotes.presentation.screens.terms.TermsScreen
import com.kin.easynotes.presentation.screens.voice_notes.VoiceNotesScreen
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -44,6 +45,7 @@ fun AppNavHost(settingsModel: SettingsViewModel,navController: NavHostController
)
)
},
onVoiceNotesClicked = { navController.navigate(NavRoutes.VoiceNotes.route) },
settingsModel = settingsModel
)
}
Expand Down Expand Up @@ -87,6 +89,10 @@ fun AppNavHost(settingsModel: SettingsViewModel,navController: NavHostController
}
}

animatedComposable(NavRoutes.VoiceNotes.route) {
VoiceNotesScreen()
}

settingScreens.forEach { (route, screen) ->
if (route == NavRoutes.Settings.route) {
slideInComposable(route) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ sealed class NavRoutes(val route: String) {
}
data object Terms : NavRoutes("terms")
data object Settings : NavRoutes("settings")
data object VoiceNotes : NavRoutes("voice_notes")
data object ColorStyles : NavRoutes("settings/color_styles")
data object Language : NavRoutes("settings/language")
data object Cloud : NavRoutes("settings/cloud")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.kin.easynotes.presentation.components.SelectAllButton
import com.kin.easynotes.presentation.components.SettingsButton
import com.kin.easynotes.presentation.components.TitleText
import com.kin.easynotes.presentation.components.VaultButton
import com.kin.easynotes.presentation.components.VoiceNotesButton
import com.kin.easynotes.presentation.components.defaultScreenEnterAnimation
import com.kin.easynotes.presentation.components.defaultScreenExitAnimation
import com.kin.easynotes.presentation.screens.home.viewmodel.HomeViewModel
Expand All @@ -54,7 +55,8 @@ fun HomeView (
viewModel: HomeViewModel = hiltViewModel(),
settingsModel: SettingsViewModel,
onSettingsClicked: () -> Unit,
onNoteClicked: (Int, Boolean) -> Unit
onNoteClicked: (Int, Boolean) -> Unit,
onVoiceNotesClicked: () -> Unit
) {
val context = LocalContext.current
if (viewModel.isPasswordPromptVisible.value) {
Expand Down Expand Up @@ -113,7 +115,8 @@ fun HomeView (
viewModel.toggleIsVaultMode(false)
viewModel.encryptionHelper.removePassword()
}
}
},
onVoiceNotesClicked = onVoiceNotesClicked
)
}
},
Expand Down Expand Up @@ -217,7 +220,8 @@ private fun NotesSearchBar(
onQueryChange: (String) -> Unit,
onSettingsClick: () -> Unit,
onVaultClicked: () -> Unit,
onClearClick: () -> Unit
onClearClick: () -> Unit,
onVoiceNotesClicked: () -> Unit
) {
SearchBar(
modifier = Modifier
Expand All @@ -231,6 +235,7 @@ private fun NotesSearchBar(
if (query.isNotBlank()) {
CloseButton(contentDescription = "Clear", onCloseClicked = onClearClick)
}
VoiceNotesButton(onClick = onVoiceNotesClicked)
if (settingsModel.settings.value.vaultSettingEnabled) {
VaultButton(viewModel.isVaultMode.value) { onVaultClicked() }
}
Expand Down Expand Up @@ -258,4 +263,4 @@ fun sorter(descending: Boolean): Comparator<Note> {
} else {
compareBy { it.createdAt }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.kin.easynotes.presentation.screens.voice_notes

import android.media.MediaPlayer
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.kin.easynotes.data.local.models.VoiceNote
import com.kin.easynotes.presentation.utils.TimerUtil

@Composable
fun VoiceNoteItem(voiceNote: VoiceNote, onDelete: (VoiceNote) -> Unit) {
val mediaPlayer = remember { MediaPlayer() }

Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable { /* Handle item click if needed */ },
verticalAlignment = Alignment.CenterVertically
) {
Text(text = TimerUtil.formatDuration(voiceNote.duration), modifier = Modifier.weight(1f))
IconButton(onClick = {
if (mediaPlayer.isPlaying) {
mediaPlayer.pause()
} else {
mediaPlayer.apply {
reset()
setDataSource(voiceNote.filePath)
prepare()
start()
}
}
}) {
Icon(imageVector = Icons.Default.PlayArrow, contentDescription = "Play/Pause")
}
Spacer(modifier = Modifier.width(8.dp))
IconButton(onClick = { onDelete(voiceNote) }) {
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
}
}
}
Loading