Skip to content

Commit

Permalink
Voice notes impl
Browse files Browse the repository at this point in the history
  • Loading branch information
shubertm committed Dec 18, 2023
1 parent f6139b7 commit e77fb6a
Show file tree
Hide file tree
Showing 38 changed files with 1,376 additions and 91 deletions.
2 changes: 0 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ dependencies {

implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'

implementation 'com.google.code.gson:gson:2.8.9'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>

<application
android:name=".App"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package dev.arkbuilders.arkmemo.contracts

import android.Manifest
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.RequiresApi
import com.simplemobiletools.commons.helpers.PERMISSION_RECORD_AUDIO

class PermissionContract: ActivityResultContract<String, Boolean>() {

Expand All @@ -19,4 +22,4 @@ class PermissionContract: ActivityResultContract<String, Boolean>() {
override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
return Environment.isExternalStorageManager()
}
}
}
21 changes: 21 additions & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/di/MediaModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.arkbuilders.arkmemo.di

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dev.arkbuilders.arkmemo.media.ArkMediaPlayer
import dev.arkbuilders.arkmemo.media.ArkMediaPlayerImpl
import dev.arkbuilders.arkmemo.media.ArkAudioRecorder
import dev.arkbuilders.arkmemo.media.ArkAudioRecorderImpl

@Module
@InstallIn(SingletonComponent::class)
abstract class MediaModule {

@Binds
abstract fun bindArkAudioRecorder(impl: ArkAudioRecorderImpl): ArkAudioRecorder

@Binds
abstract fun bindArkMediaPlayer(impl: ArkMediaPlayerImpl): ArkMediaPlayer
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import dev.arkbuilders.arkmemo.repo.NotesRepo
import dev.arkbuilders.arkmemo.repo.text.TextNotesRepo
import dev.arkbuilders.arkmemo.models.GraphicNote
import dev.arkbuilders.arkmemo.models.TextNote
import dev.arkbuilders.arkmemo.models.VoiceNote
import dev.arkbuilders.arkmemo.preferences.MemoPreferences
import dev.arkbuilders.arkmemo.repo.NotesRepoHelper
import dev.arkbuilders.arkmemo.repo.voices.VoiceNotesRepo


@InstallIn(SingletonComponent::class)
Expand All @@ -24,6 +26,9 @@ abstract class RepositoryModule {
@Binds
abstract fun bindGraphicNotesRepo(impl: GraphicNotesRepo): NotesRepo<GraphicNote>

@Binds
abstract fun bindVoiceNotesRepo(impl: VoiceNotesRepo): NotesRepo<VoiceNote>

companion object {
@Provides
fun provideNotesRepoHelper(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dev.arkbuilders.arkmemo.media

import java.nio.file.Path

interface ArkAudioRecorder {

fun init()

fun start()

fun pause()

fun stop()

fun resume()

fun reset()

fun maxAmplitude(): Int

fun getRecording(): Path
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dev.arkbuilders.arkmemo.media

import android.content.Context
import android.media.MediaRecorder
import android.os.Build
import dagger.hilt.android.qualifiers.ApplicationContext
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.createTempFile

class ArkAudioRecorderImpl @Inject constructor(
@ApplicationContext private val context: Context
): ArkAudioRecorder {

private var recorder: MediaRecorder? = null

private val tempFile = createTempFile().toFile()

override fun init() {
recorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
MediaRecorder(context)
else MediaRecorder()
recorder?.apply {
setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
setOutputFile(tempFile)
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
prepare()
}
}

override fun start() {
recorder?.start()
}

override fun pause() {
recorder?.pause()
}

override fun resume() {
recorder?.resume()
}

override fun reset() {
recorder?.reset()
}

override fun stop() {
recorder?.let {
it.stop()
it.release()
}
recorder = null
}

override fun maxAmplitude(): Int = recorder?.maxAmplitude!!

override fun getRecording(): Path = tempFile.toPath()
}
24 changes: 24 additions & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.arkbuilders.arkmemo.media

import android.media.MediaPlayer

interface ArkMediaPlayer: MediaPlayer.OnCompletionListener {

var onCompletion: () -> Unit

fun init(path: String)

fun play()

fun stop()

fun pause()

fun seekTo(point: Int)

fun duration(): Int

fun currentPosition(): Int

fun isPlaying(): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dev.arkbuilders.arkmemo.media

import android.media.MediaPlayer
import javax.inject.Inject

class ArkMediaPlayerImpl @Inject constructor(): ArkMediaPlayer {

private var player: MediaPlayer? = null

override var onCompletion: () -> Unit = {}

override fun init(path: String) {
player = MediaPlayer().apply {
setDataSource(path)
prepare()
}
}

override fun play() {
player?.start()
}

override fun stop() {
player?.let {
it.stop()
it.release()
}
player = null
}

override fun pause() {
player?.pause()
}

override fun seekTo(point: Int) {
player?.seekTo(point)
}

override fun duration(): Int = player?.duration!!

override fun currentPosition(): Int = player?.currentPosition!!

override fun isPlaying(): Boolean = player?.isPlaying!!

override fun onCompletion(p0: MediaPlayer?) {
onCompletion()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class GraphicNote(
override val title: String = "",
val description: String = "",
override val description: String = "",
@IgnoredOnParcel
val svg: SVG? = null,
@IgnoredOnParcel
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/models/Note.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import dev.arkbuilders.arklib.data.index.Resource

interface Note {
val title: String
val description: String
var resource: Resource?
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class TextNote (
override val title: String = "",
val description: String = "",
override val description: String = "",
val text: String = "",
@IgnoredOnParcel
override var resource: Resource? = null
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/models/VoiceNote.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.arkbuilders.arkmemo.models

import android.os.Parcelable
import dev.arkbuilders.arklib.data.index.Resource
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import java.nio.file.Path
import kotlin.io.path.createTempFile

@Parcelize
class VoiceNote(
override val title: String = "",
override val description: String = "",
val duration: String = "",
@IgnoredOnParcel
var path: Path = createTempFile(),
@IgnoredOnParcel
override var resource: Resource? = null
): Note, Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package dev.arkbuilders.arkmemo.repo.voices

import android.util.Log
import dev.arkbuilders.arklib.computeId
import dev.arkbuilders.arklib.data.index.Resource
import dev.arkbuilders.arkmemo.di.IO_DISPATCHER
import dev.arkbuilders.arkmemo.models.GraphicNote
import dev.arkbuilders.arkmemo.models.SaveNoteResult
import dev.arkbuilders.arkmemo.models.VoiceNote
import dev.arkbuilders.arkmemo.preferences.MemoPreferences
import dev.arkbuilders.arkmemo.repo.NotesRepo
import dev.arkbuilders.arkmemo.repo.NotesRepoHelper
import dev.arkbuilders.arkmemo.utils.listFiles
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import java.nio.file.Path
import javax.inject.Inject
import javax.inject.Named
import kotlin.io.path.exists
import kotlin.io.path.fileSize
import kotlin.io.path.createTempFile
import kotlin.io.path.extension
import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.name

class VoiceNotesRepo @Inject constructor(
private val memoPreferences: MemoPreferences,
@Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher,
private val helper: NotesRepoHelper
): NotesRepo<VoiceNote> {

private lateinit var root: Path

override suspend fun init() {
root = memoPreferences.getNotesStorage()
helper.init()
}

override suspend fun read(): List<VoiceNote> = withContext(iODispatcher) {
readStorage()
}

override suspend fun delete(note: VoiceNote) {
helper.deleteNote(note)
}

override suspend fun save(note: VoiceNote, callback: (SaveNoteResult) -> Unit) {
write(note) { callback(it) }
}

private suspend fun write(
note: VoiceNote,
callback: (SaveNoteResult) -> Unit
) = withContext(iODispatcher) {
val tempPath = note.path
val size = tempPath.fileSize()
val id = computeId(size, tempPath)

Log.d(VOICES_REPO, "initial resource name is ${tempPath.name}")

helper.persistNoteProperties(resourceId = id, noteTitle = note.title)

val resourcePath = root.resolve("${id}.$VOICE_EXT")
if (resourcePath.exists()) {
Log.d(
VOICES_REPO,
"resource with similar content already exists"
)
callback(SaveNoteResult.ERROR_EXISTING)
return@withContext
}

helper.renameResource(
note,
tempPath,
resourcePath,
id
)
note.path = resourcePath
Log.d(VOICES_REPO, "resource renamed to $resourcePath successfully")
callback(SaveNoteResult.SUCCESS)
}

private suspend fun readStorage(): List<VoiceNote> = withContext(iODispatcher) {
root.listFiles(VOICE_EXT) { path ->
val id = computeId(path.fileSize(), path)
val resource = Resource(
id = id,
name = path.name,
extension = path.extension,
modified = path.getLastModifiedTime()
)

val userNoteProperties = helper.readProperties(id, "")
VoiceNote(
title = userNoteProperties.title,
description = userNoteProperties.description,
path = path,
resource = resource
)
}
}
}

private const val VOICES_REPO = "voices-repo"
private const val VOICE_EXT = "3gp"
Loading

0 comments on commit e77fb6a

Please sign in to comment.